您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
An IMGUI inspired GUI Framework for javascript thats designed to be as simple to use as IMGUI.
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/535798/1594266/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: '#151617', text: '#eef0f2', border: '#425069', accent: '#294a7a', buttonBg: '#274972', buttonHover: '#336caf', inputBg: '#20324d', 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.3s 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); height: var(--imgui-title-height); display: flex; justify-content: space-between; align-items: center; padding-left: ${leftAlign ? '12px' : '0'}; padding-right: 8px; `; // Create a wrapper for the title const titleWrapper = document.createElement('div'); titleWrapper.style.cssText = ` ${!leftAlign ? 'flex: 1; text-align: center;' : ''} margin-bottom: 5px; `; titleWrapper.textContent = this.options.title || 'ImmediateGUI'; // Create minimize button const minimizeBtn = document.createElement('button'); minimizeBtn.className = 'imgui-minimize-btn'; minimizeBtn.innerHTML = '▼'; // Down arrow character (▼) minimizeBtn.title = "Minimize"; minimizeBtn.style.cssText = ` background: none; border: none; color: var(--imgui-text); font-size: 12px; cursor: pointer; padding: 4px 8px; margin-left: 10px; font-family: monospace; `; minimizeBtn.addEventListener('mouseenter', () => { // minimizeBtn.style.opacity = '1'; }); minimizeBtn.addEventListener('mouseleave', () => { // minimizeBtn.style.opacity = '0.7'; }); // Add click handler for minimizing this.isMinimized = false; minimizeBtn.addEventListener('click', (e) => { e.stopPropagation(); // Prevent triggering dragging this._toggleMinimize(); }); this.minimizeBtn = minimizeBtn; // Add elements to titlebar this.titleBar.appendChild(titleWrapper); this.titleBar.appendChild(minimizeBtn); this.container.appendChild(this.titleBar); } _toggleMinimize(forceState = null) { this.isMinimized = !forceState ? !this.isMinimized : (typeof forceState === 'boolean' ? forceState : !this.isMinimized); if (this.isMinimized) { // Minimize the GUI this.contentContainer.style.display = 'none'; this.container.style.maxHeight = 'var(--imgui-title-height)'; this.container.style.top = 'auto'; this.container.style.bottom = '10px'; this.minimizeBtn.innerHTML = '▲'; // Up arrow character (▲) this.minimizeBtn.title = "Restore"; this.titleBar.style.marginBottom = '0'; this.titleBar.style.borderBottom = 'none'; this.container.style.overflowY = 'hidden'; } else { // Restore the GUI this.contentContainer.style.display = 'block'; this.container.style.maxHeight = this.maxHeight; this.container.style.bottom = 'auto'; this.container.style.top = '10px'; this.minimizeBtn.innerHTML = '▼'; // Down arrow character (▼) this.minimizeBtn.title = "Minimize"; this.titleBar.style.marginBottom = '12px'; this.titleBar.style.borderBottom = '1px solid var(--imgui-border)'; this.container.style.overflowY = 'auto'; } // Make sure the GUI stays in view this._keepInView(); } _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-toolbar-height: 40px; --imgui-bottom-padding: 3px; --imgui-title-height: 30px; --imgui-scrollbar-width: 0.5em; } #${this.container.id} { /* CSS Reset for all controls inside our GUI */ &::*, & *::before, & *::after { box-sizing: border-box; margin: 0; } &::* { margin: 0; padding: 0px; outline: none; -webkit-font-smoothing: antialiased; } &::input, &::button, &::textarea, &::select { font: inherit; } &::p, &::h1, &::h2, &::h3, &::h4, &::h5, &::h6 { overflow-wrap: break-word; } &::p { text-wrap: pretty; } &::h1, &::h2, &::h3, &::h4, &::h5, &::h6 { text-wrap: balance; } &::#root, &::#__next { isolation: isolate; } /* End of CSS Reset */ scrollbar-gutter: auto; /* scrollbar-color: var(--imgui-accent); */ /* scrollbar-width: thin; */ &::-webkit-scrollbar { width: var(--imgui-scrollbar-width); background-color: var(--imgui-section-bg); } &::-webkit-scrollbar-thumb { background-color: var(--imgui-accent); /* border-radius: 3px; */ } &::-webkit-scrollbar-button:start:decrement { height: calc(var(--imgui-title-height) + ${this.toolbar ? 'var(--imgui-toolbar-height)' : '0px'}); display: block; background-color: transparent; } } .imgui-titlebar { width: 100% !important; box-sizing: border-box; right: 0; left: 0; } .imgui-progressbar { } .imgui-image { } .imgui-slider { accent-color: var(--imgui-accent); } .imgui-slider:hover { accent-color: var(--imgui-button-hover); } .imgui-checkbox { } .imgui-radiobutton { } .imgui-control { /* margin-bottom: 2px; */ width: 100%; } .imgui-wrapper { 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: calc(var(--imgui-bottom-padding) * 2); 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, tooltip = '') { const section = document.createElement('div'); section.className = 'imgui-section'; section.id = ImmediateGUI.GenerateId('section_'); section.style.paddingBottom = 'var(--imgui-bottom-padding)'; if (typeof tooltip === 'string' && tooltip.length > 0) section.title = tooltip; 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 collapseCharacter = '▼'; const uncollapseCharacter = '►'; const indicator = document.createElement('span'); indicator.className = 'imgui-section-indicator'; indicator.textContent = collapseCharacter; indicator.style.cssText = ` margin-right: 8px; font-size: 10px; font-family: monospace; 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 = uncollapseCharacter; indicator.style.transform = 'rotate(0deg)'; } else { content.style.maxHeight = '2000px'; indicator.textContent = collapseCharacter; 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; } // Row management BeginRow(gap = 2) { const row = document.createElement('div'); row.className = 'imgui-row'; row.id = ImmediateGUI.GenerateId('row_'); row.style.cssText = ` display: flex; flex-direction: row; align-items: flex-start; justify-content: center; ${gap > 0 ? `gap: ${gap}px;` : 'gap: var(--imgui-bottom-padding);'} width: 100%; `; this._getTargetContainer().appendChild(row); this.currentRow = row; return this; } EndRow() { // Post processing for items in the row if (this.currentRow) { this.currentRow.querySelectorAll('.imgui-wrapper').forEach((wrapper) => { wrapper.style.flex = '1'; wrapper.style.marginRight = '0px !important'; wrapper.querySelectorAll('.imgui-control').forEach((control) => { control.style.width = '100%'; }); }); } this.currentRow = 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; // TODO: make sure element with its new margin left wont exceed past the bounds of the container element.style.marginLeft = `${currentIndent}px`; } return element; } // Tabs management BeginTabs(tabs = [], defaultTab = 0) { const wrapper = document.createElement('div'); wrapper.className = "imgui-control imgui-wrapper imgui-tabs"; wrapper.style.cssText = ` display: flex; flex-direction: column; margin-bottom: var(--imgui-bottom-padding); border: 1px solid var(--imgui-border); border-radius: 4px; background: var(--imgui-section-bg); `; // Create tab headers const tabHeaders = document.createElement('div'); tabHeaders.className = "imgui-tab-headers"; tabHeaders.style.cssText = ` display: flex; border-bottom: 1px solid var(--imgui-border); background: var(--imgui-input-bg); `; // Create tab contents const tabContents = document.createElement('div'); tabContents.className = "imgui-tab-contents"; tabContents.style.cssText = ` padding: 10px; padding-bottom: calc(10px - var(--imgui-bottom-padding)); `; this.tabPanes = []; let activeTab = defaultTab; tabs.forEach((tab, index) => { // Create tab header const tabHeader = document.createElement('div'); tabHeader.textContent = tab; tabHeader.style.cssText = ` padding: 10px 16px; cursor: pointer; border-right: 1px solid var(--imgui-border); /* border-radius: 5px 5px 0px 0px; -webkit-border-radius: 5px 5px 0px 0px; -moz-border-radius: 5px 5px 0px 0px; */ background: ${index === activeTab ? 'var(--imgui-accent)' : 'transparent'}; color: var(--imgui-text); transition: background 0.2s ease; `; // Create tab content pane const tabPane = document.createElement('div'); tabPane.className = "imgui-tab-pane"; tabPane.style.display = index === activeTab ? 'block' : 'none'; tabPane.tabName = tabHeader.textContent; tabHeader.addEventListener('click', () => { // Hide all panes this.tabPanes.forEach(pane => pane.style.display = 'none'); // Show selected pane tabPane.style.display = 'block'; // Update header styles tabHeaders.querySelectorAll('div').forEach(header => { header.style.background = 'transparent'; }); tabHeader.style.background = 'var(--imgui-accent)'; activeTab = index; }); tabHeaders.appendChild(tabHeader); tabContents.appendChild(tabPane); this.tabPanes.push(tabPane); }); wrapper.appendChild(tabHeaders); wrapper.appendChild(tabContents); this._getTargetContainer().appendChild(wrapper); this.currentTab = { wrapper, panes: this.tabPanes, activeTab, currentTabIndex: 0 // Track which tab we're currently adding content to }; // TODO: set this.currentSection to tab with the index in the // 'defaultTab' function parameter, this means validating the value of // 'defaultTab' also this.currentSection = this.tabPanes[0]; // Start with first tab return this; } SetActiveTab(tabIndexOrTabName) { const tabIndexIsNumber = typeof tabIndexOrTabName === 'number'; if (!tabIndexIsNumber) { const tabs = this.tabPanes; tabIndexOrTabName = tabs.findIndex(tab => tab.tabName === tabIndexOrTabName); if (tabIndexOrTabName === -1) { console.error(`ImmedaiteGUI: Invalid tab name specified: ${tabIndexOrTabName}`); return this; } } if (this.currentTab && tabIndexOrTabName >= 0 && tabIndexOrTabName < this.currentTab.panes.length) { this.currentTab.currentTabIndex = tabIndexOrTabName; this.currentSection = this.currentTab.panes[tabIndexOrTabName]; } else { debugger; console.error(`ImmedaiteGUI: Invalid tab index specified: ${tabIndexOrTabName}`); } return this; } EndTabs() { this.currentTab = null; this.currentSection = null; return this; } // Utility to get current target container _getTargetContainer() { if (this.currentRow) { return this.currentRow; } if (this.currentTab) { // Use the tab we're currently building, not the active displayed tab return this.currentTab.panes[this.currentTab.currentTabIndex]; } 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; } // 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`; } }); textarea.addEventListener('mouseenter', () => { textarea.style.borderColor = 'var(--imgui-border)'; }); 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 let 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.value = normalizedValue; progressBar.max = max; progressBar.min = min; // progressBar = new Proxy(progressBar, { // set(obj, prop, value) { // if (prop === "value") { // const normalizedNewValue = Math.min(Math.max(value, min), max); // const newPercentage = ((normalizedNewValue - min) / (max - min)) * 100; // value = normalizedNewValue; // obj.style.width = `${newPercentage}%`; // return Reflect.set(...arguments); // } // return Reflect.set(...arguments); // }, // }); 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}%`; progressBar.value = normalizedNewValue; if (textElement) { textElement.textContent = `${Math.round(newPercentage)}%`; } }; progressBar.getValue = () => progressBar.value; // 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.className = "imgui-checkbox"; 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; } ToggleSwitch(label, checked = false, tooltip = '', onChangeCallback = null) { 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); `; // Hidden input for form compatibility const hiddenInput = document.createElement('input'); hiddenInput.id = ImmediateGUI.GenerateId("ctrl_"); hiddenInput.type = 'checkbox'; hiddenInput.checked = checked; hiddenInput.style.display = 'none'; hiddenInput.className = "imgui-toggle-input"; // Create the visual toggle switch const toggleTrack = document.createElement('div'); toggleTrack.className = "imgui-toggle-track"; toggleTrack.style.cssText = ` width: 48px; height: 24px; background: ${checked ? 'var(--imgui-accent)' : 'var(--imgui-border)'}; border-radius: 12px; position: relative; cursor: pointer; transition: background-color 0.3s ease; margin-right: 12px; border: 1px solid var(--imgui-border); box-sizing: border-box; `; const toggleThumb = document.createElement('div'); toggleThumb.className = "imgui-toggle-thumb"; toggleThumb.style.cssText = ` width: 20px; height: 20px; background: var(--imgui-text); border-radius: 50%; position: absolute; top: 1px; left: ${checked ? '25px' : '1px'}; transition: left 0.3s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); `; // Label element const labelElem = document.createElement('label'); labelElem.textContent = label; labelElem.htmlFor = hiddenInput.id; labelElem.style.cssText = ` cursor: pointer; color: var(--imgui-text); font-family: inherit; user-select: none; flex: 1; `; if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) { wrapper.title = tooltip; } // Add CSS for hover effects if not already added if (!document.getElementById('imgui-toggle-styles')) { const toggleStyles = document.createElement('style'); toggleStyles.id = 'imgui-toggle-styles'; toggleStyles.textContent = ` .imgui-toggle-track:hover { box-shadow: 0 0 0 2px rgba(var(--imgui-accent-rgb, 41, 74, 122), 0.2); } .imgui-toggle-track:active .imgui-toggle-thumb { transform: scale(0.95); } `; document.head.appendChild(toggleStyles); } // Toggle functionality const toggle = () => { const newChecked = !hiddenInput.checked; hiddenInput.checked = newChecked; // Update visual state if (newChecked) { toggleTrack.style.background = 'var(--imgui-accent)'; toggleThumb.style.left = '25px'; } else { toggleTrack.style.background = 'var(--imgui-border)'; toggleThumb.style.left = '1px'; } // Dispatch change event hiddenInput.dispatchEvent(new Event('change', { bubbles: true })); }; // Event listeners toggleTrack.addEventListener('click', toggle); labelElem.addEventListener('click', (e) => { e.preventDefault(); // Prevent double toggle toggle(); }); // Keyboard accessibility toggleTrack.setAttribute('tabindex', '0'); toggleTrack.setAttribute('role', 'switch'); toggleTrack.setAttribute('aria-checked', checked); toggleTrack.setAttribute('aria-labelledby', hiddenInput.id + '_label'); labelElem.id = hiddenInput.id + '_label'; toggleTrack.addEventListener('keydown', (e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); toggle(); } }); // Update aria-checked when state changes hiddenInput.addEventListener('change', () => { toggleTrack.setAttribute('aria-checked', hiddenInput.checked); if (onChangeCallback && typeof onChangeCallback === 'function') { onChangeCallback(hiddenInput.checked); } }); // Add hover effects toggleTrack.addEventListener('mouseenter', () => { if (!hiddenInput.checked) { toggleTrack.style.background = 'var(--imgui-button-hover)'; } }); toggleTrack.addEventListener('mouseleave', () => { if (!hiddenInput.checked) { toggleTrack.style.background = 'var(--imgui-border)'; } }); // Focus styles toggleTrack.addEventListener('focus', () => { toggleTrack.style.outline = '2px solid var(--imgui-accent)'; toggleTrack.style.outlineOffset = '2px'; }); toggleTrack.addEventListener('blur', () => { toggleTrack.style.outline = 'none'; }); // Build the component toggleTrack.appendChild(toggleThumb); wrapper.appendChild(hiddenInput); wrapper.appendChild(toggleTrack); wrapper.appendChild(labelElem); // Add helper methods to the hidden input for API consistency hiddenInput.toggle = toggle; hiddenInput.setChecked = (value) => { if (value !== hiddenInput.checked) { toggle(); } }; hiddenInput.label = labelElem; hiddenInput.track = toggleTrack; hiddenInput.thumb = toggleThumb; this._applyIndentation(wrapper); this._getTargetContainer().appendChild(wrapper); return hiddenInput; } 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.className = "imgui-radiobutton"; 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; } Image(src, alt = '', width = 'auto', height = 'auto', tooltip = '') { const wrapper = document.createElement('div'); wrapper.className = "imgui-control imgui-wrapper"; wrapper.style.cssText = ` display: flex; justify-content: center; margin-bottom: var(--imgui-bottom-padding); `; const img = document.createElement('img'); img.id = ImmediateGUI.GenerateId("ctrl_"); img.className = 'imgui-image'; img.src = src; img.alt = alt; img.className = "imgui-image imgui-control"; if (tooltip) img.title = tooltip; img.style.cssText = ` max-width: 100%; width: ${width}; height: ${height}; max-width: ${this.options.width}px; border-radius: 4px; border: 1px solid var(--imgui-border); `; wrapper.appendChild(img); this._applyIndentation(wrapper); this._getTargetContainer().appendChild(wrapper); return img; } 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.currentRow) { console.warn("ImmediateGUI: Show() called while in row mode. Did you forget to call EndRow() 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.75); 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 = 'var(--imgui-text)'; button.style.borderColor = 'var(--imgui-border)'; button.style.fontWeight = 'bold'; button.addEventListener('mouseenter', () => { button.style.background = 'var(--imgui-button-hover)'; }); button.addEventListener('mouseout', () => { button.style.background = '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; }); this.container.querySelectorAll('.imgui-toggle-input').forEach(toggle => { const track = toggle.track; const thumb = toggle.thumb; if (track && thumb) { // Update track color based on state if (toggle.checked) { track.style.background = 'var(--imgui-accent)'; } else { track.style.background = 'var(--imgui-border)'; } // Update thumb color thumb.style.background = 'var(--imgui-text)'; // Update border track.style.borderColor = 'var(--imgui-border)'; } }); return this; } }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址