您需要先安装一个扩展,例如 篡改猴、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/1587850/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 = {}) { // Default theme and styling options this.options = { theme: 'light', position: 'right', width: 300, draggable: true, ...options }; // Initialize themes 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' } }; // Get current theme colors 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: 6px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; font-size: 14px; z-index: 9999; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); padding: 12px; max-height: 85vh; overflow-y: auto; overflow-x: hidden; transition: all 0.2s ease; `; // Apply global styles if not already present this._applyGlobalStyles(); // Track current section for controls to be added to this.currentSection = null; // Track indentation level 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() { // Initialize dragging state let isDragging = false; let startX, startY, startLeft, startTop; // Function to check if clicked element is a control or child of a control const isClickOnControl = (element) => { if (!element) return false; // Check if the element or any of its parents have control class or is an input, button, etc. 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; }; // Add mousedown event to start dragging if not clicking on a control this.container.addEventListener('mousedown', (e) => { if (e.button !== 0) return; // Only left mouse button // Don't start dragging if clicking on a control if (isClickOnControl(e.target)) { return; } isDragging = true; // Get initial positions startX = e.clientX; startY = e.clientY; // Get current container position const rect = this.container.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; // Change cursor style document.body.style.cursor = 'move'; // Prevent text selection during drag e.preventDefault(); }); // Add mousemove event to handle dragging document.addEventListener('mousemove', (e) => { if (!isDragging) return; // Calculate new position const dx = e.clientX - startX; const dy = e.clientY - startY; // Update container position const newLeft = startLeft + dx; const newTop = startTop + dy; // Set position directly using left/top for better positioning this.container.style.left = `${newLeft}px`; this.container.style.top = `${newTop}px`; this.container.style.right = 'auto'; // Clear the right position when dragging }); // Add mouseup event to stop dragging document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; document.body.style.cursor = ''; // Ensure the panel doesn't go off-screen this._keepInView(); } }); // Stop dragging if mouse leaves the window document.addEventListener('mouseleave', () => { if (isDragging) { isDragging = false; document.body.style.cursor = ''; } }); } _keepInView() { // Get the current position and dimensions const rect = this.container.getBoundingClientRect(); const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; // Make sure at least 50px of the panel is visible on all sides const minVisiblePx = 50; let newLeft = rect.left; let newTop = rect.top; // Check horizontal position if (rect.right < minVisiblePx) { newLeft = minVisiblePx - rect.width; } else if (rect.left > windowWidth - minVisiblePx) { newLeft = windowWidth - minVisiblePx; } // Check vertical position if (rect.bottom < minVisiblePx) { newTop = minVisiblePx - rect.height; } else if (rect.top > windowHeight - minVisiblePx) { newTop = windowHeight - minVisiblePx; } // Apply the adjusted position if (newLeft !== rect.left || newTop !== rect.top) { this.container.style.left = `${newLeft}px`; this.container.style.top = `${newTop}px`; } } // Section management BeginSection(title) { 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'; 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() { return this.currentSection || this.container; } // Original API methods with improved implementation GetControlContainer() { return this.container; } GetControls() { return this.container.querySelectorAll('.imgui-control'); } AddHeader(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; } AddLabelControlPair(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; } AddButton(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; } AddTextbox(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; } AddSeparator() { 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; } AddLabel(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; } AddColorPicker(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; } AddDatePicker(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; } AddNumberInput(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; } AddSlider(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; } AddCheckbox(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; } // 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; } }