Skip to content

Style, Colors and UI

FluxRender.ui.Button

Button(text: str, on_click: Callable, x_pos: int = 0, y_pos: int = 50, width: int = 150, height: int = 50, align: Align = Align.LEFT_TOP, style: UIStyle = None)

Bases: UIWidget

Represents an interactive button widget in the UI system.

This class extends UIWidget to provide a clickable element with a text label. It manages its own visual state transitions (idle, hover, active) and efficiently updates GPU-accessible color fields (ti.field) for rendering.

Key Features:

  • Dynamic Styling: Automatically updates text and background colors based on mouse interaction.
  • Smart Callbacks: Inspects the provided on_click handler to optionally pass the button instance as an argument.
  • Text Rendering: Handles text texture baking for the button label.
  • Alignment: Supports various anchor points (e.g., Center, Top-Left) for flexible positioning.

Initializes a new Button widget with interactive behavior and dynamic styling.

The button handles mouse interactions (hover, click) and renders text.

Parameters:

Name Type Description Default
text str

The label text displayed on the button.

required
on_click callable

The function to execute when the button is clicked. This function can accept 0 arguments or 1 argument (the Button instance).

required
x_pos int

The horizontal position coordinate in pixels. Defaults to 0.

0
y_pos int

The vertical position coordinate in pixels. Defaults to 50.

50
width int

The width of the button in pixels. Defaults to 150.

150
height int

The height of the button in pixels. Defaults to 50.

50
align Align

The alignment anchor point relative to the (x_pos, y_pos) coordinates (e.g., LEFT_TOP, CENTER). Defaults to Align.LEFT_TOP.

LEFT_TOP
style UIStyle

A styling object defining colors and fonts. If None, default styling is used.

None
Example

Creating a button that changes the color_property of a VectorField when clicked:

import FluxRender as fr

# [Initializing the scene and coordinate system]

# Create a vector field
vector_field = fr.VectorField(vec_function=lambda x, y: (y, -x))

# Define function to toggle vector field color
def toggle_color():
    if vector_field.color_property == fr.Property.VELOCITY:
        vector_field.color_property = fr.Property.DIVERGENCE
    else:
        vector_field.color_property = fr.Property.VELOCITY

# Create a button with the toggle function
button = fr.Button(
    text = "Change Property",
    on_click = toggle_color,
    style = fr.UIStyle(
        font_size=15
    )
)

scene.add(vector_field, button)

Source code in FluxRender/ui.py
def __init__(self,
             text: str,
             on_click: Callable,
             x_pos: int = 0,
             y_pos: int = 50,
             width: int = 150,
             height: int = 50,
             align: Align = Align.LEFT_TOP,
             style: UIStyle = None
             ):
    """
    Initializes a new Button widget with interactive behavior and dynamic styling.

    The button handles mouse interactions (hover, click) and renders text.

    Args:
        text (str): The label text displayed on the button.
        on_click (callable): The function to execute when the button is clicked.
            This function can accept 0 arguments or 1 argument (the Button instance).
        x_pos (int, optional): The horizontal position coordinate in pixels. Defaults to 0.
        y_pos (int, optional): The vertical position coordinate in pixels. Defaults to 50.
        width (int, optional): The width of the button in pixels. Defaults to 150.
        height (int, optional): The height of the button in pixels. Defaults to 50.
        align (Align, optional): The alignment anchor point relative to the (x_pos, y_pos) coordinates
            (e.g., LEFT_TOP, CENTER). Defaults to Align.LEFT_TOP.
        style (UIStyle, optional): A styling object defining colors and fonts.
            If None, default styling is used.

    Example:
        Creating a button that changes the color_property of a VectorField when clicked:
        ```python
        import FluxRender as fr

        # [Initializing the scene and coordinate system]

        # Create a vector field
        vector_field = fr.VectorField(vec_function=lambda x, y: (y, -x))

        # Define function to toggle vector field color
        def toggle_color():
            if vector_field.color_property == fr.Property.VELOCITY:
                vector_field.color_property = fr.Property.DIVERGENCE
            else:
                vector_field.color_property = fr.Property.VELOCITY

        # Create a button with the toggle function
        button = fr.Button(
            text = "Change Property",
            on_click = toggle_color,
            style = fr.UIStyle(
                font_size=15
            )
        )

        scene.add(vector_field, button)
        ```
    """

    self._is_inicialized = False
    self._is_inicialized_shape = False

    super().__init__(x_pos, y_pos, width, height, align, style)

    self._current_background_color = ti.Vector.field(4, dtype=float, shape=())
    self._current_text_color = ti.Vector.field(4, dtype=float, shape=())

    self.is_active = False

    self.text = text
    self.on_click = on_click

    self._is_dirty = True
    self._last_applied_bg = None
    self._last_applied_txt = None
    self._was_lmb_down = False

    self.scene = None

    # checking the number of parameters
    self._params = _count_function_parameters(on_click)
    if self._params > 1 and self._params != float('inf'):
        _fatal_error(f"on_click function must have 0, 1, or unlimited parameters (*args or **kwargs). Got {self._params}.", "ValueError")

    self.is_active = False
    self._is_inicialized = True
    self._bake_text_texture()

is_hovered

is_hovered(scene: Scene) -> bool

A method to check if the cursor position matches the button area

Parameters:

Name Type Description Default
scene Scene

Scene object

required

Returns:

Name Type Description
bool bool

True if the mouse is hovering over the button, False otherwise

Source code in FluxRender/ui.py
def is_hovered(self, scene: cr.Scene) -> bool:
    """
    A method to check if the cursor position matches the button area

    Args:
        scene (Scene): Scene object

    Returns:
        bool: True if the mouse is hovering over the button, False otherwise
    """
    mx, my = scene.mouse_pos

    if mx >= self.min_x and mx <= self.max_x and my >= self.min_y and my <= self.max_y: return True
    return False

FluxRender.ui.Axis

Axis(color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), thickness: float = 2.5, label_size: int = 14, label_color: Sequence[float] = (1, 1, 1, 1), label_density: int = 10, label_offset_x: Sequence[float] = (0, -4), label_offset_y: Sequence[float] = (-4, 0), label_offset_0: Sequence[float] = (-4, -4), cover_background: bool = False, antyaliasing: bool = True, draw_arrows: bool = True, arrow_size: float = 25, arrow_style: ArrowStyle = ArrowStyle.HARPOON, arrow_color: Sequence[float] = (0.8, 0.8, 0.8, 1.0))

Bases: Renderable

Renders 2D coordinate axes with dynamic, GPU-accelerated labels.

This class handles the drawing of the main X and Y axes lines using SDF (Signed Distance Fields) for anti-aliasing, and manages the batch rendering of textual labels via a texture atlas. It supports dynamic level-of-detail (LOD) for label density and customizable positioning.

Parameters:

Name Type Description Default
color Sequence[float]

The RGBA color of the axis lines (0.0 to 1.0).

(1.0, 1.0, 1.0, 1.0)
thickness float

The thickness of the axis lines in pixels.

2.5
label_size int

The base font size used for label layout calculations.

14
label_color Sequence[float]

The RGBA color tint applied to the text labels.

(1, 1, 1, 1)
label_density int

A divisor for the screen dimension to determine target step size. Higher values result in more frequent labels (tighter spacing), while lower values result in fewer labels. Similar to the density logic in Grid.

10
label_offset_x Sequence[float]

A tuple (dx, dy) in pixels indicating the rendering offset for labels along the X-axis. Used to center or position text relative to the tick mark.

(0, -4)
label_offset_y Sequence[float]

A tuple (dx, dy) in pixels indicating the rendering offset for labels along the Y-axis.

(-4, 0)
label_offset_0 Sequence[float]

A tuple (dx, dy) in pixels specifically for the origin (0, 0) label, usually positioned in a quadrant to avoid overlapping both axes.

(-4, -4)
cover_background bool

If True, disables alpha blending for the text pixels. Instead of mixing with the background, the text color strictly overwrites the destination pixels. This is useful for making labels "erase" or cover underlying grid lines to improve legibility.

False
antyaliasing bool

If True, smooths the edges of the axis lines (default True)

True
draw_arrows bool

If True, draws arrows at the ends of the axes (default True)

True
arrow_size float

The size of the arrows in pixels (default 10.0)

25
arrow_style ArrowStyle

The style of the arrows (default ArrowStyle.HARPOON)

HARPOON
arrow_color Sequence[float]

The RGBA color of the arrows (0.0 to 1.0).

(0.8, 0.8, 0.8, 1.0)
Example

Creating a yellow coordinate axis with large labels that cover the elements behind them (such as grid lines):

import FluxRender as fr

# [Initializing the scene and coordinate system]

axis = fr.Axis(
    color = (1, 0.8, 0, 1),  # Yellow axes
    label_size = 18,    # Larger font size for labels
    label_color = (1, 0.8, 0, 1),  # Yellow labels
    cover_background = True,  # Make labels cover elements behind them
    arrow_color = (1, 0.8, 0, 1)  # Yellow arrows
)
scene.add(axis)

Source code in FluxRender/ui.py
def __init__(self,
             color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
             thickness: float = 2.5,
             label_size: int = 14,
             label_color: Sequence[float] = (1, 1, 1, 1),
             label_density: int = 10,
             label_offset_x: Sequence[float] = (0, -4),
             label_offset_y: Sequence[float] = (-4, 0),
             label_offset_0: Sequence[float] = (-4, -4),
             cover_background: bool = False,
             antyaliasing: bool = True,
             draw_arrows: bool = True,
             arrow_size: float = 25,
             arrow_style: ArrowStyle = ArrowStyle.HARPOON,
             arrow_color: Sequence[float] = (0.8, 0.8, 0.8, 1.0)
             ):

    """
    Args:
        color (Sequence[float]): The RGBA color of the axis lines (0.0 to 1.0).
        thickness (float): The thickness of the axis lines in pixels.
        label_size (int): The base font size used for label layout calculations.
        label_color (Sequence[float]): The RGBA color tint applied to the text labels.
        label_density (int): A divisor for the screen dimension to determine target step size.
            Higher values result in more frequent labels (tighter spacing), while lower values
            result in fewer labels. Similar to the density logic in Grid.
        label_offset_x (Sequence[float]): A tuple (dx, dy) in pixels indicating the rendering offset
            for labels along the X-axis. Used to center or position text relative to the tick mark.
        label_offset_y (Sequence[float]): A tuple (dx, dy) in pixels indicating the rendering offset
            for labels along the Y-axis.
        label_offset_0 (Sequence[float]): A tuple (dx, dy) in pixels specifically for the origin (0, 0) label,
            usually positioned in a quadrant to avoid overlapping both axes.
        cover_background (bool): If True, disables alpha blending for the text pixels.
            Instead of mixing with the background, the text color strictly overwrites
            the destination pixels. This is useful for making labels "erase" or cover
            underlying grid lines to improve legibility.
        antyaliasing (bool): If True, smooths the edges of the axis lines (default True)
        draw_arrows (bool): If True, draws arrows at the ends of the axes (default True)
        arrow_size (float): The size of the arrows in pixels (default 10.0)
        arrow_style (ArrowStyle): The style of the arrows (default ArrowStyle.HARPOON)
        arrow_color (Sequence[float]): The RGBA color of the arrows (0.0 to 1.0).

    Example:
        Creating a yellow coordinate axis with large labels that cover the elements behind them (such as grid lines):
        ```python
        import FluxRender as fr

        # [Initializing the scene and coordinate system]

        axis = fr.Axis(
            color = (1, 0.8, 0, 1),  # Yellow axes
            label_size = 18,    # Larger font size for labels
            label_color = (1, 0.8, 0, 1),  # Yellow labels
            cover_background = True,  # Make labels cover elements behind them
            arrow_color = (1, 0.8, 0, 1)  # Yellow arrows
        )
        scene.add(axis)
        ```
    """


    self.color_gpu = ti.Vector.field(4, dtype=float, shape=())
    self.label_color_gpu = ti.Vector.field(4, dtype=float, shape=())

    self.font = FontAtlas(label_size)

    self.color = color
    self.label_color = label_color
    self.thickness = thickness
    self.label_density = label_density

    self.label_offset_x = label_offset_x
    self.label_offset_y = label_offset_y
    self.label_offset_0 = label_offset_0

    self.draw_arrows = draw_arrows
    self.arrow_size = arrow_size
    self.arrow_style = arrow_style
    self.arrow_color = arrow_color

    self.antyaliasing = antyaliasing
    self.cover_background = cover_background

    self.coords = None

    self._max_chars = 400
    self._char_positions = ti.Vector.field(2, dtype=float, shape=self._max_chars)
    self._char_indices = ti.field(dtype=int, shape=self._max_chars)
    self._chars_count = ti.field(dtype=int, shape=())
    self._char_sizes = ti.Vector.field(2, dtype=float, shape=self._max_chars)

    self._np_indices = np.zeros(self._max_chars, dtype=np.int32)
    self._np_positions = np.zeros((self._max_chars, 2), dtype=np.float32)
    self._np_sizes = np.zeros((self._max_chars, 2), dtype=np.float32)

    self._last_cam_state = None

    self.arrow_atlas = ArrowAtlas(style=arrow_style)

FluxRender.ui.Grid

Grid(color=(0.6, 0.6, 0.6, 1), thickness=1, density=10, antyaliasing: bool = True)

Bases: Renderable

Creates a visible grid on the coordinate system that adjusts depending on zoom. Supports custom color, thickness, density, and anti-aliasing.

Parameters:

Name Type Description Default
color tuple / list

The RGBA color of the grid lines. Defaults to (0.6, 0.6, 0.6, 1).

(0.6, 0.6, 0.6, 1)
thickness float

The thickness of the grid lines in pixels. Defaults to 1.

1
density int

The target number of grid lines across the visible range (higher means more lines). Defaults to 10.

10
antyaliasing bool

Whether to apply anti-aliasing. Defaults to True.

True
Example

Creating a double grid with different colors and densities:

import FluxRender as fr

# [Initializing the scene and coordinate system]

# Create a main grid with lower density
main_grid = fr.Grid()

# Create a secondary, less prominent grid with higher density
secondary_grid = fr.Grid(
    color=(0.6, 0.6, 0.6, 0.5), # More transparent
    density=50
)

scene.add(main_grid, secondary_grid)

Source code in FluxRender/ui.py
def __init__(self, color=(0.6, 0.6, 0.6, 1), thickness=1, density = 10, antyaliasing: bool = True):
    """
    Args:
        color (tuple/list, optional): The RGBA color of the grid lines. Defaults to (0.6, 0.6, 0.6, 1).
        thickness (float, optional): The thickness of the grid lines in pixels. Defaults to 1.
        density (int, optional): The target number of grid lines across the visible range (higher means more lines). Defaults to 10.
        antyaliasing (bool, optional): Whether to apply anti-aliasing. Defaults to True.

    Example:
        Creating a double grid with different colors and densities:
        ```python
        import FluxRender as fr

        # [Initializing the scene and coordinate system]

        # Create a main grid with lower density
        main_grid = fr.Grid()

        # Create a secondary, less prominent grid with higher density
        secondary_grid = fr.Grid(
            color=(0.6, 0.6, 0.6, 0.5), # More transparent
            density=50
        )

        scene.add(main_grid, secondary_grid)
        ```
    """

    self.coords = None

    self.color = color
    self.thickness = thickness
    self.density = density
    self.antyaliasing = antyaliasing

FluxRender.ui.UIStyle dataclass

UIStyle(background_color: Optional[Sequence[float]] = None, hover_background_color: Optional[Sequence[float]] = None, active_background_color: Optional[Sequence[float]] = None, text_color: Optional[Sequence[float]] = None, hover_text_color: Optional[Sequence[float]] = None, text_stroke: Optional[int] = None, text_stroke_color: Optional[Sequence[float]] = None, active_text_color: Optional[Sequence[float]] = None, border_radius: Optional[int] = None, font_size: Optional[int] = None, padding: Optional[Tuple[int, int]] = None, display: Optional[bool] = None, visible: Optional[bool] = None)

A highly flexible, CSS-like styling configuration for UI elements.

Unlike rigid styling properties, UIStyle utilizes a cascading resolution system. By default, all attributes are initialized to None. This allows elements to intelligently inherit properties from their parent containers or fall back to their class-specific and global default themes.

The Cascading Resolution Order

When a UI element needs to render, it resolves its style properties in this exact order: 1. Instance Override: Is it explicitly set in this specific UIStyle instance? 2. Parent Inheritance: If the property is inheritable, does the parent container have it set? 3. Class Default: What is the natural default for this element type (e.g., Button vs. Container)? 4. Global Theme: The ultimate fallback theme defined by the engine.

Inheritable vs. Non-Inheritable Properties
  • Inheritable Properties: Typography and deep layout states (e.g., text_color, display). If you set these on a container, all child elements inside will inherit them.
  • Non-Inheritable Properties: Physical bounds and surface appearances (e.g., background_color, padding, visible). Setting a red background on a container will NOT make its inner buttons red.

Parameters:

Name Type Description Default
background_color Optional[Sequence[float]]

The background color of the widget in RGBA format.

None
hover_background_color Optional[Sequence[float]]

The background color when the mouse hovers over the widget.

None
active_background_color Optional[Sequence[float]]

The background color when the widget is active (e.g., clicked).

None
text_color Optional[Sequence[float]]

(Inheritable) The color of the text in RGBA format.

None
hover_text_color Optional[Sequence[float]]

(Inheritable) The color of the text when the mouse hovers.

None
text_stroke Optional[int]

(Inheritable) The width of the stroke around the text.

None
text_stroke_color Optional[Sequence[float]]

(Inheritable) The color of the stroke around the text.

None
active_text_color Optional[Sequence[float]]

(Inheritable) The color of the text when the widget is active.

None
border_radius Optional[int]

Corner rounding radius.

None
font_size Optional[int]

(Inheritable) The size of the font used for the widget's text.

None
padding Optional[Tuple[int, int]]

Inner spacing (x, y) between the widget's border and its internal content.

None
display Optional[bool]

(Inheritable) Toggles rendering for both the element AND all of its children.

None
visible Optional[bool]

Toggles rendering for the element's surface only. Children remain drawn.

None
Example

Creating a button with custom styling:

import FluxRender as fr

# [Initializing the scene and coordinate system]

# Create a container style with a warning aesthetic.
# Background is explicitly red, and text is explicitly yellow.
warning_panel_style = fr.UIStyle(
    background_color = (1.0, 0.0, 0.0, 0.5),
    text_color = (1.0, 1.0, 0.0, 1.0)
)

warning_container = fr.VBox(x_pos=100, y_pos=100, style=warning_panel_style)

# Define function to handle button click
def acknowledge_warning():
    print("Warning acknowledged!")


# Create a button WITHOUT passing any specific style.
# It will use its default button background, but intelligently inherit the yellow text color from the warning container.
action_button = fr.Button(
    text = "Understood",
    on_click = acknowledge_warning
)

warning_container.add(action_button)
scene.add(warning_container)

FluxRender.ui.VBox

VBox(x_pos: int, y_pos: int, spacing: int = 15, align: Align = Align.LEFT_TOP, common_width: int = None, common_height: int = None, style: UIStyle = None)

Bases: Container

A vertical layout manager that automatically stacks its children from top to bottom.

The VBox utilizes a deferred layout resolution system. It does not calculate positions immediately upon adding elements. Instead, it waits until the entire UI tree is constructed, and then recursively computes bounding boxes (bottom-up) and element coordinates (top-down). This ensures pixel-perfect alignment regardless of the order in which nested containers and widgets are added.

Parameters:

Name Type Description Default
x_pos int

The starting X coordinate (anchor point) of the container.

required
y_pos int

The starting Y coordinate (anchor point) of the container.

required
spacing int

The number of pixels inserted vertically between each child element.

15
align Align

The alignment method that objects in the container will inherit.

LEFT_TOP
common_width int

Forces a uniform width for all DIRECT children (e.g., Buttons) added to this specific container. Does not affect deeply nested elements.

None
common_height int

Forces a uniform height for all DIRECT children added to this specific container. Does not affect deeply nested elements.

None
style UIStyle

The UIStyle object dictating the container's appearance (e.g., background, padding). Inheritable properties provided here will cascade to all children.

None
Example

Creating a vertical set of three buttons:

import FluxRender as fr

# [Inicialize the scene and coordinate system]

# Define the container for the buttons
vertical_container = fr.VBox(30, 400, common_height=40, common_width=290)

# Create a function called by buttons
def print_name(button):
    print(f"Button pressed: {button.text}")

# Create buttons
button1 = fr.Button("Orange", print_name)
button2 = fr.Button("Blue", print_name)
button3 = fr.Button("Green", print_name)

# Add buttons to the container
vertical_container.add(button1, button2, button3)

# Add the container to the scene
scene.add(vertical_container)

Source code in FluxRender/ui.py
def __init__(self,
             x_pos: int, y_pos: int,
             spacing: int = 15,
             align: Align = Align.LEFT_TOP,
             common_width: int = None,
             common_height: int = None,
             style: UIStyle = None
             ):
    """
    Args:
        x_pos: The starting X coordinate (anchor point) of the container.
        y_pos: The starting Y coordinate (anchor point) of the container.
        spacing: The number of pixels inserted vertically between each child element.
        align: The alignment method that objects in the container will inherit.
        common_width: Forces a uniform width for all DIRECT children (e.g., Buttons)
            added to this specific container. Does not affect deeply nested elements.
        common_height: Forces a uniform height for all DIRECT children added to this
            specific container. Does not affect deeply nested elements.
        style: The UIStyle object dictating the container's appearance (e.g., background,
            padding). Inheritable properties provided here will cascade to all children.

    Example:
        Creating a vertical set of three buttons:
        ```python
        import FluxRender as fr

        # [Inicialize the scene and coordinate system]

        # Define the container for the buttons
        vertical_container = fr.VBox(30, 400, common_height=40, common_width=290)

        # Create a function called by buttons
        def print_name(button):
            print(f"Button pressed: {button.text}")

        # Create buttons
        button1 = fr.Button("Orange", print_name)
        button2 = fr.Button("Blue", print_name)
        button3 = fr.Button("Green", print_name)

        # Add buttons to the container
        vertical_container.add(button1, button2, button3)

        # Add the container to the scene
        scene.add(vertical_container)
        ```
    """

    super().__init__(x_pos, y_pos, spacing, align, common_width, common_height, style)

    self._current_y = y_pos

FluxRender.ui.HBox

HBox(x_pos: int, y_pos: int, spacing: int = 15, align: Align = Align.LEFT_TOP, common_width: int = None, common_height: int = None, style: UIStyle = None)

Bases: Container

A horizontal layout manager that automatically arranges its children from left to right.

The HBox relies on a robust deferred layout architecture. By separating the hierarchy building phase from the mathematical layout phase, it can dynamically adapt its own bounding box to wrap exactly around its content, making it highly scalable for complex, nested UI structures.

Parameters:

Name Type Description Default
x_pos int

The starting X coordinate (anchor point) of the container.

required
y_pos int

The starting Y coordinate (anchor point) of the container.

required
spacing int

The number of pixels inserted horizontally between each child element.

15
align Align

The alignment method that objects in the container will inherit.

LEFT_TOP
common_width int

Forces a uniform width for all DIRECT children (e.g., Buttons) added to this specific container. Does not affect deeply nested elements.

None
common_height int

Forces a uniform height for all DIRECT children added to this specific container. Does not affect deeply nested elements.

None
style UIStyle

The UIStyle object dictating the container's appearance (e.g., background, padding). Inheritable properties provided here will cascade to all children.

None
Example

Creating a horizontal set of three buttons:

import FluxRender as fr

# [Inicialize the scene and coordinate system]

# Define the container for the buttons
horizontal_container = fr.HBox(10, 80, common_height=40, common_width=290)

# Create a function called by buttons
def print_name(button):
    print(f"Button pressed: {button.text}")

# Create buttons
button1 = fr.Button("Orange", print_name)
button2 = fr.Button("Blue", print_name)
button3 = fr.Button("Green", print_name)

# Add buttons to the container
horizontal_container.add(button1, button2, button3)

# Add the container to the scene
scene.add(horizontal_container)

Source code in FluxRender/ui.py
def __init__(self,
             x_pos: int, y_pos: int,
             spacing: int = 15,
             align: Align = Align.LEFT_TOP,
             common_width: int = None,
             common_height: int = None,
             style: UIStyle = None
             ):
    """
    Args:
        x_pos: The starting X coordinate (anchor point) of the container.
        y_pos: The starting Y coordinate (anchor point) of the container.
        spacing: The number of pixels inserted horizontally between each child element.
        align: The alignment method that objects in the container will inherit.
        common_width: Forces a uniform width for all DIRECT children (e.g., Buttons)
            added to this specific container. Does not affect deeply nested elements.
        common_height: Forces a uniform height for all DIRECT children added to this
            specific container. Does not affect deeply nested elements.
        style: The UIStyle object dictating the container's appearance (e.g., background,
            padding). Inheritable properties provided here will cascade to all children.

    Example:
        Creating a horizontal set of three buttons:
        ```python
        import FluxRender as fr

        # [Inicialize the scene and coordinate system]

        # Define the container for the buttons
        horizontal_container = fr.HBox(10, 80, common_height=40, common_width=290)

        # Create a function called by buttons
        def print_name(button):
            print(f"Button pressed: {button.text}")

        # Create buttons
        button1 = fr.Button("Orange", print_name)
        button2 = fr.Button("Blue", print_name)
        button3 = fr.Button("Green", print_name)

        # Add buttons to the container
        horizontal_container.add(button1, button2, button3)

        # Add the container to the scene
        scene.add(horizontal_container)
        ```
    """

    super().__init__(x_pos, y_pos, spacing, align, common_width, common_height, style)

    self._current_x = x_pos

FluxRender.colors.ColorMapper

ColorMapper(min_hue: int = 240, max_hue: int = 0, min_saturation: float = 0.4, max_saturation: float = 1.0, min_lightness: float = 0.3, max_lightness: float = 0.65, min_alpha: float = 0.8, max_alpha: float = 1.0, scale_type: ScaleType = ScaleType.LINEAR, scale_function=None, min_value: float = None, max_value: float = None)

A dynamic color interpolation engine that translates scalar fields into vibrant visual gradients.

The ColorMapper acts as the visual translator for the mathematical engine. It takes raw scalar values (such as velocity magnitude, divergence, or custom topological metrics) and smoothly maps them to physical RGBA colors.

Crucially, all color interpolation is performed natively within the HSL (Hue, Saturation, Lightness) color space rather than RGB. This architectural choice guarantees perceptually uniform, vivid transitions and completely eliminates the "muddy" or desaturated mid-tones commonly seen in standard linear color blending. It features highly flexible normalization, allowing bounds to be strictly locked at initialization or dynamically injected frame-by-frame by the rendering entities (like VectorField or ParticleSystem).

Parameters:

Name Type Description Default
min_hue int

Minimum hue in degrees (0-360). Default is 240 (blue).

240
max_hue int

Maximum hue in degrees (0-360). Default is 0 (red).

0
min_saturation float

Minimum saturation (0-1). Default is 0.4.

0.4
max_saturation float

Maximum saturation (0-1). Default is 1.0.

1.0
min_lightness float

Minimum lightness (0-1). Default is 0.3.

0.3
max_lightness float

Maximum lightness (0-1). Default is 0.65.

0.65
min_alpha float

Minimum alpha (0-1). Default is 0.8.

0.8
max_alpha float

Maximum alpha (0-1). Default is 1.0.

1.0
scale_type ScaleType

The type of scaling to apply to the input values. Default is ScaleType.LINEAR.

LINEAR
scale_function Callable

A custom single-argument function f(t). Input t is normalized in [0.0, 1.0], and the output must also be within [0.0, 1.0]. Used only if scale_type is ScaleType.CUSTOM.

None
min_value float

Hardcoded minimum value for normalization. If None, the rendering entity will dynamically calculate and inject the minimum value of the current frame.

None
max_value float

Hardcoded maximum value for normalization. If None, the rendering entity will dynamically calculate and inject the maximum value.

None
Example

Creating a highly saturated thermal gradient (Blue to Red) with a custom logarithmic-like curve:

import FluxRender as fr

# [Initialize scene and coordinate system here]

# This mapper transitions from green to orange, but uses a square-root curve
mapper = fr.ColorMapper(
    min_hue=150,
    max_hue=20,
    min_saturation=0.8,
    max_saturation=1,
    min_lightness=0.1,
    max_lightness=0.6,
    min_alpha=1,
    scale_type = fr.ScaleType.CUSTOM,
    scale_function = lambda x: x ** 0.5,
)

# Apply the mapper directly to a ParticleSystem
particles = fr.ParticleSystem(
    vec_function = lambda x, y: (np.sin(x), np.cos(x) * np.sin(y)),
    color_mapper = mapper,
)

scene.add(particles)

Source code in FluxRender/colors.py
def __init__(self,
             min_hue: int = 240, max_hue: int = 0,
             min_saturation: float = 0.4, max_saturation: float = 1.0,
             min_lightness: float = 0.3, max_lightness: float = 0.65,
             min_alpha: float = 0.8, max_alpha: float = 1.0,
             scale_type: ScaleType = ScaleType.LINEAR,
             scale_function = None,
             min_value: float = None, max_value: float = None
             ):
    """
    Args:
        min_hue (int): Minimum hue in degrees (0-360). Default is 240 (blue).
        max_hue (int): Maximum hue in degrees (0-360). Default is 0 (red).
        min_saturation (float): Minimum saturation (0-1). Default is 0.4.
        max_saturation (float): Maximum saturation (0-1). Default is 1.0.
        min_lightness (float): Minimum lightness (0-1). Default is 0.3.
        max_lightness (float): Maximum lightness (0-1). Default is 0.65.
        min_alpha (float): Minimum alpha (0-1). Default is 0.8.
        max_alpha (float): Maximum alpha (0-1). Default is 1.0.
        scale_type (ScaleType): The type of scaling to apply to the input values. Default is ScaleType.LINEAR.
        scale_function (Callable, optional): A custom single-argument function f(t). Input t is normalized in [0.0, 1.0], and the output must also be within [0.0, 1.0]. Used only if `scale_type` is `ScaleType.CUSTOM`.
        min_value (float, optional): Hardcoded minimum value for normalization. If None, the rendering entity will dynamically calculate and inject the minimum value of the current frame.
        max_value (float, optional): Hardcoded maximum value for normalization. If None, the rendering entity will dynamically calculate and inject the maximum value.

    Example:
        Creating a highly saturated thermal gradient (Blue to Red) with a custom logarithmic-like curve:
        ```python
        import FluxRender as fr

        # [Initialize scene and coordinate system here]

        # This mapper transitions from green to orange, but uses a square-root curve
        mapper = fr.ColorMapper(
            min_hue=150,
            max_hue=20,
            min_saturation=0.8,
            max_saturation=1,
            min_lightness=0.1,
            max_lightness=0.6,
            min_alpha=1,
            scale_type = fr.ScaleType.CUSTOM,
            scale_function = lambda x: x ** 0.5,
        )

        # Apply the mapper directly to a ParticleSystem
        particles = fr.ParticleSystem(
            vec_function = lambda x, y: (np.sin(x), np.cos(x) * np.sin(y)),
            color_mapper = mapper,
        )

        scene.add(particles)
        ```
    """

    self.min_hue = min_hue
    self.max_hue = max_hue
    self.min_saturation = min_saturation
    self.max_saturation = max_saturation
    self.min_lightness = min_lightness
    self.max_lightness = max_lightness
    self.min_alpha = min_alpha
    self.max_alpha = max_alpha

    self.min_value = min_value
    self.max_value = max_value

    self.scale_type = scale_type
    self.scale_function = scale_function

calc_color

calc_color(value: float, min_value: float = None, max_value: float = None)

Maps a scalar value to an RGBA color using HSL color space.

Parameters:

Name Type Description Default
value float

The scalar value to map.

required
min_value float

The minimum value of the range (if self.min_value is not None, it will be used).

None
max_value float

The maximum value of the range (if self.max_value is not None, it will be used).

None

Returns:

Name Type Description
color tuple

A tuple representing the RGBA color (r, g, b, a).

Source code in FluxRender/colors.py
def calc_color(self, value: float, min_value: float = None, max_value: float = None):
    """
    Maps a scalar value to an RGBA color using HSL color space.

    Args:
        value (float): The scalar value to map.
        min_value (float): The minimum value of the range (if self.min_value is not None, it will be used).
        max_value (float): The maximum value of the range (if self.max_value is not None, it will be used).

    Returns:
        color (tuple): A tuple representing the RGBA color (r, g, b, a).
    """

    # Use instance min_value and max_value if not provided
    if self.min_value is not None and self.max_value is not None:
        min_value = self.min_value
        max_value = self.max_value

    # Normalize value to [0, 1]
    t = (value - min_value) / (max_value - min_value)
    t = np.clip(t, 0.0, 1.0)

    # Interpolate HSL components
    if self.scale_type == ScaleType.LOGARITHMIC:
        t = np.log(1 + 9 * t) / np.log(10)
    elif self.scale_type == ScaleType.EXPONENTIAL:
        t = (10 ** t - 1) / 9
    elif self.scale_type == ScaleType.CUSTOM and self.scale_function is not None:
        t = self.scale_function(t)

    hue = self.min_hue + t * (self.max_hue - self.min_hue)
    saturation = self.min_saturation + t * (self.max_saturation - self.min_saturation)
    lightness = self.min_lightness + t * (self.max_lightness - self.min_lightness)
    alpha = self.min_alpha + t * (self.max_alpha - self.min_alpha)


    return hsl_to_rgba(hue / 360.0, saturation, lightness, alpha)

map_array

map_array(values: ndarray, min_value: float = None, max_value: float = None)

Maps a NumPy array of scalar values to an array of RGBA colors.

Parameters:

Name Type Description Default
values ndarray

A NumPy array of scalar values.

required
min_value float

The minimum value of the range.

None
max_value float

The maximum value of the range.

None

Returns:

Name Type Description
colors ndarray

A NumPy array of RGBA colors corresponding to the input values.

Source code in FluxRender/colors.py
def map_array(self, values: np.ndarray, min_value: float = None, max_value: float = None):
    """
    Maps a NumPy array of scalar values to an array of RGBA colors.

    Args:
        values (np.ndarray): A NumPy array of scalar values.
        min_value (float): The minimum value of the range.
        max_value (float): The maximum value of the range.

    Returns:
        colors (np.ndarray): A NumPy array of RGBA colors corresponding to the input values.
    """

    diff = max_value - min_value
    if diff == 0: diff = 1.0 # Avoid division by zero when all values are the same

    with np.errstate(divide='ignore', over='ignore', invalid='ignore'):
        # Normalize value to [0, 1]
        t = (values - min_value) / diff
        t = np.clip(t, 0.0, 1.0)

        if self.scale_type == ScaleType.LOGARITHMIC:
            t = np.log10(1 + 9 * t)
        elif self.scale_type == ScaleType.EXPONENTIAL:
            t = (np.power(10.0, t) - 1) / 9.0
        elif self.scale_type == ScaleType.CUSTOM and self.scale_function is not None:
            try:
                # The function can take list/array inputs and return list/array outputs (vectorized)
                t = self.scale_function(t)
            except Exception:
                # The function is a regular Python function (for each point individually)
                f = np.vectorize(self.scale_function)
                t = f(t)

        hue = self.min_hue + t * (self.max_hue - self.min_hue)
        saturation = self.min_saturation + t * (self.max_saturation - self.min_saturation)
        lightness = self.min_lightness + t * (self.max_lightness - self.min_lightness)
        alpha = self.min_alpha + t * (self.max_alpha - self.min_alpha)

        r, g, b = hsl_to_rgb_vectorized(hue, saturation, lightness)

        return np.column_stack((r, g, b, alpha)).astype(np.float32)