您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
An IMGUI inspired GUI Framework for javascript thats designed to be as simple to use as IMGUI.
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/535798/1590637/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: 'light', position: 'right', width: 300, draggable: true, ...options }; this.themes = { light: { background: '#ffffff', text: '#333333', border: '#cccccc', accent: '#4285f4', buttonBg: '#f5f5f5', buttonHover: '#e0e0e0', inputBg: '#ffffff', sectionBg: '#f9f9f9' }, dark: { background: '#2d2d2d', text: '#e0e0e0', border: '#555555', accent: '#4d90fe', buttonBg: '#444444', buttonHover: '#555555', inputBg: '#3d3d3d', sectionBg: '#333333' } }; this.maxHeight = '85vh'; this.theme = this.themes[this.options.theme] || this.themes.light; // Create main container this.container = document.createElement('div'); this.container.id = ImmediateGUI.GenerateId(); this.container.style.cssText = ` position: fixed; ${this.options.position === 'right' ? 'right' : 'left'}: 10px; top: 10px; width: ${this.options.width}px; background: ${this.theme.background}; color: ${this.theme.text}; border: 1px solid ${this.theme.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: 12px; max-height: ${this.maxHeight}; overflow-y: auto; overflow-x: hidden; transition: all 0.2s ease; `; this._applyGlobalStyles(); this.currentSection = null; this.indentationLevel = 0; this.indentationSize = 10; // pixels per level this.isCustomIndentationLevel = false; if (this.options.draggable) { this._setupDragging(); } } _applyGlobalStyles() { if (!document.getElementById('imgui-global-styles')) { const styleEl = document.createElement('style'); styleEl.id = 'imgui-global-styles'; styleEl.textContent = ` .imgui-control { margin-bottom: 2px; width: 100%; box-sizing: border-box; } .imgui-button { background: ${this.theme.buttonBg}; color: ${this.theme.text}; border: 1px solid ${this.theme.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: ${this.theme.buttonHover}; } .imgui-button:active { transform: translateY(1px); } .imgui-input { background: ${this.theme.inputBg}; color: ${this.theme.text}; border: 1px solid ${this.theme.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:focus { border-color: ${this.theme.accent}; } .imgui-section { border: 1px solid ${this.theme.border}; border-radius: 4px; padding: 10px; margin-bottom: 12px; background: ${this.theme.sectionBg}; } .imgui-section-header { font-weight: 600; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid ${this.theme.border}; color: ${this.theme.text}; } .imgui-label { display: block; margin-bottom: 4px; color: ${this.theme.text}; font-weight: 500; } `; document.head.appendChild(styleEl); } } _setupDragging() { let isDragging = false; let startX, startY, startLeft, startTop; const isClickOnControl = (element) => { if (!element) return false; 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_'); if (title) { const header = document.createElement('div'); header.className = 'imgui-section-header'; if (collapsible) { header.style.cssText = ` display: flex; align-items: center; cursor: pointer; user-select: none; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid ${this.theme.border}; color: ${this.theme.text}; `; 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.container.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; } // Original API methods with improved implementation GetControlContainer() { return this.container; } GetControls() { return this.container.querySelectorAll('.imgui-control'); } Separator() { 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%; `; this._applyIndentation(separator); this._getTargetContainer().appendChild(separator); return separator; } Header(text, level = 1) { 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; `; this._applyIndentation(header); this._getTargetContainer().appendChild(header); return header; } LabelControlPair(text, controlGeneratorFunction) { const wrapper = document.createElement("div"); wrapper.className = "imgui-control"; wrapper.style.cssText = ` display: flex; flex-direction: column; margin-bottom: 10px; `; const label = document.createElement('label'); label.id = ImmediateGUI.GenerateId("ctrl_"); label.textContent = text; label.className = "imgui-label"; const control = typeof controlGeneratorFunction === "function" ? controlGeneratorFunction() : controlGeneratorFunction; control.label = label; wrapper.appendChild(label); wrapper.appendChild(control); this._applyIndentation(wrapper); this._getTargetContainer().appendChild(wrapper); return control; } Button(text, callback) { const btn = document.createElement('button'); btn.id = ImmediateGUI.GenerateId("ctrl_"); btn.textContent = text; btn.className = "imgui-button imgui-control"; if (callback && typeof callback === 'function') { btn.addEventListener('click', callback); } this._applyIndentation(btn); this._getTargetContainer().appendChild(btn); return btn; } Textbox(placeholder, defaultValue = "") { const input = document.createElement('input'); input.id = ImmediateGUI.GenerateId("ctrl_"); input.type = 'text'; input.placeholder = placeholder; input.value = defaultValue; input.className = "imgui-input imgui-control"; this._applyIndentation(input); this._getTargetContainer().appendChild(input); return input; } TextArea(placeholder = "", defaultValue = "", rows = 4) { 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 = ` resize: vertical; min-height: ${rows * 20}px; font-family: inherit; margin-bottom: 10px; max-height: calc(${this.maxHeight} - 50px); /* Limit max height to prevent overflowing */ `; 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`; } }); this._applyIndentation(textarea); this._getTargetContainer().appendChild(textarea); return textarea; } Label(text) { const label = document.createElement('label'); label.id = ImmediateGUI.GenerateId("ctrl_"); label.textContent = text; label.className = "imgui-label imgui-control"; this._applyIndentation(label); this._getTargetContainer().appendChild(label); return label; } ProgressBar(value = 0, min = 0, max = 100, showText = true) { // Create wrapper element const wrapper = document.createElement('div'); wrapper.className = "imgui-control"; wrapper.style.cssText = ` display: flex; flex-direction: column; width: 100%; margin-bottom: 10px; `; // Create progress container const progressContainer = document.createElement('div'); progressContainer.style.cssText = ` width: 100%; height: 20px; background: ${this.theme.inputBg}; border: 1px solid ${this.theme.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"; // 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: ${this.theme.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: ${this.theme.text}; font-size: 12px; font-weight: bold; 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') { const wrapper = document.createElement('div'); wrapper.className = "imgui-control"; wrapper.style.cssText = `display: flex; align-items: center;`; const colorPicker = document.createElement('input'); colorPicker.id = ImmediateGUI.GenerateId("ctrl_"); colorPicker.type = 'color'; colorPicker.value = defaultValue; colorPicker.style.cssText = ` margin-right: 8px; width: 40px; height: 40px; border: 1px solid ${this.theme.border}; border-radius: 4px; background: none; cursor: pointer; `; const colorValue = document.createElement('span'); colorValue.textContent = defaultValue; colorValue.style.cssText = ` font-family: monospace; color: ${this.theme.text}; `; 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]) { const datePicker = document.createElement('input'); datePicker.id = ImmediateGUI.GenerateId("ctrl_"); datePicker.type = 'date'; datePicker.value = defaultValue; datePicker.className = "imgui-input imgui-control"; this._applyIndentation(datePicker); this._getTargetContainer().appendChild(datePicker); return datePicker; } Dropdown(options = [], defaultValue = null) { const wrapper = document.createElement('div'); wrapper.className = "imgui-control"; wrapper.style.cssText = ` display: flex; flex-direction: column; margin-bottom: 10px; `; 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 ${this.theme.border}; border-radius: 4px; background: ${this.theme.inputBg}; color: ${this.theme.text}; font-family: inherit; cursor: pointer; appearance: auto; `; // Add options to the select element options.forEach(option => { const optElement = document.createElement('option'); // 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) { const wrapper = document.createElement('div'); wrapper.className = "imgui-control"; wrapper.style.cssText = `display: flex; align-items: center;`; const labelElem = document.createElement('label'); labelElem.textContent = label; labelElem.style.cssText = ` margin-right: 10px; flex: 1; color: ${this.theme.text}; `; const input = document.createElement('input'); input.label = labelElem; input.id = ImmediateGUI.GenerateId("ctrl_"); input.type = 'number'; input.value = defaultValue; 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.onkeyup = (e) => { // const currentValue = parseInt(input.value); // if (isNaN(currentValue)) { // input.value = Math.floor(min); // return; // } // // Clamp input.value to a value between input.min and input.max // if (min !== null && currentValue < min) { // input.value = Math.floor(min); // } else if (max !== null && currentValue > max) { // input.value = Math.floor(max); // } // }; // } input.style.cssText = ` width: 80px; padding: 6px; border: 1px solid ${this.theme.border}; border-radius: 4px; background: ${this.theme.inputBg}; color: ${this.theme.text}; font-family: inherit; `; wrapper.appendChild(labelElem); wrapper.appendChild(input); //this._applyIndentation(wrapper); this._applyIndentation(labelElem); this._getTargetContainer().appendChild(wrapper); return input; } Slider(minValue = 0, maxValue = 100, defaultValue = 50) { const wrapper = document.createElement('div'); wrapper.className = "imgui-control"; wrapper.style.cssText = `display: flex; flex-direction: column;`; const sliderContainer = document.createElement('div'); sliderContainer.style.cssText = `display: flex; align-items: center; width: 100%;`; const slider = document.createElement('input'); 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: ${this.theme.accent}; `; const valueDisplay = document.createElement('span'); valueDisplay.textContent = defaultValue; valueDisplay.style.cssText = ` min-width: 40px; text-align: right; color: ${this.theme.text}; font-family: inherit; `; 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) { const wrapper = document.createElement('div'); wrapper.className = "imgui-control"; wrapper.style.cssText = `display: flex; align-items: center;`; const checkbox = document.createElement('input'); checkbox.id = ImmediateGUI.GenerateId("ctrl_"); checkbox.type = 'checkbox'; checkbox.checked = checked; checkbox.style.cssText = ` margin-right: 8px; accent-color: ${this.theme.accent}; width: 16px; height: 16px; `; const labelElem = document.createElement('label'); labelElem.textContent = label; labelElem.htmlFor = checkbox.id; labelElem.style.cssText = ` cursor: pointer; color: ${this.theme.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; } Show() { if (this.container.children.length === 0) return this; 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' 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: ${this.theme.background}; border: 1px solid ${this.theme.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: ${this.theme.text}; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid ${this.theme.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: ${this.theme.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 = this.theme.accent; button.style.color = '#ffffff'; button.style.borderColor = this.theme.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._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 => { el.style.background = this.theme.sectionBg; 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; } }