base.py

Base UI elements and core classes for LunaEngine UI system.

LOCATION: lunaengine/ui/elements/base.py

DESCRIPTION:
Defines the foundational UIElement class and supporting components:
- FontManager: central font loading and caching, now with atlas integration.
- UIState and ElementStyle: state handling and theming.
- UIElement: base class for all interactive UI components.

KEY FEATURES:
- Pygame font system initialization and caching.
- Support for system fonts and file fonts.
- Atlas-based font resolution (via engine's atlas).
- UI state machine (normal, hover, pressed, disabled).
- Theming with ThemeManager.
- Unique ID generation for each element.
- Hierarchical parent-child relationships.
- Mouse and controller input handling.
- Tooltip integration.

DEPENDENCIES:
- pygame: For font rendering and surface operations.
- ..themes: Theme management for colors and styles.
- ...core.renderer: OpenGL-based rendering backend.
- ...backend.types: Input state and layer types.

class _UIDGenerator

Description

Internal class for generating unique IDs for UI elements.

Generates IDs in the format: ui_{element_type}_{counter}
Example: ui_button_1, ui_label_2, ui_dropdown_1

Methods
def __init__(self: Any) -> Any
No documentation
def generate_id(self: Any, element_type: str) -> str
Generate a unique ID for a UI element.

Args:
   element_type (str): Type of the UI element (e.g., 'button', 'label')
   
Returns:
   str: Unique ID in format "ui_{element_type}_{counter}"

class UIState

Description

Enumeration of possible UI element states.

Attributes
NORMAL: Any = 0
HOVERED: Any = 1
PRESSED: Any = 2
DISABLED: Any = 3
Methods

No methods defined.

class ElementStyle

Description

Class representing style properties for UI elements.

Attributes
normal_color: Optional[Tuple[int, int, int]] = None
hovered_color: Optional[Tuple[int, int, int]] = None
pressed_color: Optional[Tuple[int, int, int]] = None
disabled_color: Optional[Tuple[int, int, int]] = None
text_color: Optional[Tuple[int, int, int]] = None
border_color: Optional[Tuple[int, int, int]] = None
alpha: float = 1.0
border_radius: int = 0
border_width: int = 0
blur: int = 0
Methods
def loadFromTheme(theme_type: ThemeType) -> 'ElementStyle'
Load complete element style from the theme system.
Uses button_* properties as base, but can be overridden for specific elements.

class FontManager

Description

Manages fonts and ensures Pygame font system is initialized.
Supports both system fonts (by name) and file fonts (by path).
Caches fonts with style variants (bold/italic).
Also supports resolving font names via the engine's Atlas resource catalog,
and loading fonts from a bundled resource (using io.BytesIO).

Usage:
   # Basic usage
   font = FontManager.get_font("Arial", 24)
   
   # Using an atlas-registered font
   FontManager.set_atlas(engine.atlas)
   font = FontManager.get_font("main_font", 24)  # looks up "main_font" in atlas
   
   # File font
   font = FontManager.get_font("path/to/font.ttf", 16)

Attributes
_initialized: Any = False
_font_cache: Dict[Tuple[str, int, bool, bool], pygame.font.Font] = {}
_atlas: Any = None
Methods
def initialize(cls: Any) -> Any
Initialize the font system (call once before using fonts).
def set_atlas(cls: Any, atlas: Any) -> Any
Set the Atlas instance to use for font resolution.

Args:
   atlas: An Atlas instance (from ..misc.atlas) or None to disable.
def get_font(cls: Any, font_name: Optional[str] = None, font_size: int = 24, bold: bool = False, italic: bool = False) -> pygame.font.Font
Get a font object with the given name, size, and style.

Resolution order:
1. Check internal cache.
2. If atlas is set and the name exists in atlas:
  a. If atlas is in bundle mode and has bytes for this font, load from bytes.
  b. Otherwise load from file path.
3. If `font_name` exists as a file path, load it directly.
4. Otherwise, treat as a system font name (or None for default).

Args:
   font_name (Optional[str]):
       - If None, uses the default system font.
       - If it's a path to a font file (e.g., "path/to/font.ttf"), loads it.
       - Otherwise, treats it as a system font name (e.g., "Arial", "Times New Roman").
   font_size (int): Size of the font in pixels.
   bold (bool): Whether the font should be bold.
   italic (bool): Whether the font should be italic.
   
Returns:
   pygame.font.Font: A font object ready for text rendering.
def get_system_fonts(cls: Any) -> List[str]
Return a list of available system font names.

class UIElement

Description

Base class for all UI elements providing common functionality.

Attributes:
   element_id (str): Unique identifier for this element in format ui_{type}_{counter}
   x (int): X coordinate position
   y (int): Y coordinate position
   width (int): Width of the element in pixels
   height (int): Height of the element in pixels
   pivot (Tuple[float, float]): Anchor point for positioning
   state (UIState): Current state of the element
   visible (bool): Whether element is visible
   enabled (bool): Whether element is enabled
   children (List[UIElement]): Child elements
   parent (UIElement): Parent element
   can_focus (bool): Whether this element can receive controller focus (override in subclasses)

Attributes
_global_engine: 'LunaEngine' = None
_properties: Dict[str, Dict[str, Any]] = {'x': {'name': 'x position', 'key': 'x', 'type': int, 'editable': True, 'description': 'X coordinate position of the element', 'range': (0, '')}, 'y': {'name': 'y position', 'key': 'y', 'type': int, 'editable': True, 'description': 'Y coordinate position of the element', 'range': (0, '')}, 'width': {'name': 'width', 'key': 'width', 'type': int, 'editable': True, 'description': 'Width of the element in pixels', 'range': (0, '')}, 'height': {'name': 'height', 'key': 'height', 'type': int, 'editable': True, 'description': 'Height of the element in pixels', 'range': (0, '')}, 'style': {'name': 'style', 'key': 'style', 'type': ElementStyle, 'editable': False, 'description': 'Style properties for the element'}, 'visible': {'name': 'visible', 'key': 'visible', 'type': bool, 'editable': True, 'description': 'Whether the element is visible'}, 'enabled': {'name': 'enabled', 'key': 'enabled', 'type': bool, 'editable': True, 'description': 'Whether the element is enabled for interaction'}, 'pivot': {'name': 'root point', 'key': 'pivot', 'type': Tuple[float, float], 'editable': True, 'description': 'Anchor point for positioning where (0,0) is top-left and (1,1) is bottom-right'}, 'selection_order': {'name': 'selection order', 'key': 'selection_order', 'type': int, 'editable': True, 'description': 'Order for controller focus navigation (lower = earlier)'}}
category: str = 'None'
Methods
def __init__(self: Any, x: int, y: int, width: int, height: int, pivot: Tuple[float, float] = (0, 0), element_id: Optional[str] = None) -> Any
Initialize a UI element with position and dimensions.

Args:
   x (int): X coordinate position.
   y (int): Y coordinate position.
   width (int): Width of the element in pixels.
   height (int): Height of the element in pixels.
   pivot (Tuple[float, float]): Anchor point for positioning where (0,0) is top-left
                                   and (1,1) is bottom-right.
   element_id (Optional[str]): Custom element ID. If None, generates automatic ID.
def can_focus(self: Any) -> bool
Override in subclasses that should be focusable (buttons, inputs, etc.).
def is_globally_visible(self: Any) -> bool
Return True if this element and all its ancestors are visible.
This is used to skip elements inside hidden containers (e.g., inactive tabs).
def add_to_parent(self: Any, parent: 'UIElement', extra: Any = None) -> None
Add this element to a parent, optionally with extra data (e.g., tab name).
def getIndexedChilds(self: Any) -> int
Return the total number of descendants (including nested).
def set_enabled(self: Any, enabled: bool) -> Any
Enable or disable this element.
def get_engine(self: Any) -> 'LunaEngine'
Return the global engine instance.
def set_corner_radius(self: Any, radius: int | Tuple[int, int, int, int]) -> Any
Set the corner radius (can be a single int or tuple for each corner).
def group(self: Any) -> str
No documentation
def add_group(self: Any, group: str) -> Any
Add this element to a named group.
def remove_group(self: Any, group: str) -> Any
Remove this element from a named group.
def clear_groups(self: Any) -> Any
Remove all group associations.
def has_group(self: Any, group: str) -> bool
Check if this element belongs to a given group.
def __str__(self: Any) -> str
No documentation
def __repr__(self: Any) -> str
No documentation
def type(self: Any) -> str
No documentation
def get_id(self: Any) -> str
Get the unique ID of this UI element.
def set_id(self: Any, new_id: str) -> None
Set a new unique ID for this UI element.
def get_scroll_offset(self: Any, is_for_scroll_event: Optional[bool] = False) -> Union[Tuple[int, int], Tuple[float, float]]
Get the accumulated scroll offset from this element and its ancestors.
If is_for_scroll_event is True, scroll offsets are not applied (for hit testing).
def get_actual_position(self: Any, x: Union[int, float, None] = None, y: Union[int, float, None] = None) -> Union[Tuple[int, int], Tuple[float, float]]
Get the absolute screen position of this element, considering parent and pivot.
def mouse_over(self: Any, input_state: InputState | Tuple[int, int], rect: pygame.Rect | pygame.Rect | None = None, is_for_scroll_event: Optional[bool] = False) -> bool
Check if the mouse is over this element.

Args:
   input_state: The current input state or mouse position tuple.
   rect: Optional custom rectangle to use for collision (otherwise uses the element's rect).
   is_for_scroll_event: If True, ignores scroll offsets.
def get_mouse_position(self: Any, input_state: InputState | Tuple[int, int]) -> Tuple[int, int]
Get the raw mouse position from the input state or tuple.
def getCollideRect(self: Any, rect: pygame.Rect | None = None, is_for_scroll_event: Optional[bool] = False) -> pygame.Rect
Get the collision rectangle for this element, considering parent and scroll offsets.
def add_child(self: Any, child: Any) -> Any
Add a child element to this UI element.
def remove_child(self: Any, child: 'UIElement') -> Any
Remove a child element from this UI element.
def set_tooltip(self: Any, tooltip: 'Tooltip') -> Any
Set tooltip for this element using a Tooltip instance.

Args:
   tooltip (Tooltip): Tooltip instance to associate with this element
def set_simple_tooltip(self: Any, text: str, **kwargs: dict) -> Any
Quick method to set a simple tooltip with text.

Args:
   text (str): Tooltip text
   **kwargs: Additional arguments for TooltipConfig
def remove_tooltip(self: Any) -> Any
Remove tooltip from this element.
def update(self: Any, dt: float, inputState: InputState) -> Any
Update element state based on mouse interaction and input.

Args:
   dt (float): Delta time in seconds since last update.
   inputState (InputState): Current input state.
def update_theme(self: Any, theme_type: ThemeType) -> Any
Update the theme for this element and all its children.

Args:
   theme_type (ThemeType): The new theme to apply.
def render(self: Any, renderer: Renderer | OpenGLRenderer) -> Any
Render this element using OpenGL backend.  
Override this in subclasses for OpenGL-specific rendering.
def kill(self: Any) -> Any
Remove this element from the scene.
def _get_init_args(self: Any) -> Dict[str, Any]
Return a dictionary of arguments to pass to the constructor when restarting.
Subclasses can override this to include additional parameters (e.g., options, label, etc.).
def restart(self: Any) -> Optional['UIElement']
Recreate the element with its current properties and replace it in the scene.
This is useful after modifying properties that require a full rebuild
(e.g., changing max_visible_options in a Dropdown).

Returns:
   The newly created element, or None if the operation failed.
def on_click(self: Any) -> Any
Called when element is clicked by the user.
def on_hover(self: Any) -> Any
Called when mouse hovers over the element.
def on_activate(self: Any) -> None
Called when the element is "activated" (e.g., via controller A button or Enter key).
Override in subclasses to perform actions like toggling, selecting, etc.
def on_directional_input(self: Any, direction: str) -> bool
Handle directional input (up/down/left/right) when this element is focused.
Return True if the input was consumed (so focus won't move to another element).
Back to Ui Module