Ui Comprehensive Demo

ui_comprehensive_demo.py
"""
ui_comprehensive_demo.py - Comprehensive UI Elements Demo for LunaEngine

ENGINE PATH:
lunaengine -> examples -> ui_comprehensive_demo.py

DESCRIPTION:
This demo showcases all available UI elements in the LunaEngine system.
All elements are organized in tabs by functional sections.
"""

import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from lunaengine.ui import *
from lunaengine.core import LunaEngine, Scene
from lunaengine.ui.tween import Tween, EasingType, AnimationHandler
from lunaengine.misc import icons  # Import the icons module
from lunaengine.backend import *


class ComprehensiveUIDemo(Scene):
    def __init__(self, engine: LunaEngine):
        super().__init__(engine)
        self.demo_state = {
            'button_clicks': 0,
            'slider_value': 50,
            'dropdown_selection': 'Option 1',
            'text_input': 'Type here...',
            'progress_value': 0,
            'switch_state': False,
            'select_index': 0,
            'dialog_active': False,
            'number_selector_value': 10,
            'checkbox_state': True,
            'text_area_content': "This is a multi-line text area.\nYou can type here...\nIt supports line numbers and word wrapping.\nTry editing this text!",
            'file_finder_path': "C:/example.txt",
            'current_page': 1,
        }
        self.animations = {}
        
        self.last_controller_count = 0
        self.controller_dropdown = None
        self.controller_info_labels = {}
        self.left_stick_indicator = None
        self.right_stick_indicator = None
        self.dpad_indicator = None
        self.button_indicators = {}  # for some key buttons
        self.selected_controller_idx = None
        
        self.setup_ui()
        
    def on_enter(self, previous_scene: str = None):
        print("=== LunaEngine UI Demo ===")
        print("Explore all UI elements organized by section!")
        print("Use the tabs to navigate between different UI element categories.")
        print("Controls:")
        print("- Click buttons to interact")
        print("- Use dropdowns and selects to choose options")
        print("- Drag the draggable element")
        print("- Type in the text box")
        print("- Change themes with the theme dropdown")
        print("- Hover over elements with tooltips")
        print("- Click to advance dialog boxes")
        print("- Use animation controls to pause/resume animations")
        
    def on_exit(self, next_scene: str = None):
        print("Exiting UI Demo scene")
        # Clean up animations
        self.animation_handler.cancel_all()
        
    def setup_ui(self):
        """Sets up all UI elements organized in tabs by section."""
        self.engine.set_global_theme(ThemeType.DEFAULT)
        
        # --- TITLE ---
        title = TextLabel(512, 30, "LunaEngine - UI Demo", 36, root_point=(0.5, 0))
        self.add_ui_element(title)
        
        # --- MAIN TAB CONTAINER ---
        self.main_tabs = Tabination(25, 90, 980, 650, 20)
        
        # --- SECTION 1: Interactive Elements ---
        self.main_tabs.add_tab('Interactive')
        self.setup_interactive_tab()
        
        # --- SECTION 2: Selection Elements ---
        self.main_tabs.add_tab('Selection')
        self.setup_selection_tab()
        
        # --- SECTION 3: Visual Elements ---
        self.main_tabs.add_tab('Visual')
        self.setup_visual_tab()
        
        # --- SECTION 4: Advanced Elements ---
        self.main_tabs.add_tab('Advanced')
        self.setup_advanced_tab()
        
        # --- SECTION 5: Animation Examples ---
        self.main_tabs.add_tab('Animation')
        self.setup_animation_tab()
        
        # --- SECTION 6: Icons Gallery ---
        self.main_tabs.add_tab('Icons')
        self.setup_icons_tab()
        
        # --- SECTION 7: Notifications ---
        self.main_tabs.add_tab('Notifications')
        self.setup_notification_tab()
        
        # --- SECTION 8: Charts ---
        self.main_tabs.add_tab('Charts')
        self.setup_charts_tab()
        
        # --- SECTION 9: Controller ---
        self.main_tabs.add_tab('Controller')
        self.setup_controller_tab()

        
        
        # Add the main tabs container to the scene
        self.add_ui_element(self.main_tabs)
        
        # --- GLOBAL ELEMENTS (outside tabs) ---
        self.dialog_box = DialogBox(120, 300, 400, 150, style="modern")
        self.dialog_box.visible = False
        self.dialog_box.set_on_advance(lambda: self.hide_dialog())
        self.dialog_box.z_index = 100
        self.add_ui_element(self.dialog_box)
        
        self.fps_display = TextLabel(self.engine.width - 5, 20, "FPS: --", 16, (100, 255, 100), root_point=(1, 0))
        self.add_ui_element(self.fps_display)
        
        # Initialize animations
        self.setup_animations()
        self.main_tabs.set_corner_radius((10, 10, 10, 10))

    def setup_icons_tab(self):
        """Sets up the icons gallery tab."""
        # Tab title
        self.main_tabs.add_to_tab('Icons', TextLabel(10, 10, "Icons Gallery", 24, (255, 255, 0)))
        
        # Description
        desc = TextLabel(10, 45, "All available icons in LunaEngine (from icons.py):", 14, (200, 200, 200))
        self.main_tabs.add_to_tab('Icons', desc)
        
        # Icon size selector
        size_label = TextLabel(10, 75, "Icon Size:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Icons', size_label)
        
        self.icon_size_selector = NumberSelector(100, 70, 80, 25, 16, 96, 32, step=8)
        self.icon_size_selector.on_value_changed = lambda v: self.update_icons_size(v)
        self.main_tabs.add_to_tab('Icons', self.icon_size_selector)
        
        size_text = TextLabel(190, 75, "pixels", 14, (150, 150, 150))
        self.main_tabs.add_to_tab('Icons', size_text)
        
        # Background color toggle
        bg_toggle = Checkbox(250, 70, 150, 25, True, "Show Background")
        bg_toggle.set_on_toggle(lambda s: self.toggle_icons_background(s))
        self.main_tabs.add_to_tab('Icons', bg_toggle)
        
        # Scrolling frame for icons
        self.icons_scroll = ScrollingFrame(10, 110, 960, 480, 950, 1500)
        self.icons_scroll.set_simple_tooltip("Scroll to see all available icons")
        self.main_tabs.add_to_tab('Icons', self.icons_scroll)
        
        # Get all icons
        self.all_icons = icons.get_all_icons(32)
        self.icon_elements = []
        
        # Add icons to the scrolling frame in a grid
        self.update_icons_display()
    
    def update_icons_display(self):
        """Updates the icons display with current size and settings."""
        # Clear existing icon elements
        for element in self.icon_elements:
            self.icons_scroll.remove_child(element)
        self.icon_elements.clear()
        
        # Get current icon size
        icon_size = self.icon_size_selector.value
        
        # Get updated icons with new size
        self.all_icons = icons.get_all_icons(icon_size)
        
        # Grid layout
        icons_per_row = 8
        spacing = 20
        icon_with_spacing = icon_size + spacing + 60  # Extra space for label
        
        x_offset = 20
        y_offset = 20
        
        # Add each icon with label
        for i, (icon_name, icon_surface) in enumerate(self.all_icons.items()):
            row = i // icons_per_row
            col = i % icons_per_row
            
            x = x_offset + col * icon_with_spacing
            y = y_offset + row * (icon_size + 40)  # Extra space for label
            
            
            frame_size = int(icon_size * 1.8)
            # Create container frame
            icon_frame = UiFrame(x, y, frame_size, frame_size)
            icon_frame.set_background_color((40, 40, 50, 150))
            icon_frame.set_border((80, 80, 100), 1)
            icon_frame.set_corner_radius(5)
            
            # Create ImageLabel element for the icon
            icon_img = ImageLabel(frame_size//2, frame_size//2 + 10, None, icon_size, icon_size, root_point=(0.5, 0.5))
            icon_img.set_image(icon_surface)
            
            # Create label for icon name
            label = TextLabel(frame_size//2, 5, 
                             icon_name, 12, (200, 200, 200), root_point=(0.5, 0))
            label.set_simple_tooltip(f"Icon: {icon_name}")
            
            # Add to scrolling frame
            icon_frame.add_child(icon_img)
            icon_frame.add_child(label)
            self.icons_scroll.add_child(icon_frame)
            
            # Store references
            self.icon_elements.extend([icon_frame, icon_img, label])
        
        # Update total count display
        count_label = TextLabel(10, 600, f"Total Icons: {len(self.all_icons)}", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Icons', count_label)
        
        # Add icon usage example
        usage_label = TextLabel(400, 600, "Usage: icons.get_icon('icon_name', size)", 14, (150, 200, 255))
        self.main_tabs.add_to_tab('Icons', usage_label)
        
        # Example buttons using icons
        example_frame = UiFrame(600, 590, 350, 60)
        example_frame.set_background_color((40, 40, 60, 180))
        example_frame.set_border((80, 100, 150), 1)
        example_frame.set_corner_radius(8)
        self.main_tabs.add_to_tab('Icons', example_frame)
        
        example_label = TextLabel(610, 595, "Icon Usage Examples:", 14, (220, 220, 255))
        self.main_tabs.add_to_tab('Icons', example_label)
        
        # Example 1: Button with icon
        btn1 = Button(610, 615, 100, 30, "Save")
        btn1_icon = ImageLabel(620, 620, None, 16, 16)
        btn1_icon.set_image(icons.get_icon('save', 16))
        self.main_tabs.add_to_tab('Icons', btn1)
        self.main_tabs.add_to_tab('Icons', btn1_icon)
        
        # Example 2: Button with icon
        btn2 = Button(720, 615, 100, 30, "Load")
        btn2_icon = ImageLabel(730, 620, None, 16, 16)
        btn2_icon.set_image(icons.get_icon('load', 16))
        self.main_tabs.add_to_tab('Icons', btn2)
        self.main_tabs.add_to_tab('Icons', btn2_icon)
        
        # Example 3: Button with icon
        btn3 = Button(830, 615, 100, 30, "Home")
        btn3_icon = ImageLabel(840, 620, None, 16, 16)
        btn3_icon.set_image(icons.get_icon('home', 16))
        self.main_tabs.add_to_tab('Icons', btn3)
        self.main_tabs.add_to_tab('Icons', btn3_icon)
    
    def update_icons_size(self, size: int):
        """Updates all icons to new size."""
        print(f"Updating icons to size: {size}")
        self.update_icons_display()
    
    def toggle_icons_background(self, show: bool):
        """Toggles the background for icon containers."""
        for element in self.icon_elements:
            if isinstance(element, UiFrame):
                if show:
                    element.set_background_color((40, 40, 50, 150))
                    element.set_border((80, 80, 100), 1)
                else:
                    element.set_background_color((0, 0, 0, 0))
                    element.set_border((0, 0, 0, 0), 0)

    def setup_notification_tab(self):
        self.main_tabs.add_to_tab('Notifications', TextLabel(10, 10, "Notifications", 24, (255, 255, 0)))
        
        self.notification_type = Dropdown(20, 50, 200, 35, ['Success', 'Info', 'Warning', 'Error'])
        self.main_tabs.add_to_tab('Notifications', self.notification_type)
        
        self.notification_position = Dropdown(235, 50, 200, 35, ['Top Left', 'Top Center', 'Top Right', 'Bottom Left', 'Bottom Center', 'Bottom Right'])
        self.main_tabs.add_to_tab('Notifications', self.notification_position)
        
        self.notification_duration = NumberSelector(20, 100, 200, 35, 1, 5, 5)
        self.main_tabs.add_to_tab('Notifications', self.notification_duration)
        
        self.show_close_button = Checkbox(20, 150, 80, 30, True, "Show Close Button")
        self.main_tabs.add_to_tab('Notifications', self.show_close_button)
        
        self.auto_close = Checkbox(220, 150, 80, 30, True, "Auto Close")
        self.main_tabs.add_to_tab('Notifications', self.auto_close)
        
        self.show_progress_bar = Checkbox(420, 150, 80, 30, True, "Show Progress Bar")
        self.main_tabs.add_to_tab('Notifications', self.show_progress_bar)
        
        self.notification_button = Button(20, 200, 200, 40, "Show Notification")
        self.notification_button.set_on_click(self.show_notification)
        self.main_tabs.add_to_tab('Notifications', self.notification_button)
        
    def setup_charts_tab(self):
        """Sets up the charts tab with various GraphicVisualizer examples."""
        # Tab title
        self.main_tabs.add_to_tab('Charts', TextLabel(10, 10, "Charts Gallery", 24, (255, 255, 0)))

        # Description
        desc = TextLabel(10, 45, "Various chart types using GraphicVisualizer:", 14, (200, 200, 200))
        self.main_tabs.add_to_tab('Charts', desc)

        # Store references to charts for randomisation
        self.charts = []

        # Bar chart
        bar_label = TextLabel(20, 75, "Bar Chart", 16, (100, 255, 100))
        self.main_tabs.add_to_tab('Charts', bar_label)
        bar_chart = ChartVisualizer(20, 100, 200, 150,
                                    data=[15, 30, 45, 25, 60, 35],
                                    labels=['Jan','Feb','Mar','Apr','May','Jun'],
                                    chart_type='bar',
                                    title='Monthly Sales',
                                    show_labels=True,
                                    show_legend=False,
                                    colors=[(54,162,235)])
        bar_chart.set_simple_tooltip("Bar chart showing monthly data")
        self.main_tabs.add_to_tab('Charts', bar_chart)
        self.charts.append(bar_chart)

        # Pie chart
        pie_label = TextLabel(250, 75, "Pie Chart", 16, (255, 200, 100))
        self.main_tabs.add_to_tab('Charts', pie_label)
        pie_chart = ChartVisualizer(250, 100, 200, 150,
                                    data=[30, 20, 25, 15, 10],
                                    labels=['A','B','C','D','E'],
                                    chart_type='pie',
                                    title='Distribution',
                                    show_labels=True,
                                    show_legend=True)
        pie_chart.set_simple_tooltip("Pie chart with legend")
        self.main_tabs.add_to_tab('Charts', pie_chart)
        self.charts.append(pie_chart)

        # Line chart
        line_label = TextLabel(20, 270, "Line Chart", 16, (100, 200, 255))
        self.main_tabs.add_to_tab('Charts', line_label)
        line_chart = ChartVisualizer(20, 295, 200, 150,
                                    data=[10, 25, 15, 30, 20, 35],
                                    labels=['Mon','Tue','Wed','Thu','Fri','Sat'],
                                    chart_type='line',
                                    title='Weekly Trend',
                                    show_labels=True,
                                    show_legend=False)
        line_chart.set_simple_tooltip("Line chart with points")
        self.main_tabs.add_to_tab('Charts', line_chart)
        self.charts.append(line_chart)

        # Scatter plot
        scatter_label = TextLabel(250, 270, "Scatter Plot", 16, (255, 100, 255))
        self.main_tabs.add_to_tab('Charts', scatter_label)
        scatter_chart = ChartVisualizer(250, 295, 200, 150,
                                        data=[5, 12, 9, 15, 7, 20],
                                        labels=['P1','P2','P3','P4','P5','P6'],
                                        chart_type='scatter',
                                        title='Data Points',
                                        show_labels=True,
                                        show_legend=False)
        scatter_chart.set_simple_tooltip("Scatter plot")
        self.main_tabs.add_to_tab('Charts', scatter_chart)
        self.charts.append(scatter_chart)

        # Additional note
        note = TextLabel(20, 470, "You can create custom charts with your own data and colors.", 14, (150, 200, 255))
        self.main_tabs.add_to_tab('Charts', note)

        # Randomize button
        randomize_btn = Button(20, 500, 150, 30, "Randomize Charts")
        randomize_btn.set_on_click(self.randomize_charts)
        randomize_btn.set_simple_tooltip("Click to randomize all chart data with smooth animation")
        self.main_tabs.add_to_tab('Charts', randomize_btn)
        
    def setup_controller_tab(self):
        """Build the UI elements for the Controller information tab."""
        tab = 'Controller'

        # Title
        self.main_tabs.add_to_tab(tab, TextLabel(10, 10, "Controller Status", 24, (255, 255, 0)))

        # Dropdown to select controller (if multiple)
        self.controller_dropdown = Dropdown(10, 50, 300, 30, ["No controller"])
        self.controller_dropdown.set_on_selection_changed(self.on_controller_selected)
        self.controller_dropdown.set_simple_tooltip("Choose which controller to display")
        self.main_tabs.add_to_tab(tab, self.controller_dropdown)

        # Info frame
        info_frame = UiFrame(10, 100, 460, 150)
        info_frame.set_background_color((30, 30, 40, 200))
        info_frame.set_border((80, 80, 100), 1)
        self.main_tabs.add_to_tab(tab, info_frame)

        # Info labels (will be updated dynamically)
        self.controller_info_labels['type'] = TextLabel(20, 10, "Type: --", 16, (200, 200, 200))
        info_frame.add_child(self.controller_info_labels['type'])
        self.controller_info_labels['connection'] = TextLabel(20, 30, "Connection: --", 16, (200, 200, 200))
        info_frame.add_child(self.controller_info_labels['connection'])
        self.controller_info_labels['touchpad'] = TextLabel(20, 50, "Touchpad: --", 16, (200, 200, 200))
        info_frame.add_child(self.controller_info_labels['touchpad'])
        self.controller_info_labels['gyro'] = TextLabel(20, 70, "Gyro: --", 16, (200, 200, 200))
        info_frame.add_child(self.controller_info_labels['gyro'])
        self.controller_info_labels['rumble'] = TextLabel(20, 90, "Rumble: --", 16, (200, 200, 200))
        info_frame.add_child(self.controller_info_labels['rumble'])

        # Right column of info
        self.controller_info_labels['axes'] = TextLabel(250, 10, "Axes: --", 16, (200, 200, 200))
        info_frame.add_child(self.controller_info_labels['axes'])
        self.controller_info_labels['buttons'] = TextLabel(250, 30, "Buttons: --", 16, (200, 200, 200))
        info_frame.add_child(self.controller_info_labels['buttons'])
        self.controller_info_labels['hats'] = TextLabel(250, 50, "Hats: --", 16, (200, 200, 200))
        info_frame.add_child(self.controller_info_labels['hats'])

        # Joystick visualizations
        joy_label = TextLabel(10, 270, "Joysticks:", 20, (200, 200, 255))
        self.main_tabs.add_to_tab(tab, joy_label)

        # Left stick
        left_frame = UiFrame(10, 310, 120, 120)
        left_frame.set_background_color((50, 50, 60))
        left_frame.set_border((100, 100, 150), 1)
        left_frame.set_corner_radius(5)
        self.main_tabs.add_to_tab(tab, left_frame)

        self.left_stick_indicator = UiFrame(60, 60, 10, 10, root_point=(0.5, 0.5))  # centered initially
        self.left_stick_indicator.set_background_color((255, 100, 100))
        self.left_stick_indicator.set_corner_radius(5)
        left_frame.add_child(self.left_stick_indicator)

        left_label = TextLabel(10, 435, "Left Stick", 14, (180, 180, 180))
        self.main_tabs.add_to_tab(tab, left_label)

        # Right stick
        right_frame = UiFrame(150, 310, 120, 120)
        right_frame.set_background_color((50, 50, 60))
        right_frame.set_border((100, 100, 150), 1)
        right_frame.set_corner_radius(5)
        self.main_tabs.add_to_tab(tab, right_frame)

        self.right_stick_indicator = UiFrame(60, 60, 10, 10, root_point=(0.5, 0.5))
        self.right_stick_indicator.set_background_color((100, 255, 100))
        self.right_stick_indicator.set_corner_radius(5)
        right_frame.add_child(self.right_stick_indicator)

        right_label = TextLabel(150, 435, "Right Stick", 14, (180, 180, 180))
        self.main_tabs.add_to_tab(tab, right_label)

        # D‑pad visual (simple cross)
        dpad_label = TextLabel(300, 270, "D-Pad:", 20, (200, 200, 255))
        self.main_tabs.add_to_tab(tab, dpad_label)

        dpad_frame = UiFrame(300, 310, 100, 100)
        dpad_frame.set_background_color((40, 40, 50))
        dpad_frame.set_border((100, 100, 150), 1)
        self.main_tabs.add_to_tab(tab, dpad_frame)

        self.dpad_indicator = UiFrame(50, 50, 10, 10, root_point=(0.5, 0.5))  # center
        self.dpad_indicator.set_background_color((200, 200, 0))
        self.dpad_indicator.set_corner_radius(5)
        dpad_frame.add_child(self.dpad_indicator)
        
        # R2, L2, LT, RT
        triggers_indicators = UiFrame(425, 310, 80, 100)
        triggers_indicators.set_background_color((40, 40, 50))
        
        self.right_trigger = ProgressBar(75, 50, 30, 90, min_val=-1, max_val=1, value=0, root_point=(1, 0.5), orientation='vertical')
        triggers_indicators.add_child(self.right_trigger)
        
        self.left_trigger = ProgressBar(5, 50, 30, 90,min_val=-1, max_val=1, value=0, root_point=(0, 0.5), orientation='vertical')
        triggers_indicators.add_child(self.left_trigger)
        self.main_tabs.add_to_tab(tab, triggers_indicators)

        # Button indicators (A, B, X, Y)
        btn_label = TextLabel(10, 470, "Buttons:", 20, (200, 200, 255))
        self.main_tabs.add_to_tab(tab, btn_label)

        btn_frame = UiFrame(10, 510, 400, 90)
        btn_frame.set_background_color((30, 30, 40, 200))
        btn_frame.set_border((80, 80, 100), 1)
        self.main_tabs.add_to_tab(tab, btn_frame)

        button_names = ['A', 'B', 'X', 'Y', 'LB', 'RB', 'Back', 'Start', 'RSC', 'LSC']
        x_pos = 10
        y_pos = 10
        for index, name in enumerate(button_names): # Frame can handle 7 per line
            # Create a small colored circle for each button
            circle = UiFrame(x_pos, y_pos, 45, 30)
            circle.set_background_color((80, 80, 80))  # default grey
            circle.set_corner_radius(20)
            btn_frame.add_child(circle)
            self.button_indicators[name] = circle

            # Label under circle
            lbl = TextLabel(22.5, 15, name, 18, (200, 200, 200), root_point=(0.5, 0.5))
            circle.add_child(lbl)

            x_pos += 55
            if index % 7 == 6:
                x_pos = 10
                y_pos += 40

        # Update the dropdown with currently connected controllers
        self.refresh_controller_dropdown()

    def refresh_controller_dropdown(self):
        """Update the dropdown list with current controllers."""
        controllers = self.engine.controller_manager.get_all_controllers()
        if controllers:
            names = [f"{c.name} (ID:{c.joystick_id})" for c in controllers]
            self.controller_dropdown.set_options(names)
            if self.selected_controller_idx is None or self.selected_controller_idx >= len(controllers):
                self.selected_controller_idx = 0
                self.controller_dropdown.selected_index = 0
        else:
            self.controller_dropdown.set_options(["No controller"])
            self.selected_controller_idx = None

    def on_controller_selected(self, index: int, value: str):
        """Handle selection of a controller from dropdown."""
        controllers = self.engine.controller_manager.get_all_controllers()
        if controllers and 0 <= index < len(controllers):
            self.selected_controller_idx = index

    def update_controller_display(self):
        """Update the UI elements with current data from the selected controller."""
        if self.selected_controller_idx is None:
            # No controller selected, set all indicators to default
            for label in self.controller_info_labels.values():
                label.set_text(label.text.split(':')[0] + ": --")
            # Reset joystick indicators to center
            self.left_stick_indicator.x = 60
            self.left_stick_indicator.y = 60
            self.right_stick_indicator.x = 60
            self.right_stick_indicator.y = 60
            self.dpad_indicator.x = 50
            self.dpad_indicator.y = 50
            for circle in self.button_indicators.values():
                circle.set_background_color((80, 80, 80))
            return

        controllers = self.engine.controller_manager.get_all_controllers()
        if not controllers or self.selected_controller_idx >= len(controllers):
            return

        ctrl = controllers[self.selected_controller_idx]

        # Update info labels
        self.controller_info_labels['type'].set_text(f"Type: {ctrl.type.name}")
        self.controller_info_labels['connection'].set_text(f"Connection: {ctrl.connection.name}")
        self.controller_info_labels['touchpad'].set_text(f"Touchpad: {'Yes' if ctrl._touch_joystick else 'No'}")
        self.controller_info_labels['gyro'].set_text(f"Gyro: {'Yes' if Axis.GYRO_X in ctrl._axis_map else 'No'}")
        self.controller_info_labels['rumble'].set_text(f"Rumble: {'Yes' if ctrl._has_rumble else 'No'}")
        self.controller_info_labels['axes'].set_text(f"Axes: {ctrl.num_axes}")
        self.controller_info_labels['buttons'].set_text(f"Buttons: {ctrl.num_buttons}")
        self.controller_info_labels['hats'].set_text(f"Hats: {ctrl.num_hats}")

        # Update joystick positions
        # Left stick: map axis values (-1..1) to frame coordinates (0..120) with offset
        lx = ctrl.get_axis(Axis.LEFT_X)
        ly = ctrl.get_axis(Axis.LEFT_Y)
        # Invert Y because screen Y increases downwards
        left_x = 60 + int(lx * 50)   # range -50..50 around center 55
        left_y = 60 + int(-ly * 50)  # negative ly moves up
        # Clamp to frame bounds
        left_x = max(5, min(115, left_x))
        left_y = max(5, min(115, left_y))
        self.left_stick_indicator.x = left_x
        self.left_stick_indicator.y = left_y

        rx = ctrl.get_axis(Axis.RIGHT_X)
        ry = ctrl.get_axis(Axis.RIGHT_Y)
        right_x = 60 + int(rx * 50)
        right_y = 60 + int(-ry * 50)
        right_x = max(5, min(115, right_x))
        right_y = max(5, min(115, right_y))
        self.right_stick_indicator.x = right_x
        self.right_stick_indicator.y = right_y

        # D‑pad
        hat_x = -1 if ctrl.get_button_pressed(JButton.DPAD_LEFT) else (1 if ctrl.get_button_pressed(JButton.DPAD_RIGHT) else 0)
        hat_y = 1 if ctrl.get_button_pressed(JButton.DPAD_UP) else (-1 if ctrl.get_button_pressed(JButton.DPAD_DOWN) else 0)
        dpad_x = 50 + int(hat_x * 40)   # max displacement 40
        dpad_y = 50 + int(-hat_y * 40)
        dpad_x = max(5, min(95, dpad_x))
        dpad_y = max(5, min(95, dpad_y))
        self.dpad_indicator.x = dpad_x
        self.dpad_indicator.y = dpad_y
        
        # (R2, L2), (LT, RT)
        self.right_trigger.set_value(ctrl.get_axis(Axis.RIGHT_TRIGGER))
        self.left_trigger.set_value(ctrl.get_axis(Axis.LEFT_TRIGGER))

        # Button indicators
        button_map = {
            'A': JButton.A,
            'B': JButton.B,
            'X': JButton.X,
            'Y': JButton.Y,
            'LB': JButton.LEFT_BUMPER,
            'RB': JButton.RIGHT_BUMPER,
            'Back': JButton.BACK,
            'Start': JButton.START,
            'RSC': JButton.RIGHT_STICK,
            'LSC': JButton.LEFT_STICK,
        }
        for name, btn in button_map.items():
            if ctrl.get_button_pressed(btn):
                self.button_indicators[name].set_background_color((0, 255, 0))  # green when pressed
            else:
                self.button_indicators[name].set_background_color((80, 80, 80))

    def randomize_charts(self):
        """Generate new random data for each chart and animate the change."""
        import random
        for chart in self.charts:
            n = len(chart.data)
            if chart.chart_type == 'pie':
                # Random values for pie
                new_data = [random.randint(5, 40) for _ in range(n)]
            else:
                new_data = [random.randint(5, 70) for _ in range(n)]

            # Animate the change over 0.8 seconds
            chart.set_data(new_data, animate=True, duration=0.8)

        print("Charts randomized with smooth transition!")
    
    def show_notification(self):
        notification_type = [NotificationType.SUCCESS, NotificationType.INFO, NotificationType.WARNING, NotificationType.ERROR][self.notification_type.selected_index]
        notification_position = [NotificationPosition.TOP_LEFT, NotificationPosition.TOP_CENTER, NotificationPosition.TOP_RIGHT, NotificationPosition.BOTTOM_LEFT, NotificationPosition.BOTTOM_CENTER, NotificationPosition.BOTTOM_RIGHT][self.notification_position.selected_index]
        self.engine.show_notification(text="Notification Example Text", notification_type=notification_type, duration=self.notification_duration.value, position=notification_position, show_close_button=self.show_close_button.value, auto_close=self.auto_close.value, show_progress_bar=self.show_progress_bar.value)
    
    def setup_interactive_tab(self):
        """Sets up interactive elements tab."""
        # Tab title
        self.main_tabs.add_to_tab('Interactive', TextLabel(10, 10, "Interactive Elements", 24, (255, 255, 0)))
        
        # Button Example
        button1 = Button(x=20, y=50, width=150, height=40, text="Click Me")
        button1.set_on_click(self.update_state, 'button_clicks', self.demo_state['button_clicks'] + 1)
        button1.set_simple_tooltip("This button counts your clicks!")
        self.main_tabs.add_to_tab('Interactive', button1)
        
        self.button_counter = TextLabel(180, 70, "Clicks: 0", 16)
        self.main_tabs.add_to_tab('Interactive', self.button_counter)
        
        # Slider Example
        slider_label = TextLabel(20, 125, "Slider:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Interactive', slider_label)
        
        slider = Slider(100, 120, 200, 30, 0, 100, 50)
        slider.on_value_changed = lambda v: self.update_state('slider_value', v)
        slider.set_simple_tooltip("Drag to change the value")
        self.main_tabs.add_to_tab('Interactive', slider)
        
        self.slider_display = TextLabel(310, 125, "Value: 50.0", 14)
        self.main_tabs.add_to_tab('Interactive', self.slider_display)
        
        # Progress Bar Example
        progress_label = TextLabel(20, 175, "Progress Bar:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Interactive', progress_label)
        
        self.progress_bar = ProgressBar(150, 170, 200, 20, 0, 100, 0)
        self.progress_bar.set_simple_tooltip("Shows progress from 0% to 100%")
        self.main_tabs.add_to_tab('Interactive', self.progress_bar)
        
        progress_btn = Button(360, 170, 100, 20, "Add 10%")
        progress_btn.set_on_click(lambda: self.add_progress(10))
        self.main_tabs.add_to_tab('Interactive', progress_btn)
        
        self.progress_display = TextLabel(470, 175, "Progress: 0%", 14)
        self.main_tabs.add_to_tab('Interactive', self.progress_display)
        
        # Draggable Element
        draggable_label = TextLabel(20, 220, "Draggable Element:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Interactive', draggable_label)
        
        draggable = UIDraggable(160, 220, 100, 50)
        draggable.set_simple_tooltip("Drag me around within the tab!")
        self.main_tabs.add_to_tab('Interactive', draggable)
    
    def setup_selection_tab(self):
        """Sets up selection elements tab."""
        # Tab title
        self.main_tabs.add_to_tab('Selection', TextLabel(10, 10, "Selection Elements", 24, (255, 255, 0)))
        
        # Dropdown Example
        dropdown_label = TextLabel(20, 50, "Dropdown:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Selection', dropdown_label)
        
        dropdown = Dropdown(120, 40, 200, 30, ["Option 1", "Option 2", "Option 3"])
        dropdown.set_on_selection_changed(lambda i, v: self.update_state('dropdown_selection', v))
        dropdown.set_simple_tooltip("Click to expand and select an option")
        self.main_tabs.add_to_tab('Selection', dropdown)
        
        self.dropdown_display = TextLabel(330, 50, "Selected: Option 1", 14)
        self.main_tabs.add_to_tab('Selection', self.dropdown_display)
        
        # Theme Dropdown
        theme_label = TextLabel(20, 100, "Theme Selector:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Selection', theme_label)
        
        theme_dropdown = Dropdown(150, 90, 150, 30, ThemeManager.get_theme_names(), font_size=16, searchable=True)
        theme_dropdown.set_on_selection_changed(lambda i, v: self.engine.set_global_theme(v))
        theme_dropdown.set_simple_tooltip("Change the global theme")
        self.main_tabs.add_to_tab('Selection', theme_dropdown)
        
        # Switch Example
        switch_label = TextLabel(20, 160, "Switch:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Selection', switch_label)
        
        switch = Switch(100, 150, 60, 30)
        switch.set_on_toggle(lambda s: self.update_state('switch_state', s))
        switch.set_simple_tooltip("Toggle switch on/off")
        self.main_tabs.add_to_tab('Selection', switch)
        
        self.switch_display = TextLabel(170, 160, "Switch: OFF", 14)
        self.main_tabs.add_to_tab('Selection', self.switch_display)
        
        # Checkbox Example
        checkbox_label = TextLabel(20, 205, "Checkbox:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Selection', checkbox_label)
        
        checkbox = Checkbox(120, 200, 200, 25, self.demo_state['checkbox_state'], label="Enable Feature X")
        checkbox.set_on_toggle(lambda s: self.update_state('checkbox_state', s))
        checkbox.set_simple_tooltip("Toggle this feature on/off")
        self.main_tabs.add_to_tab('Selection', checkbox)
        
        self.checkbox_display = TextLabel(330, 205, "Feature X: ON", 14)
        self.main_tabs.add_to_tab('Selection', self.checkbox_display)
        
        # Number Selector Example
        number_label = TextLabel(20, 255, "Number Selector:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Selection', number_label)
        
        number_selector = NumberSelector(160, 250, 75, 25, 0, 99, self.demo_state['number_selector_value'], min_length=2)
        number_selector.on_value_changed = lambda v: self.update_state('number_selector_value', v)
        number_selector.set_simple_tooltip("Select a number from 00 to 99")
        self.main_tabs.add_to_tab('Selection', number_selector)

        self.number_selector_display = TextLabel(245, 255, "Number: 10", 14)
        self.main_tabs.add_to_tab('Selection', self.number_selector_display)
        
        # Select Example
        select_label = TextLabel(20, 295, "Select (Cycle):", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Selection', select_label)
        
        select = Select(150, 290, 200, 30, ["Choice A", "Choice B", "Choice C"])
        select.set_on_selection_changed(lambda i, v: self.update_state('select_index', i))
        select.set_simple_tooltip("Use arrows to cycle through options")
        self.main_tabs.add_to_tab('Selection', select)
        
        self.select_display = TextLabel(360, 295, "Choice: 1", 14)
        self.main_tabs.add_to_tab('Selection', self.select_display)
    
    def setup_visual_tab(self):
        """Sets up visual elements tab."""
        # Tab title
        self.main_tabs.add_to_tab('Visual', TextLabel(10, 10, "Visual Elements", 24, (255, 255, 0)))
        
        # Gradient Example
        gradient_label = TextLabel(20, 50, "Color Gradient:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Visual', gradient_label)
        
        gradient = UIGradient(40, 75, 300, 60, [(255, 0, 0), (200, 100, 0), (0, 255, 0), (0, 200, 100), (0, 0, 255)])
        gradient.set_simple_tooltip("Beautiful gradient with multiple colors")
        self.main_tabs.add_to_tab('Visual', gradient)
        
        # Text Label Examples
        labels_label = TextLabel(20, 175, "Text Labels:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Visual', labels_label)
        
        label1 = TextLabel(40, 195, "Regular Label", 18, (255, 255, 255))
        self.main_tabs.add_to_tab('Visual', label1)
        
        label2 = TextLabel(40, 225, "Colored Label", 22, (100, 255, 100))
        self.main_tabs.add_to_tab('Visual', label2)
        
        label3 = TextLabel(40, 255, "Large Label", 28, (255, 200, 50))
        self.main_tabs.add_to_tab('Visual', label3)
        
        # Frame Example
        frame_label = TextLabel(20, 280, "UI Frame:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Visual', frame_label)
        
        frame = UiFrame(40, 300, 200, 100)
        frame.set_background_color((50, 50, 100, 200))
        frame.set_border((100, 150, 255),2)
        self.main_tabs.add_to_tab('Visual', frame)
        
        # Frame with text
        inner_label = TextLabel(5,5, "This is a frame", 16, (255, 255, 255))
        frame.add_child(inner_label)
        
        # Separator line
        separator = TextLabel(20, 420, "Horizontal Separator:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Visual', separator)
        
        separator_line = UiFrame(20, 440, 400, 2)
        separator_line.set_background_color((100, 100, 100))
        self.main_tabs.add_to_tab('Visual', separator_line)
        
        clock12hranalog = Clock(20, 460, 150, None, 16, True, True, '12hr', 'analog')
        clock12hrdigital = Clock(200, 460, 150, None, 16, True, True, '12hr', 'digital')
        clock24hranalog = Clock(380, 460, 150, None, 16, True, True, '24hr', 'analog')
        clock24hrdigital = Clock(560, 460, 150, None, 16, True, True, '24hr', 'digital')
        clock24hrboth = Clock(740, 460, 150, None, 16, True, True, '24hr', 'both')
        self.main_tabs.add_to_tab('Visual', clock12hranalog)
        self.main_tabs.add_to_tab('Visual', clock12hrdigital)
        self.main_tabs.add_to_tab('Visual', clock24hranalog)
        self.main_tabs.add_to_tab('Visual', clock24hrdigital)
        self.main_tabs.add_to_tab('Visual', clock24hrboth)
    
    def setup_advanced_tab(self):
        """Sets up advanced elements tab."""
        # Tab title
        self.main_tabs.add_to_tab('Advanced', TextLabel(10, 10, "Advanced Elements", 24, (255, 255, 0)))
        
        # TextBox Example
        textbox_label = TextLabel(20, 60, "TextBox (Single-line):", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Advanced', textbox_label)
        
        textbox = TextBox(150, 50, 250, 30, "Type here...")
        textbox.set_simple_tooltip("Click and type to enter text")
        self.main_tabs.add_to_tab('Advanced', textbox)
        
        # NEW: TextArea Example
        textarea_label = TextLabel(20, 100, "TextArea (Multi-line):", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Advanced', textarea_label)
        
        self.text_area = TextArea(20, 130, 350, 200, 
                                text=self.demo_state['text_area_content'],
                                font_size=14,
                                line_numbers=True,
                                word_wrap=True,
                                read_only=False,
                                tab_size=4)
        self.text_area.set_simple_tooltip("A multi-line text editor with line numbers and word wrap")
        self.main_tabs.add_to_tab('Advanced', self.text_area)
        
        # TextArea controls
        textarea_controls_y = 340
        textarea_clear_btn = Button(20, textarea_controls_y, 80, 25, "Clear")
        textarea_clear_btn.set_on_click(lambda: self.clear_text_area())
        self.main_tabs.add_to_tab('Advanced', textarea_clear_btn)
        
        textarea_undo_btn = Button(110, textarea_controls_y, 80, 25, "Undo")
        textarea_undo_btn.set_on_click(lambda: self.text_area.undo())
        self.main_tabs.add_to_tab('Advanced', textarea_undo_btn)
        
        textarea_redo_btn = Button(200, textarea_controls_y, 80, 25, "Redo")
        textarea_redo_btn.set_on_click(lambda: self.text_area.redo())
        self.main_tabs.add_to_tab('Advanced', textarea_redo_btn)
        
        textarea_select_all_btn = Button(290, textarea_controls_y, 80, 25, "Select All")
        textarea_select_all_btn.set_on_click(lambda: self.text_area.select_all())
        self.main_tabs.add_to_tab('Advanced', textarea_select_all_btn)
        
        # NEW: FileFinder Example
        filefinder_label = TextLabel(420, 60, "FileFinder:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Advanced', filefinder_label)
        
        self.file_finder = FileFinder(420, 90, 350, 40, 
                                    file_path=self.demo_state['file_finder_path'],
                                    file_filter=['.txt', '.py', '.json', '.png', '.jpg'],
                                    dialog_title="Select a file",
                                    button_text="Browse...",
                                    show_icon=True)
        self.file_finder.set_simple_tooltip("Select a file from your computer")
        self.file_finder.on_file_selected = lambda path: self.on_file_selected(path)
        self.main_tabs.add_to_tab('Advanced', self.file_finder)
        
        # FileFinder status display
        self.file_finder_status = TextLabel(420, 135, f"Selected: {self.demo_state['file_finder_path']}", 14, (200, 200, 200))
        self.main_tabs.add_to_tab('Advanced', self.file_finder_status)
        
        # NEW: Pagination Example
        pagination_label = TextLabel(420, 210, "Pagination:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Advanced', pagination_label)
        
        self.pagination = Pagination(420, 230, 350, 40, total_pages=10, current_page=self.demo_state['current_page'])
        self.pagination.set_on_page_change(lambda page, _: self.on_page_change(page))
        self.pagination.set_simple_tooltip("Navigate through pages")
        self.main_tabs.add_to_tab('Advanced', self.pagination)
        
        # Pagination controls
        pagination_controls_y = 290
        pagination_prev_btn = Button(420, pagination_controls_y, 80, 25, "Previous")
        pagination_prev_btn.set_on_click(lambda: self.pagination.previous_page())
        self.main_tabs.add_to_tab('Advanced', pagination_prev_btn)
        
        pagination_next_btn = Button(510, pagination_controls_y, 80, 25, "Next")
        pagination_next_btn.set_on_click(lambda: self.pagination.next_page())
        self.main_tabs.add_to_tab('Advanced', pagination_next_btn)
        
        pagination_goto_btn = Button(600, pagination_controls_y, 80, 25, "Go to 5")
        pagination_goto_btn.set_on_click(lambda: self.pagination.set_page(5))
        self.main_tabs.add_to_tab('Advanced', pagination_goto_btn)
        
        self.pagination_display = TextLabel(690, pagination_controls_y + 5, f"Page: {self.demo_state['current_page']}/10", 14)
        self.main_tabs.add_to_tab('Advanced', self.pagination_display)
        
        # ScrollingFrame Example (moved to make room)
        scroll_label = TextLabel(20, 380, "Scrolling Frame:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Advanced', scroll_label)
        
        scroll_frame = ScrollingFrame(20, 410, 350, 200, 330, 600)
        scroll_frame.set_simple_tooltip("Scrollable container with multiple items")
        self.main_tabs.add_to_tab('Advanced', scroll_frame)
        
        # Add items to scrolling frame
        for i in range(15):
            item_color = (100 + i * 10, 150, 200) if i % 2 == 0 else (200, 150, 100 + i * 10)
            if i % 2 == 0: # Even = TextLabel
                item = TextLabel(10, 15 + i * 30, f"Scrollable Item {i + 1}", 14, item_color)
            else: # Odd = Button
                item = Button(10, 15 + i * 30, 80, 20, f"Button {i + 1}")
                item.set_on_click(print, f"Button {i + 1} clicked!")
                
            scroll_frame.add_child(item)
        
        # Dialog Button (moved to right side)
        dialog_label = TextLabel(400, 360, "Dialog Box:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Advanced', dialog_label)
        
        dialog_btn = Button(500, 355, 150, 40, "Show Dialog")
        dialog_btn.set_on_click(lambda: self.show_dialog())
        dialog_btn.set_simple_tooltip("Click to show an RPG-style dialog box")
        self.main_tabs.add_to_tab('Advanced', dialog_btn)
        
        # Advanced Tooltip Button
        tooltip_label = TextLabel(400, 410, "Advanced Tooltip:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Advanced', tooltip_label)
        
        advanced_tooltip_btn = Button(500, 405, 180, 40, "Hover for Tooltip")
        advanced_tooltip_config = TooltipConfig(
            text="This is an advanced tooltip with custom styling! It has more padding, and a delay.",
            font_size=16,
            padding=12,
            offset_x=15,
            offset_y=15,
            show_delay=0.2,
            max_width=250,
        )
        advanced_tooltip = Tooltip(advanced_tooltip_config)
        advanced_tooltip_btn.set_tooltip(advanced_tooltip)
        self.main_tabs.add_to_tab('Advanced', advanced_tooltip_btn)
    
    def setup_animation_tab(self):
        """Sets up animation examples tab."""
        # Tab title
        self.main_tabs.add_to_tab('Animation', TextLabel(10, 10, "Animation Examples", 24, (255, 255, 0)))
        
        # Animation controls label
        animation_controls = TextLabel(20, 50, "Animation Controls:", 20, (200, 200, 255))
        self.main_tabs.add_to_tab('Animation', animation_controls)
        
        # Linear Animation Example
        linear_label = TextLabel(20, 80, "Linear Animation:", 16, (100, 255, 100))
        self.main_tabs.add_to_tab('Animation', linear_label)
        
        self.linear_box = UiFrame(20, 105, 20, 20)
        self.linear_box.set_background_color((100, 255, 100))
        self.main_tabs.add_to_tab('Animation', self.linear_box)
        
        # Linear animation path
        self.linear_path = UiFrame(20, 115, 300, 3)
        self.linear_path.set_background_color((50, 150, 50, 100))
        self.linear_path.z_index = -1
        self.main_tabs.add_to_tab('Animation', self.linear_path)
        
        self.linear_progress = TextLabel(330, 110, "0%", 14, (100, 255, 100))
        self.main_tabs.add_to_tab('Animation', self.linear_progress)
        
        # Bounce Animation Example
        bounce_label = TextLabel(20, 140, "Bounce Animation:", 16, (255, 200, 50))
        self.main_tabs.add_to_tab('Animation', bounce_label)
        
        self.bounce_box = UiFrame(20, 165, 20, 20)
        self.bounce_box.set_background_color((255, 200, 50))
        self.main_tabs.add_to_tab('Animation', self.bounce_box)
        
        # Bounce animation path
        self.bounce_path = UiFrame(20, 175, 300, 3)
        self.bounce_path.set_background_color((200, 150, 50, 100))
        self.bounce_path.z_index = -1
        self.main_tabs.add_to_tab('Animation', self.bounce_path)
        
        self.bounce_progress = TextLabel(330, 170, "0%", 14, (255, 200, 50))
        self.main_tabs.add_to_tab('Animation', self.bounce_progress)
        
        # Back Animation Example
        back_label = TextLabel(20, 200, "Back Animation:", 16, (255, 100, 100))
        self.main_tabs.add_to_tab('Animation', back_label)
        
        self.back_box = UiFrame(20, 225, 20, 20)
        self.back_box.set_background_color((255, 100, 100))
        self.main_tabs.add_to_tab('Animation', self.back_box)
        
        # Back animation path
        self.back_path = UiFrame(20, 235, 300, 3)
        self.back_path.set_background_color((200, 50, 50, 100))
        self.back_path.z_index = -1
        self.main_tabs.add_to_tab('Animation', self.back_path)
        
        self.back_progress = TextLabel(330, 230, "0%", 14, (255, 100, 100))
        self.main_tabs.add_to_tab('Animation', self.back_progress)
        
        # Animation control buttons (horizontal layout)
        control_y = 270
        pause_btn = Button(20, control_y, 90, 30, "Pause All")
        pause_btn.set_on_click(lambda: self.pause_animations())
        pause_btn.set_simple_tooltip("Pause all animations")
        self.main_tabs.add_to_tab('Animation', pause_btn)
        
        resume_btn = Button(120, control_y, 90, 30, "Resume All")
        resume_btn.set_on_click(lambda: self.resume_animations())
        resume_btn.set_simple_tooltip("Resume all animations")
        self.main_tabs.add_to_tab('Animation', resume_btn)
        
        reset_btn = Button(220, control_y, 90, 30, "Reset All")
        reset_btn.set_on_click(lambda: self.reset_animations())
        reset_btn.set_simple_tooltip("Reset all animations")
        self.main_tabs.add_to_tab('Animation', reset_btn)
        
        # Animation speed control
        speed_label = TextLabel(20, 310, "Animation Speed:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Animation', speed_label)
        
        self.speed_slider = Slider(170, 310, 150, 20, 0.5, 3.0, 1.0)
        self.speed_slider.on_value_changed = lambda v: self.update_animation_speed(v)
        self.speed_slider.set_simple_tooltip("Adjust animation speed (0.5x to 3.0x)")
        self.main_tabs.add_to_tab('Animation', self.speed_slider)
        
        self.speed_display = TextLabel(330, 315, "1.0x", 14)
        self.main_tabs.add_to_tab('Animation', self.speed_display)
        
        # Loop control buttons
        loop_label = TextLabel(20, 350, "Loop Controls:", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Animation', loop_label)
        
        loop_y = 380
        loop_btn = Button(20, loop_y, 100, 30, "3 Loops")
        loop_btn.set_on_click(lambda: self.set_animations_loops(3))
        loop_btn.set_simple_tooltip("Set all animations to loop 3 times")
        self.main_tabs.add_to_tab('Animation', loop_btn)

        infinite_loop_btn = Button(130, loop_y, 100, 30, "Infinite")
        infinite_loop_btn.set_on_click(lambda: self.set_animations_loops(-1))
        infinite_loop_btn.set_simple_tooltip("Set all animations to loop infinitely")
        self.main_tabs.add_to_tab('Animation', infinite_loop_btn)

        no_loop_btn = Button(240, loop_y, 100, 30, "No Loop")
        no_loop_btn.set_on_click(lambda: self.set_animations_loops(0))
        no_loop_btn.set_simple_tooltip("Disable looping for all animations")
        self.main_tabs.add_to_tab('Animation', no_loop_btn)
        
        # Loop count display
        self.loop_display = TextLabel(20, 420, "Loops: Infinite", 16, (200, 200, 255))
        self.main_tabs.add_to_tab('Animation', self.loop_display)
        
        # Animation description
        desc_text = "Animations use the Tween system with Yoyo effect (forward-backward motion)."
        desc_label = TextLabel(20, 460, desc_text, 14, (150, 200, 255))
        self.main_tabs.add_to_tab('Animation', desc_label)
        
    def clear_text_area(self):
        """Clear the text area content."""
        self.text_area.set_text("")
        self.demo_state['text_area_content'] = ""
        print("Text area cleared")

    def on_file_selected(self, file_path):
        """Handle file selection."""
        self.demo_state['file_finder_path'] = file_path
        self.file_finder_status.set_text(f"Selected: {file_path}")
        print(f"File selected: {file_path}")

    def on_page_change(self, page):
        """Handle pagination page change."""
        self.demo_state['current_page'] = page
        self.pagination_display.set_text(f"Page: {page}/10")
        print(f"Page changed to: {page}")
    
    def set_animations_loops(self, loops: int):
        """Set loop count for all animations"""
        yoyo = True  # Always use yoyo for this demo
        
        for anim_name, tween in self.animations.items():
            tween.set_loops(loops, yoyo=yoyo)
        
        if loops == -1:
            loop_text = "Loops: Infinite"
        elif loops == 0:
            loop_text = "Loops: No Loop"
        else:
            loop_text = f"Loops: {loops}"
        
        if yoyo:
            loop_text += " (Yoyo)"
        
        self.loop_display.set_text(loop_text)
        print(f"Set all animations to {loops if loops != -1 else 'infinite'} loops with yoyo={yoyo}")
    
    def setup_animations(self):
        """Set up the three animation examples using Tween system"""
        
        # 1. Linear Animation (smooth back and forth with yoyo)
        linear_tween = Tween.create(self.linear_box)
        linear_tween.to(
            x=320,  # Move within the tab boundaries
            duration=2.0,
            easing=EasingType.LINEAR
        )
        linear_tween.set_loops(-1, yoyo=True)  # Infinite loops with yoyo
        self.animation_handler.add('linear_animation', linear_tween, auto_play=True)
        
        # 2. Bounce Animation (bounces at the end with yoyo)
        bounce_tween = Tween.create(self.bounce_box)
        bounce_tween.to(
            x=320,  # Move within the tab boundaries
            duration=2.0,
            easing=EasingType.BOUNCE_OUT
        )
        bounce_tween.set_loops(-1, yoyo=True)  # Infinite loops with yoyo
        self.animation_handler.add('bounce_animation', bounce_tween, auto_play=True)
        
        # 3. Back Animation (overshoots and comes back with yoyo)
        back_tween = Tween.create(self.back_box)
        back_tween.to(
            x=320,  # Move within the tab boundaries
            duration=2.0,
            easing=EasingType.BACK_OUT
        )
        back_tween.set_loops(-1, yoyo=True)  # Infinite loops with yoyo
        self.animation_handler.add('back_animation', back_tween, auto_play=True)
        
        # Store animation objects for reference
        self.animations['linear'] = linear_tween
        self.animations['bounce'] = bounce_tween
        self.animations['back'] = back_tween
        
        print("Animations started with infinite yoyo loops")
    
    def update_animation_display(self, anim_type: str, tween: Tween):
        """Update animation progress display"""
        progress = tween.get_progress_percentage()
        current_loop = tween.current_loop
        yoyo_dir = "forward" if tween.yoyo_forward else "backward"
        
        if anim_type == 'linear':
            self.linear_progress.set_text(f"{progress:.0f}% L{current_loop} {yoyo_dir}")
        elif anim_type == 'bounce':
            self.bounce_progress.set_text(f"{progress:.0f}% L{current_loop} {yoyo_dir}")
        elif anim_type == 'back':
            self.back_progress.set_text(f"{progress:.0f}% L{current_loop} {yoyo_dir}")
    
    def update_animation_speed(self, speed: float):
        """Update animation speed by adjusting duration"""
        self.speed_display.set_text(f"{speed:.1f}x")
        
        # Update all animations' durations based on speed multiplier
        base_duration = 2.0  # Original duration
        new_duration = base_duration / speed
        
        for anim_name, tween in self.animations.items():
            # Use the new set_duration method
            tween.set_duration(new_duration)
            print(f"Updated {anim_name} animation duration to {new_duration:.2f}s")
    
    def pause_animations(self):
        """Pause all animations"""
        self.animation_handler.pause_all()
        print("All animations paused")
    
    def resume_animations(self):
        """Resume all animations"""
        self.animation_handler.resume_all()
        print("All animations resumed")
    
    def reset_animations(self):
        """Reset all animations to starting position"""
        print("Resetting animations...")
        
        # Cancel current animations
        self.animation_handler.cancel_all()
        
        # Reset box positions
        self.linear_box.x = 20
        self.bounce_box.x = 20
        self.back_box.x = 20
        
        # Reset progress displays
        self.linear_progress.set_text("0%")
        self.bounce_progress.set_text("0%")
        self.back_progress.set_text("0%")
        
        # Restart animations
        self.setup_animations()
        print("Animations reset and restarted")
    
    def update_state(self, key, value):
        """Updates the demo state and prints feedback for interactive elements."""
        self.demo_state[key] = value
        
        # Print for debug/console feedback
        if key in ['dropdown_selection', 'switch_state', 'number_selector_value', 'checkbox_state']:
            print(f"{key}: {value}")
    
    def add_progress(self, amount):
        """Increments the progress bar value."""
        self.demo_state['progress_value'] = min(100, self.demo_state['progress_value'] + amount)
        self.progress_bar.set_value(self.demo_state['progress_value'])
    
    def show_dialog(self):
        """Shows the RPG-style dialog box."""
        self.dialog_box.visible = True
        self.dialog_box.set_text(
            "Welcome to LunaEngine! This is an RPG-style dialog box with typewriter animation. Click to continue...",
            "System",
            instant=False  # Change to True if you want instant display
        )
        self.demo_state['dialog_active'] = True
    
    def hide_dialog(self):
        """Hides the RPG-style dialog box."""
        self.dialog_box.visible = False
        self.demo_state['dialog_active'] = False
    
    def update_ui_displays(self):
        """Updates all TextLabels that reflect current UI state."""
        self.button_counter.set_text(f"Clicks: {self.demo_state['button_clicks']}")
        self.slider_display.set_text(f"Value: {self.demo_state['slider_value']:.1f}")
        self.dropdown_display.set_text(f"Selected: {self.demo_state['dropdown_selection']}")
        self.switch_display.set_text(f"Switch: {'ON' if self.demo_state['switch_state'] else 'OFF'}")
        self.progress_display.set_text(f"Progress: {self.demo_state['progress_value']}%")
        self.select_display.set_text(f"Choice: {self.demo_state['select_index'] + 1}")
        
        # Existing element displays
        self.number_selector_display.set_text(f"Number: {self.demo_state['number_selector_value']:02d}")
        self.checkbox_display.set_text(f"Feature X: {'ON' if self.demo_state['checkbox_state'] else 'OFF'}")
        
        self.fps_display.set_text(f"FPS: {self.engine.get_fps_stats()['current_fps']:.1f}")
        
        # Update text area content if it exists
        if hasattr(self, 'text_area'):
            current_text = self.text_area.get_text()
            if current_text != self.demo_state.get('text_area_content'):
                self.demo_state['text_area_content'] = current_text
    
    def on_controller_connected(self, controller):
        print(f"[Controller] Connected: {controller.name}")
        self.refresh_controller_dropdown()

    def on_controller_disconnected(self, controller):
        print(f"[Controller] Disconnected: {controller.name}")
        self.refresh_controller_dropdown()
    
    def update(self, dt):
        # Update UI displays
        self.update_ui_displays()
        self.update_controller_display()

        current_count = len(self.engine.controller_manager.get_all_controllers())
        if current_count != self.last_controller_count:
            self.refresh_controller_dropdown()
            self.last_controller_count = current_count
        
        # Update progress bar animation
        if self.demo_state['progress_value'] < 100:
            self.demo_state['progress_value'] += dt * 2
            self.progress_bar.set_value(self.demo_state['progress_value'])
    
    def render(self, renderer):
        renderer.fill_screen(ThemeManager.get_color('background'))
        
        # Header background
        renderer.draw_rect(0, 0, self.engine.width, 90, ThemeManager.get_color('background2'))

def main():
    # Create engine
    engine = LunaEngine("LunaEngine - UI Demo", 1024, 768, use_opengl=True, fullscreen=False)
    
    # Configure the max FPS
    engine.fps = 60
    
    # Add and set the main scene
    engine.add_scene("main", ComprehensiveUIDemo)
    engine.set_scene("main")
    
    # Run the engine
    engine.run()

if __name__ == "__main__":
    main()
About This Example

ui_comprehensive_demo.py - Comprehensive UI Elements Demo for LunaEngine