ImmediateGUI

An IMGUI inspired GUI Framework for javascript thats designed to be as simple to use as IMGUI.

此脚本不应直接安装,它是供其他脚本使用的外部库。如果你需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.org/scripts/535798/1591785/ImmediateGUI.js

class ImmediateGUI {
    static OccupiedElementIds = [];
    static GenerateId(prefix = "gui_") {
        if (typeof prefix !== 'string' || prefix.length === 0) {
            prefix = "gui_";
        }
        const timestamp = Date.now().toString(36);
        const randomPart = Math.random().toString(36).substring(2, 15);
        const generatedId = prefix + timestamp + randomPart;
        const exists = ImmediateGUI.OccupiedElementIds.includes(generatedId);
        if (exists) return ImmediateGUI.GenerateId(prefix);
        ImmediateGUI.OccupiedElementIds.push(generatedId);
        return generatedId;
    }

    constructor(options = {}) {
        this.options = {
            theme: 'dark',
            position: 'right',
            width: 300,
            draggable: true,
            title: 'Immediate GUI',
            titleLeftAligned: true,
            ...options
        };

        this.themes = {
            light: {
                background: '#ffffff',
                text: '#333333',
                border: '#cccccc',
                accent: '#4285f4',
                buttonBg: '#f5f5f5',
                buttonHover: '#e0e0e0',
                inputBg: '#ffffff',
                sectionBg: '#f9f9f9'
            },
            dark: {
                // background: '#2d2d2d',
                background: '#151617',
                // text: '#e0e0e0',
                text: '#eef0f2',
                // border: '#555555',
                border: '#425069',
                // accent: '#4d90fe',
                accent: '#294a7a',
                // buttonBg: '#444444',
                buttonBg: '#274972',
                // buttonHover: '#555555',
                buttonHover: '#336caf',
                // inputBg: '#3d3d3d',
                inputBg: '#20324d',
                // sectionBg: '#333333'
                sectionBg: '#232426',
            }
        };

        this.maxHeight = '85vh';
        //this.maxHeight = 'auto;';

        this.theme = this.themes[this.options.theme] || this.themes.light;

        // Create main container
        this.container = document.createElement('div');
        this.container.id = ImmediateGUI.GenerateId();

        this._applyGlobalStyles();
        this._updateCSSVariables();

        this.container.style.cssText = `
            position: fixed;
            ${this.options.position === 'right' ? 'right' : 'left'}: 10px;
            top: 10px;
            min-width: ${(typeof this.options.width === 'string' ? this.options.width : this.options.width) + 'px'};
            background: var(--imgui-bg);
            color: var(--imgui-text);
            border: 1px solid var(--imgui-border);
            border-radius: 4px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
            -webkit-font-smoothing: antialiased;
            font-size: 14px;
            z-index: 9999;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            padding: 0; /* Remove all padding */
            max-height: ${this.maxHeight};
            overflow-y: auto;
            overflow-x: hidden;
            transition: all 0.2s ease;
        `;

        this._createTitleBar(this.options.titleLeftAligned);
        
        this.contentContainer = document.createElement('div');
        this.contentContainer.id = ImmediateGUI.GenerateId('content_');

        this.contentContainer.style.cssText = `
            width: 100%;
            box-sizing: border-box;
            padding: 0 12px 12px 12px; /* Add padding to content area only */
        `;
        this.container.appendChild(this.contentContainer);

        this.currentSection = null;

        this.indentationLevel = 0;
        this.indentationSize = 10; // pixels per level
        this.isCustomIndentationLevel = false;

        if (this.options.draggable) {
            this._setupDragging();
        }
    }

    _createTitleBar(leftAlign = true) {
        this.titleBar = document.createElement('div');
        this.titleBar.className = 'imgui-titlebar';
        this.titleBar.style.cssText = `
            width: 100% !important;
            padding-top: var(--imgui-bottom-padding);
            background: var(--imgui-section-bg);
            color: var(--imgui-text);
            font-weight: bold;
            cursor: ${this.options.draggable ? 'pointer' : 'default'};
            user-select: none;
            box-sizing: border-box;
            position: sticky;
            top: 0;
            z-index: 999999999;
            margin-bottom: 12px;
            border-bottom: 1px solid var(--imgui-border);
            ${leftAlign ? 'text-align: left; padding-left: 12px;' : 'text-align: center;'}
            height: var(--imgui-title-height);
        `;

        this.titleBar.textContent = this.options.title || 'ImmediateGUI';
        this.container.appendChild(this.titleBar);
    }

    _updateCSSVariables() {
        document.documentElement.style.setProperty('--imgui-bg', this.theme.background);
        document.documentElement.style.setProperty('--imgui-text', this.theme.text);
        document.documentElement.style.setProperty('--imgui-border', this.theme.border);
        document.documentElement.style.setProperty('--imgui-accent', this.theme.accent);
        document.documentElement.style.setProperty('--imgui-button-bg', this.theme.buttonBg);
        document.documentElement.style.setProperty('--imgui-button-hover', this.theme.buttonHover);
        document.documentElement.style.setProperty('--imgui-input-bg', this.theme.inputBg);
        document.documentElement.style.setProperty('--imgui-section-bg', this.theme.sectionBg);
    }

    _applyGlobalStyles() {
        const styleSheetId = `imgui-global-styles_${this.container.id}`;

        if (!document.getElementById(styleSheetId)) {
            const styleEl = document.createElement('style');

            styleEl.id = styleSheetId;
            styleEl.textContent = `
                :root {
                    --imgui-bg: ${this.theme.background};
                    --imgui-text: ${this.theme.text};
                    --imgui-border: ${this.theme.border};
                    --imgui-accent: ${this.theme.accent};
                    --imgui-button-bg: ${this.theme.buttonBg};
                    --imgui-button-hover: ${this.theme.buttonHover};
                    --imgui-input-bg: ${this.theme.inputBg};
                    --imgui-section-bg: ${this.theme.sectionBg};
                    --imgui-bottom-padding: 3px;
                    --imgui-title-height: 27px;
                    --imgui-scrollbar-width: 0.5em;
                }

                #${this.container.id} {
                    scrollbar-gutter: auto;
                    /* scrollbar-color: var(--imgui-accent); */
                    /* scrollbar-width: thin; */

                    &::-webkit-scrollbar {
                        width: var(--imgui-scrollbar-width);
                        height: 0.5em;
                        background-color: var(--imgui-section-bg);
                    }

                    &::-webkit-scrollbar-thumb {
                        background-color: var(--imgui-accent);
                        /* border-radius: 3px; */
                    }

                    /* This positions the scrollbar to start below the title bar */
                    &::-webkit-scrollbar-button:start:decrement {
                        height: var(--imgui-title-height);
                        display: block;
                        background-color: transparent;
                    }
                }

                .imgui-titlebar {
                    width: 100% !important;
                    box-sizing: border-box;
                    right: 0;
                    left: 0;
                }

                .imgui-progressbar {

                }

                .imgui-slider {
                    accent-color: var(--imgui-accent);
                }

                .imgui-slider:hover {
                    accent-color: var(--imgui-button-hover);
                }

                .imgui-control {
                    margin-bottom: 2px;
                    width: 100%;
                    box-sizing: border-box;
                }
                
                .imgui-button {
                    background: var(--imgui-button-bg);
                    color: var(--imgui-text);
                    border: 1px solid var(--imgui-border);
                    border-radius: 4px;
                    padding: 8px 12px;
                    font-size: 14px;
                    cursor: pointer;
                    transition: all 0.2s ease;
                    outline: none;
                    width: auto;
                    font-family: inherit;
                    margin-right: 5px;
                }
                
                .imgui-button:hover {
                    background: var(--imgui-button-hover);
                }
                
                .imgui-button:active {
                    transform: translateY(1px);
                }
                
                .imgui-input {
                    background: var(--imgui-input-bg);
                    color: var(--imgui-text);
                    border: 1px solid var(--imgui-border);
                    border-radius: 4px;
                    padding: 8px 10px;
                    font-size: 14px;
                    width: 100%;
                    box-sizing: border-box;
                    outline: none;
                    transition: border-color 0.2s ease;
                    font-family: inherit;
                }

                .imgui-input::placeholder {
                    color: var(--imgui-text);
                    opacity: 0.5;
                }
                
                .imgui-input:focus {
                    border-color: var(--imgui-accent);
                }
                
                .imgui-section {
                    border: 1px solid var(--imgui-border);
                    border-radius: 4px;
                    padding: 10px 10px 0px;
                    margin-bottom: 12px;
                    background: var(--imgui-section-bg);
                }
                
                .imgui-section-header {
                    font-weight: 600;
                    margin-bottom: 8px;
                    padding-bottom: 6px;
                    border-bottom: 1px solid var(--imgui-border);
                    color: var(--imgui-text);
                }
                
                .imgui-label {
                    display: block;
                    margin-bottom: 4px;
                    color: var(--imgui-text);
                    font-weight: 500;
                }
            `;
            document.head.appendChild(styleEl);
            this.container.classList.add('imgui-container');
        }
    }

    _setupDragging() {
        let isDragging = false;
        let startX, startY, startLeft, startTop;

        const isClickOnControl = (element) => {
            if (!element) return false;
            
            return !element.classList.contains('imgui-titlebar');

            let current = element;
            while (current && current !== this.container) {
                if (
                    /* current.classList.contains('imgui-control') || */
                    current.tagName === 'INPUT' ||
                    current.tagName === 'BUTTON' ||
                    current.tagName === 'SELECT' ||
                    current.tagName === 'TEXTAREA'
                ) {
                    return true;
                }
                current = current.parentElement;
            }
            return false;
        };

        this.container.addEventListener('mousedown', (e) => {
            if (e.button !== 0) return; 
            if (isClickOnControl(e.target)) {
                return;
            }
            
            isDragging = true;
            
            startX = e.clientX;
            startY = e.clientY;
            
            const rect = this.container.getBoundingClientRect();
            startLeft = rect.left;
            startTop = rect.top;
            
            document.body.style.cursor = 'move';
            
            e.preventDefault();
        });
        
        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            
            const newLeft = startLeft + dx;
            const newTop = startTop + dy;
            
            this.container.style.left = `${newLeft}px`;
            this.container.style.top = `${newTop}px`;
            this.container.style.right = 'auto';
        });
        
        document.addEventListener('mouseup', () => {
            if (isDragging) {
                isDragging = false;
                document.body.style.cursor = '';
                
                this._keepInView();
            }
        });
        
        document.addEventListener('mouseleave', () => {
            if (isDragging) {
                isDragging = false;
                document.body.style.cursor = '';
            }
        });
    }
    
    _keepInView() {
        const rect = this.container.getBoundingClientRect();
        const windowWidth = window.innerWidth;
        const windowHeight = window.innerHeight;
        
        const minVisiblePx = 50;
        
        let newLeft = rect.left;
        let newTop = rect.top;
        
        if (rect.right < minVisiblePx) {
            newLeft = minVisiblePx - rect.width;
        } else if (rect.left > windowWidth - minVisiblePx) {
            newLeft = windowWidth - minVisiblePx;
        }
        
        if (rect.bottom < minVisiblePx) {
            newTop = minVisiblePx - rect.height;
        } else if (rect.top > windowHeight - minVisiblePx) {
            newTop = windowHeight - minVisiblePx;
        }
        
        if (newLeft !== rect.left || newTop !== rect.top) {
            this.container.style.left = `${newLeft}px`;
            this.container.style.top = `${newTop}px`;
        }
    }

    // Section management
    BeginSection(title, collapsible = false, collapsedByDefault = false) {
        const section = document.createElement('div');
        section.className = 'imgui-section';
        section.id = ImmediateGUI.GenerateId('section_');
        section.style.paddingBottom = 'var(--imgui-bottom-padding)';

        if (title) {
            const header = document.createElement('div');
            header.className = 'imgui-section-header';
            header.style.color = `var(--imgui-text)`;
            header.style.borderBottom = `1px solid var(--imgui-border)`;


            if (collapsible) {
                header.style.cssText = `
                    display: flex;
                    align-items: center;
                    cursor: pointer;
                    user-select: none;
                    margin-bottom: 8px;
                    padding-bottom: 6px;
                `;
                
                const indicator = document.createElement('span');
                indicator.className = 'imgui-section-indicator';
                indicator.textContent = '▼';
                indicator.style.cssText = `
                    margin-right: 8px;
                    font-size: 10px;
                    transition: transform 0.2s ease;
                `;
                
                const titleSpan = document.createElement('span');
                titleSpan.textContent = title;
                titleSpan.style.flex = '1';
                
                const content = document.createElement('div');
                content.className = 'imgui-section-content';
                content.style.cssText = `
                    overflow: hidden;
                    transition: max-height 0.3s ease;
                `;
                
                section.isCollapsed = false;
                
                const toggleCollapse = () => {
                    section.isCollapsed = !section.isCollapsed;
                    
                    if (section.isCollapsed) {
                        content.style.maxHeight = '0px';
                        indicator.textContent = '►';
                        // TODO: figure out why the ► character looks squished and skinny??
                        //indicator.style.fontSize = '14px';
                        indicator.style.transform = 'rotate(0deg)';
                    } else {
                        content.style.maxHeight = '2000px';
                        indicator.textContent = '▼'; 
                        indicator.style.transform = 'rotate(0deg)';
                    }
                };

                if (collapsedByDefault) toggleCollapse();
                
                header.addEventListener('click', toggleCollapse);
                
                header.appendChild(indicator);
                header.appendChild(titleSpan);
                section.appendChild(header);
                section.appendChild(content);
                
                section.contentContainer = content;
            } 
            else {
                header.textContent = title;
                section.appendChild(header);
            }
        }
        
        this._getTargetContainer().appendChild(section);
        this.currentSection = section;
        return this;
    }

    EndSection() {
        this.currentSection = null;
        return this;
    }

    // Indentation management
    BeginIndentation(level = -1) {
        if (level === -1) this.indentationLevel++;
        else {
            this.isCustomIndentationLevel = true;
            this.indentationLevel = level;
        }
        return this;
    }

    EndIndentation() {
        if (this.indentationLevel > 0) {
            if (this.isCustomIndentationLevel) {
                this.indentationLevel = 0;
                this.isCustomIndentationLevel = false;
            }
            else this.indentationLevel--;
        }
        return this;
    }

    _applyIndentation(element) {
        if (this.indentationLevel > 0) {
            const currentIndent = this.indentationLevel * this.indentationSize;
            element.style.marginLeft = `${currentIndent}px`;
        }
        return element;
    }

    // Utility to get current target container
    _getTargetContainer() {
        // if (this.currentSection) {
        //     // If current section is collapsible, use its content container
        //     if (this.currentSection.contentContainer) {
        //         return this.currentSection.contentContainer;
        //     }
        //     return this.currentSection;
        // }
        // return this.container;

        if (this.currentSection) {
            // If current section is collapsible, use its content container
            if (this.currentSection.contentContainer) {
                return this.currentSection.contentContainer;
            }
            return this.currentSection;
        }
        return this.contentContainer; // Changed from this.container
    }

    // Original API methods with improved implementation
    GetControlContainer() {
        return this.container;
    }

    GetControls() {
        return this.GetControlContainer().querySelectorAll('.imgui-wrapper');
    }

    Separator() {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `
            display: flex;
            width: 100%;
            margin-bottom: var(--imgui-bottom-padding);
        `;
        
        const separator = document.createElement('hr');
        separator.id = ImmediateGUI.GenerateId("ctrl_");
        separator.style.cssText = `
            border: none;
            border-top: 1px solid ${this.theme.border};
            margin: 10px 0;
            width: 100%;
        `;
        
        wrapper.appendChild(separator);
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return separator;
    }

    Header(text, level = 1) {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `
            display: flex;
            width: 100%;
            margin-bottom: var(--imgui-bottom-padding);
        `;
        
        const validLevel = Math.min(Math.max(level, 1), 6);
        const header = document.createElement(`h${validLevel}`);
        header.id = ImmediateGUI.GenerateId("ctrl_");
        header.textContent = text;
        header.style.cssText = `
            margin: 0 0 10px 0;
            padding: 0;
            font-weight: ${validLevel <= 2 ? 'bold' : '600'};
            color: ${this.theme.text};
            font-size: ${24 - (validLevel * 2)}px;
            font-family: inherit;
            width: 100%;
        `;
        
        wrapper.appendChild(header);
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return header;
    }

    Button(text, callback, tooltip = '') {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `
            display: flex;
            margin-bottom: var(--imgui-bottom-padding);
        `;
        
        const btn = document.createElement('button');
        btn.id = ImmediateGUI.GenerateId("ctrl_");
        btn.textContent = text;
        btn.className = "imgui-button imgui-control";
        if (typeof tooltip === 'string' && tooltip.length > 0) btn.title = tooltip;
        
        btn.addEventListener('mouseenter', () => {
            //btn.style.background = this.theme.buttonHover;
            btn.style.background = 'var(--imgui-button-hover)';
        })
        btn.addEventListener('mouseout', () => {
            //btn.style.background = this.theme.buttonBg;
            btn.style.background = 'var(--imgui-button-bg)';
        })

        if (callback && typeof callback === 'function') {
            btn.addEventListener('click', callback);
        }
        
        wrapper.appendChild(btn);
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return btn;
    }

    Textbox(placeholder, defaultValue = "", tooltip = '') {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `
            display: flex;
            flex-direction: column;
            margin-bottom: var(--imgui-bottom-padding);
        `;
        
        const input = document.createElement('input');
        input.id = ImmediateGUI.GenerateId("ctrl_");
        input.type = 'text';
        input.placeholder = placeholder;
        input.value = defaultValue;
        input.style.background = 'var(--imgui-input-bg)';
        input.style.border = '1px solid var(--imgui-border)';
        input.className = "imgui-input imgui-control";
        if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) input.title = tooltip;
        
        wrapper.appendChild(input);
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return input;
    }

    TextArea(placeholder = "", defaultValue = "", rows = 4, tooltip = '') {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `
            display: flex;
            flex-direction: column;
            margin-bottom: var(--imgui-bottom-padding);
        `;
        
        const textarea = document.createElement('textarea');
        textarea.id = ImmediateGUI.GenerateId("ctrl_");
        textarea.placeholder = placeholder;
        textarea.value = defaultValue;
        textarea.rows = rows;
        textarea.className = "imgui-input imgui-control";
        textarea.style.cssText = `
            background: var(--imgui-input-bg);
            resize: vertical;
            min-height: ${rows * 20}px;
            font-family: inherit;
            margin-bottom: 0;
            max-height: calc(${this.maxHeight} - 50px); /* Limit max height to prevent overflowing */
        `;
        if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) textarea.title = tooltip;
        
        textarea.addEventListener('mouseup', () => {
            const container = this.container;
            const containerMaxHeight = parseInt(getComputedStyle(container).maxHeight);
            const containerRect = container.getBoundingClientRect();
            const textareaRect = textarea.getBoundingClientRect();
            
            // Calculate how much space is available in the container
            const availableSpace = containerMaxHeight - (textareaRect.top - containerRect.top) - 20; // 20px buffer
            
            // If textarea is too tall, limit its height
            if (textarea.offsetHeight > availableSpace) {
                textarea.style.height = `${availableSpace}px`;
            }
        });

        wrapper.appendChild(textarea);
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return textarea;
    }

    Label(text) {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `
            display: flex;
            margin-bottom: var(--imgui-bottom-padding);
        `;
        
        const label = document.createElement('label');
        label.id = ImmediateGUI.GenerateId("ctrl_");
        label.textContent = text;
        label.className = "imgui-label imgui-control";
        
        wrapper.appendChild(label);
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return label;
    }

    ProgressBar(value = 0, min = 0, max = 100, showText = true, tooltip = '') {
        // Create wrapper element
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `
            display: flex;
            flex-direction: column;
            width: 100%;
            margin-bottom: var(--imgui-bottom-padding);
        `;
        
        // Create progress container
        const progressContainer = document.createElement('div');
        progressContainer.style.cssText = `
            width: 100%;
            height: 20px;
            background: var(--imgui-input-bg);
            border: 1px solid var(--imgui-border);
            border-radius: 4px;
            overflow: hidden;
            position: relative;
        `;
        
        // Create progress bar element
        const progressBar = document.createElement('div');
        progressBar.id = ImmediateGUI.GenerateId("ctrl_");
        progressBar.className = "imgui-progressbar imgui-control";
        if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) progressBar.title = tooltip;
        
        // Calculate the percentage
        const normalizedValue = Math.min(Math.max(value, min), max);
        const percentage = ((normalizedValue - min) / (max - min)) * 100;
        
        progressBar.style.cssText = `
            width: ${percentage}%;
            height: 100%;
            background: var(--imgui-accent);
            transition: width 0.3s ease;
        `;
        
        // Optional text display
        let textElement = null;
        if (showText) {
            textElement = document.createElement('div');
            textElement.textContent = `${Math.round(percentage)}%`;
            textElement.style.cssText = `
                position: absolute;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                display: flex;
                align-items: center;
                justify-content: center;
                color: var(--imgui-text);
                font-size: 12px;
                font-weight: 500;
                text-shadow: 0 0 2px rgba(0,0,0,0.5);
                pointer-events: none;
            `;
            progressContainer.appendChild(textElement);
        }
        
        // Add elements to the DOM
        progressContainer.appendChild(progressBar);
        wrapper.appendChild(progressContainer);
        
        // Add methods to update the progress bar
        progressBar.setValue = (newValue) => {
            const normalizedNewValue = Math.min(Math.max(newValue, min), max);
            const newPercentage = ((normalizedNewValue - min) / (max - min)) * 100;
            progressBar.style.width = `${newPercentage}%`;
            if (textElement) {
                textElement.textContent = `${Math.round(newPercentage)}%`;
            }
        };
        
        // Store references
        progressBar.textElement = textElement;
        progressBar.min = min;
        progressBar.max = max;
        
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return progressBar;
    }

    ColorPicker(defaultValue = '#000000', tooltip = '') {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `display: flex; align-items: center; padding-bottom: var(--imgui-bottom-padding);`;
        
        const colorPicker = document.createElement('input');
        colorPicker.id = ImmediateGUI.GenerateId("ctrl_");
        colorPicker.type = 'color';
        colorPicker.value = defaultValue;
        colorPicker.className = "imgui-input";
        colorPicker.style.cssText = `
            margin-right: 8px;
            width: 80px;
            height: 40px;
            border: 1px solid var(--imgui-border);
            border-radius: 4px;
            background: none;
            background-color: var(--imgui-input-bg);
            cursor: pointer;
        `;
        if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) colorPicker.title = tooltip;
        
        const colorValue = document.createElement('span');
        colorValue.textContent = defaultValue;
        colorValue.style.cssText = `
            font-family: monospace;
            color: var(--imgui-text);
            cursor: pointer;
        `;

        //colorValue.addEventListener('click', () => { alert('lel'); });
        
        colorPicker.addEventListener('input', () => {
            colorValue.textContent = colorPicker.value;
        });
        
        wrapper.appendChild(colorPicker);
        wrapper.appendChild(colorValue);
        
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return colorPicker;
    }

    DatePicker(defaultValue = new Date().toISOString().split('T')[0], tooltip = '') {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `
            display: flex;
            flex-direction: column;
            margin-bottom: var(--imgui-bottom-padding);
        `;
        
        const datePicker = document.createElement('input');
        datePicker.id = ImmediateGUI.GenerateId("ctrl_");
        datePicker.type = 'date';
        datePicker.value = defaultValue;
        datePicker.className = "imgui-input imgui-control";
        datePicker.style.cursor = "pointer"; 
        if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) datePicker.title = tooltip;

        wrapper.appendChild(datePicker);
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return datePicker;
    }

    Dropdown(options = [], defaultValue = null, tooltip = '') {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `
            display: flex;
            flex-direction: column;
            margin-bottom: var(--imgui-bottom-padding);
        `;

        const select = document.createElement('select');
        select.id = ImmediateGUI.GenerateId("ctrl_");
        select.className = "imgui-input imgui-dropdown imgui-control";
        select.style.cssText = `
            padding: 6px 10px;
            border: 1px solid var(--imgui-border);
            border-radius: 4px;
            background: var(--imgui-input-bg);
            color: var(--imgui-text);
            font-family: inherit;
            cursor: pointer;
            appearance: auto;
        `;
        if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) select.title = tooltip;
        
        // Add options to the select element
        options.forEach(option => {
            const optElement = document.createElement('option');
            optElement.style.backgroundColor = 'var(--imgui-input-bg)';
            optElement.style.color = 'var(--imgui-text)';
            
            // Handle both simple strings and {text, value} objects
            if (typeof option === 'object' && option !== null) {
                optElement.textContent = option.text || option.label || '';
                optElement.value = option.value !== undefined ? option.value : option.text || '';
            } else {
                optElement.textContent = option;
                optElement.value = option;
            }
            
            // Set as selected if it matches the default value
            if (defaultValue !== null && optElement.value === defaultValue) {
                optElement.selected = true;
            }
            
            select.appendChild(optElement);
        });
        
        wrapper.appendChild(select);
        
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return select;
    }

    NumberInput(label, defaultValue = 0, min = null, max = null, tooltip = '') {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `display: flex; align-items: center; margin-bottom: var(--imgui-bottom-padding);`;
        
        const labelElem = document.createElement('label');
        labelElem.textContent = label;
        labelElem.style.cssText = `
            margin-right: 10px;
            margin-top:8px;
            flex: 1;
            color: var(--imgui-text);
        `;
        
        const input = document.createElement('input');
        input.label = labelElem;
        input.id = ImmediateGUI.GenerateId("ctrl_");
        input.type = 'number';
        input.value = defaultValue;
        if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) input.title = tooltip;
        if (min !== null) input.min = min;
        if (max !== null) input.max = max;

        // TODO: hacky solution to make input elements respect .min and .max values when inputting values manually using the keyboard
        if (min !== null || max !== null) {
            input.addEventListener('keyup', () => {
                let currentValue = parseInt(input.value);
                if (isNaN(currentValue)) {
                    input.value = Math.floor(min);
                }
                else {
                    if (min !== null && currentValue < min) {
                        input.value = Math.floor(min);
                    } else if (max !== null && currentValue > max) {
                        input.value = Math.floor(max);
                    }
                }
            });
        }

        input.className = 'imgui-input'
        input.style.cssText = `
            width: 80px;
            padding: 6px;
            border: 1px solid var(--imgui-border);
            border-radius: 4px;
            background: var(--imgui-input-bg);
            color: var(--imgui-text);
            font-family: inherit;
        `;
        
        wrapper.appendChild(labelElem);
        wrapper.appendChild(input);
        
        this._applyIndentation(labelElem);
        this._getTargetContainer().appendChild(wrapper);
        return input;
    }

    Slider(minValue = 0, maxValue = 100, defaultValue = 50, tooltip = '') {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `display: flex; flex-direction: column; margin-bottom: var(--imgui-bottom-padding);`;
        
        const sliderContainer = document.createElement('div');
        sliderContainer.style.cssText = `display: flex; align-items: center; width: 100%;`;
        
        const slider = document.createElement('input');
        slider.className = "imgui-slider";
        slider.id = ImmediateGUI.GenerateId("ctrl_");
        slider.type = 'range';
        slider.min = minValue;
        slider.max = maxValue;
        slider.value = defaultValue;
        slider.style.cssText = `
            flex: 1;
            margin-right: 8px;
            /* accent-color: var(--imgui-accent); */
        `;

        if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) slider.title = tooltip;
        
        const valueDisplay = document.createElement('span');
        valueDisplay.textContent = defaultValue;
        valueDisplay.style.cssText = `
            min-width: 40px;
            text-align: right;
            color: var(--imgui-text);
            font-family: inherit;
            font-weight: 500;
        `;
        
        slider.addEventListener('input', () => {
            valueDisplay.textContent = slider.value;
        });
        
        slider.label = valueDisplay;

        sliderContainer.appendChild(slider);
        sliderContainer.appendChild(valueDisplay);
        wrapper.appendChild(sliderContainer);
        
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return slider;
    }

    Checkbox(label, checked = false, tooltip = '') {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `display: flex; align-items: center; margin-bottom: var(--imgui-bottom-padding)`;
        
        const checkbox = document.createElement('input');
        checkbox.id = ImmediateGUI.GenerateId("ctrl_");
        checkbox.type = 'checkbox';
        checkbox.checked = checked;
        checkbox.style.cssText = `
            margin-right: 8px;
            accent-color: var(--imgui-accent);
            clip-path: circle(46% at 50% 50%);
            width: 16px;
            height: 16px;
        `;

        if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) wrapper.title = tooltip;
        
        const labelElem = document.createElement('label');
        labelElem.textContent = label;
        labelElem.htmlFor = checkbox.id;
        labelElem.style.cssText = `
            cursor: pointer;
            color: var(--imgui-text);
            font-family: inherit;
            margin-top: 6px;
        `;

        checkbox.label = labelElem;
        
        wrapper.appendChild(checkbox);
        wrapper.appendChild(labelElem);
        
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return checkbox;
    }

    RadioButtons(options = [], defaultValue = null, tooltip = '') {
        const wrapper = document.createElement('div');
        wrapper.className = "imgui-control imgui-wrapper";
        wrapper.style.cssText = `
            display: flex;
            flex-direction: column;
            margin-bottom: var(--imgui-bottom-padding);
        `;
        if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) wrapper.title = tooltip;

        // Generate a unique group name for this set of radio buttons
        const groupName = ImmediateGUI.GenerateId("radio_group_");
        
        // Create an object to store references to the radio buttons
        const radioButtons = {};
        
        // Add radio buttons to the wrapper
        options.forEach(option => {
            // Handle both simple strings and {text, value} objects
            let text, value;
            if (typeof option === 'object' && option !== null) {
                text = option.text || option.label || '';
                value = option.value !== undefined ? option.value : option.text || '';
            } else {
                text = option;
                value = option;
            }
            
            const radioContainer = document.createElement('div');
            radioContainer.style.cssText = `
                display: flex;
                align-items: center;
            `;
            
            const radio = document.createElement('input');
            radio.id = ImmediateGUI.GenerateId("ctrl_");
            radio.type = 'radio';
            radio.name = groupName;
            radio.value = value;
            radio.checked = value === defaultValue;
            radio.style.cssText = `
                margin-right: 8px;
                accent-color: var(--imgui-accent);
                width: 16px;
                height: 16px;
            `;
            
            const label = document.createElement('label');
            label.textContent = text;
            label.htmlFor = radio.id;
            label.style.cssText = `
                cursor: pointer;
                color: var(--imgui-text);
                font-family: inherit;
                margin-top: ${radioContainer.style.marginBottom || '6px'};
            `;
            
            radio.label = label;
            
            radioContainer.appendChild(radio);
            radioContainer.appendChild(label);
            wrapper.appendChild(radioContainer);
            
            // Store reference to the radio button
            radioButtons[value] = radio;
        });
        
        // Add helper methods to the radio group
        radioButtons.getChecked = () => {
            const selected = wrapper.querySelector(`input[name="${groupName}"]:checked`);
            return selected ? selected.value : null;
        };
        
        radioButtons.setChecked = (value, checked) => {
            const radio = wrapper.querySelector(`input[name="${groupName}"][value="${value}"]`);
            if (radio) {
                radio.checked = checked;
            }
        };
        
        this._applyIndentation(wrapper);
        this._getTargetContainer().appendChild(wrapper);
        return radioButtons;
    }

    Show() { 
        if (this.container.style.display === 'none') {
            this.container.style.display = 'block';
            return this;
        } else {
            if (this.indentationLevel > 0) {
                console.warn("ImmediateGUI: Show() called while in indentation mode. Did you forget to call EndIndentation() somewhere?");
            }

            if (this.currentSection) {
                console.warn("ImmediateGUI: Show() called while in section mode. Did you forget to call EndSection() somewhere?");
            }

            if (this.container.children.length === 0) return this;
            if (!document.body.contains(this.container)) {

                // Last control wrapper does not need bottom margin, strip that away
                this.GetControls()[this.GetControls().length - 1].style.marginBottom = '0px';
                document.body.appendChild(this.container);
            }
            return this;
        }
    }

    Remove() {
        this.container.remove();
    }

    Hide() {
        this.container.style.display = "none";
        return this;
    }

    ShowModal(message, title = '', options = {}) {
        // Default options
        const config = {
            title: title || '',
            type: 'info', // 'info', 'warning', 'error', 'success'
            buttons: ['OK'],
            closeOnBackdropClick: true,
            width: 400,
            ...options
        };
        
        const backdrop = document.createElement('div');
        backdrop.className = 'imgui-modal-backdrop';
        backdrop.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            z-index: 10000;
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0;
            transition: opacity 0.2s ease;
        `;

        const modal = document.createElement('div');
        modal.className = 'imgui-modal';
        modal.style.cssText = `
            background: var(--imgui-bg);
            border: 1px solid var(--imgui-border);
            border-radius: 6px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
            width: ${config.width}px;
            max-width: 90vw;
            max-height: 80vh;
            overflow-y: auto;
            padding: 16px;
            transform: translateY(-20px);
            opacity: 0;
            transition: transform 0.3s ease, opacity 0.3s ease;
            font-family: inherit;
        `;
        
        if (config.title) {
            const title = document.createElement('div');
            title.className = 'imgui-modal-title';
            title.textContent = config.title;
            title.style.cssText = `
                font-size: 18px;
                font-weight: bold;
                color: var(--imgui-text);
                margin-bottom: 12px;
                padding-bottom: 8px;
                border-bottom: 1px solid var(--imgui-border);
            `;
            modal.appendChild(title);
        }
        
        let iconHtml = '';
        if (config.type === 'warning') {
            iconHtml = '<div style="color: #f0ad4e; margin-right: 10px; font-size: 24px;">⚠️</div>';
        } else if (config.type === 'error') {
            iconHtml = '<div style="color: #d9534f; margin-right: 10px; font-size: 24px;">❌</div>';
        } else if (config.type === 'info') {
            iconHtml = '<div style="color: #5bc0de; margin-right: 10px; font-size: 24px;">ℹ️</div>';
        } else if (config.type === 'success') {
            iconHtml = '<div style="color: #5cb85c; margin-right: 10px; font-size: 24px;">✅</div>';
        }
        
        const messageContainer = document.createElement('div');
        messageContainer.className = 'imgui-modal-message';
        messageContainer.style.cssText = `
            color: var(--imgui-text);
            margin-bottom: 16px;
            line-height: 1.5;
            display: flex;
            align-items: flex-start;
        `;
        
        if (iconHtml) {
            const iconElement = document.createElement('div');
            iconElement.innerHTML = iconHtml;
            messageContainer.appendChild(iconElement);
        }
        
        const messageText = document.createElement('div');
        messageText.style.flex = '1';
        
        if (typeof message === 'object' && message.nodeType) {
            messageText.appendChild(message);
        } else {
            messageText.textContent = message;
        }
        
        messageContainer.appendChild(messageText);
        modal.appendChild(messageContainer);
        
        const buttonsContainer = document.createElement('div');
        buttonsContainer.className = 'imgui-modal-buttons';
        buttonsContainer.style.cssText = `
            display: flex;
            justify-content: flex-end;
            gap: 8px;
            margin-top: 16px;
        `;
        
        const closeModal = () => {
            modal.style.transform = 'translateY(-20px)';
            modal.style.opacity = '0';
            backdrop.style.opacity = '0';
            
            setTimeout(() => {
                document.body.removeChild(backdrop);
            }, 300);
        };
        
        const buttonsList = Array.isArray(config.buttons) ? config.buttons : [config.buttons];
        
        buttonsList.forEach((buttonConfig) => {
            const isObject = typeof buttonConfig === 'object';
            const buttonText = isObject ? buttonConfig.text : buttonConfig;
            const isPrimary = isObject ? buttonConfig.primary : false;
            const callback = isObject ? buttonConfig.callback : null;
            
            const button = document.createElement('button');
            button.textContent = buttonText;
            button.className = isPrimary ? 'imgui-button imgui-primary-button' : 'imgui-button';
            
            if (isPrimary) {
                button.style.background = 'var(--imgui-accent)';
                button.style.color = '#ffffff';
                button.style.borderColor = 'var(--imgui-accent)';
            }
            
            button.addEventListener('click', () => {
                if (callback) callback();
                closeModal();
            });
            
            buttonsContainer.appendChild(button);
        });
        
        modal.appendChild(buttonsContainer);
        backdrop.appendChild(modal);
        
        if (config.closeOnBackdropClick) {
            backdrop.addEventListener('click', (e) => {
                if (e.target === backdrop) {
                    closeModal();
                }
            });
        }
        
        const escHandler = (e) => {
            if (e.key === 'Escape') {
                closeModal();
                document.removeEventListener('keydown', escHandler);
            }
        };
        document.addEventListener('keydown', escHandler);
    
        document.body.appendChild(backdrop);
        
        setTimeout(() => {
            backdrop.style.opacity = '1';
            modal.style.transform = 'translateY(0)';
            modal.style.opacity = '1';
        }, 10);
        
        return {
            close: closeModal,
            element: modal,
            backdrop: backdrop
        };
    }

    // New methods for better theming and layout
    SetTheme(themeName) {
        if (this.themes[themeName]) {
            this.options.theme = themeName;
            this.theme = this.themes[themeName];

            this._updateCSSVariables();
            this._applyThemeToElements();
        }
        return this;
    }

    _applyThemeToElements() {
        // Update container
        this.container.style.background = this.theme.background;
        this.container.style.color = this.theme.text;
        this.container.style.borderColor = this.theme.border;
        
        // Update all controls
        this.container.querySelectorAll('.imgui-button').forEach(el => {
            el.style.background = this.theme.buttonBg;
            el.style.color = this.theme.text;
            el.style.borderColor = this.theme.border;
        });
        
        this.container.querySelectorAll('.imgui-input').forEach(el => {
            el.style.background = this.theme.inputBg;
            el.style.color = this.theme.text;
            el.style.borderColor = this.theme.border;
        });
        
        this.container.querySelectorAll('.imgui-section').forEach(el => {
            // Shitty hack to make the section header text color change for sections
            // that are not collapsible
            el.querySelectorAll('.imgui-section-header').forEach(h => { h.style.color = this.theme.text;});
            el.style.background = this.theme.sectionBg;
            el.style.borderColor = this.theme.border;
        });

        // this.container.querySelectorAll('.imgui-progressbar').forEach(el => {
        //     // el.style.background = this.theme.inputBg;
        //     // el.style.borderColor = this.theme.border;
        // });
        
        // Update text elements
        this.container.querySelectorAll('label, h1, h2, h3, h4, h5, h6, span').forEach(el => {
            el.style.color = this.theme.text;
        });
        
        return this;
    }
}