Tento skript by nemal byť nainštalovaný priamo. Je to knižnica pre ďalšie skripty, ktorú by mali používať cez meta príkaz // @require https://update.greasyfork.org/scripts/535798/1591785/ImmediateGUI.js
class ImmediateGUI {
static OccupiedElementIds = [];
static GenerateId(prefix = "gui_") {
if (typeof prefix !== 'string' || prefix.length === 0) {
prefix = "gui_";
}
const timestamp = Date.now().toString(36);
const randomPart = Math.random().toString(36).substring(2, 15);
const generatedId = prefix + timestamp + randomPart;
const exists = ImmediateGUI.OccupiedElementIds.includes(generatedId);
if (exists) return ImmediateGUI.GenerateId(prefix);
ImmediateGUI.OccupiedElementIds.push(generatedId);
return generatedId;
}
constructor(options = {}) {
this.options = {
theme: 'dark',
position: 'right',
width: 300,
draggable: true,
title: 'Immediate GUI',
titleLeftAligned: true,
...options
};
this.themes = {
light: {
background: '#ffffff',
text: '#333333',
border: '#cccccc',
accent: '#4285f4',
buttonBg: '#f5f5f5',
buttonHover: '#e0e0e0',
inputBg: '#ffffff',
sectionBg: '#f9f9f9'
},
dark: {
// background: '#2d2d2d',
background: '#151617',
// text: '#e0e0e0',
text: '#eef0f2',
// border: '#555555',
border: '#425069',
// accent: '#4d90fe',
accent: '#294a7a',
// buttonBg: '#444444',
buttonBg: '#274972',
// buttonHover: '#555555',
buttonHover: '#336caf',
// inputBg: '#3d3d3d',
inputBg: '#20324d',
// sectionBg: '#333333'
sectionBg: '#232426',
}
};
this.maxHeight = '85vh';
//this.maxHeight = 'auto;';
this.theme = this.themes[this.options.theme] || this.themes.light;
// Create main container
this.container = document.createElement('div');
this.container.id = ImmediateGUI.GenerateId();
this._applyGlobalStyles();
this._updateCSSVariables();
this.container.style.cssText = `
position: fixed;
${this.options.position === 'right' ? 'right' : 'left'}: 10px;
top: 10px;
min-width: ${(typeof this.options.width === 'string' ? this.options.width : this.options.width) + 'px'};
background: var(--imgui-bg);
color: var(--imgui-text);
border: 1px solid var(--imgui-border);
border-radius: 4px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
z-index: 9999;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 0; /* Remove all padding */
max-height: ${this.maxHeight};
overflow-y: auto;
overflow-x: hidden;
transition: all 0.2s ease;
`;
this._createTitleBar(this.options.titleLeftAligned);
this.contentContainer = document.createElement('div');
this.contentContainer.id = ImmediateGUI.GenerateId('content_');
this.contentContainer.style.cssText = `
width: 100%;
box-sizing: border-box;
padding: 0 12px 12px 12px; /* Add padding to content area only */
`;
this.container.appendChild(this.contentContainer);
this.currentSection = null;
this.indentationLevel = 0;
this.indentationSize = 10; // pixels per level
this.isCustomIndentationLevel = false;
if (this.options.draggable) {
this._setupDragging();
}
}
_createTitleBar(leftAlign = true) {
this.titleBar = document.createElement('div');
this.titleBar.className = 'imgui-titlebar';
this.titleBar.style.cssText = `
width: 100% !important;
padding-top: var(--imgui-bottom-padding);
background: var(--imgui-section-bg);
color: var(--imgui-text);
font-weight: bold;
cursor: ${this.options.draggable ? 'pointer' : 'default'};
user-select: none;
box-sizing: border-box;
position: sticky;
top: 0;
z-index: 999999999;
margin-bottom: 12px;
border-bottom: 1px solid var(--imgui-border);
${leftAlign ? 'text-align: left; padding-left: 12px;' : 'text-align: center;'}
height: var(--imgui-title-height);
`;
this.titleBar.textContent = this.options.title || 'ImmediateGUI';
this.container.appendChild(this.titleBar);
}
_updateCSSVariables() {
document.documentElement.style.setProperty('--imgui-bg', this.theme.background);
document.documentElement.style.setProperty('--imgui-text', this.theme.text);
document.documentElement.style.setProperty('--imgui-border', this.theme.border);
document.documentElement.style.setProperty('--imgui-accent', this.theme.accent);
document.documentElement.style.setProperty('--imgui-button-bg', this.theme.buttonBg);
document.documentElement.style.setProperty('--imgui-button-hover', this.theme.buttonHover);
document.documentElement.style.setProperty('--imgui-input-bg', this.theme.inputBg);
document.documentElement.style.setProperty('--imgui-section-bg', this.theme.sectionBg);
}
_applyGlobalStyles() {
const styleSheetId = `imgui-global-styles_${this.container.id}`;
if (!document.getElementById(styleSheetId)) {
const styleEl = document.createElement('style');
styleEl.id = styleSheetId;
styleEl.textContent = `
:root {
--imgui-bg: ${this.theme.background};
--imgui-text: ${this.theme.text};
--imgui-border: ${this.theme.border};
--imgui-accent: ${this.theme.accent};
--imgui-button-bg: ${this.theme.buttonBg};
--imgui-button-hover: ${this.theme.buttonHover};
--imgui-input-bg: ${this.theme.inputBg};
--imgui-section-bg: ${this.theme.sectionBg};
--imgui-bottom-padding: 3px;
--imgui-title-height: 27px;
--imgui-scrollbar-width: 0.5em;
}
#${this.container.id} {
scrollbar-gutter: auto;
/* scrollbar-color: var(--imgui-accent); */
/* scrollbar-width: thin; */
&::-webkit-scrollbar {
width: var(--imgui-scrollbar-width);
height: 0.5em;
background-color: var(--imgui-section-bg);
}
&::-webkit-scrollbar-thumb {
background-color: var(--imgui-accent);
/* border-radius: 3px; */
}
/* This positions the scrollbar to start below the title bar */
&::-webkit-scrollbar-button:start:decrement {
height: var(--imgui-title-height);
display: block;
background-color: transparent;
}
}
.imgui-titlebar {
width: 100% !important;
box-sizing: border-box;
right: 0;
left: 0;
}
.imgui-progressbar {
}
.imgui-slider {
accent-color: var(--imgui-accent);
}
.imgui-slider:hover {
accent-color: var(--imgui-button-hover);
}
.imgui-control {
margin-bottom: 2px;
width: 100%;
box-sizing: border-box;
}
.imgui-button {
background: var(--imgui-button-bg);
color: var(--imgui-text);
border: 1px solid var(--imgui-border);
border-radius: 4px;
padding: 8px 12px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
outline: none;
width: auto;
font-family: inherit;
margin-right: 5px;
}
.imgui-button:hover {
background: var(--imgui-button-hover);
}
.imgui-button:active {
transform: translateY(1px);
}
.imgui-input {
background: var(--imgui-input-bg);
color: var(--imgui-text);
border: 1px solid var(--imgui-border);
border-radius: 4px;
padding: 8px 10px;
font-size: 14px;
width: 100%;
box-sizing: border-box;
outline: none;
transition: border-color 0.2s ease;
font-family: inherit;
}
.imgui-input::placeholder {
color: var(--imgui-text);
opacity: 0.5;
}
.imgui-input:focus {
border-color: var(--imgui-accent);
}
.imgui-section {
border: 1px solid var(--imgui-border);
border-radius: 4px;
padding: 10px 10px 0px;
margin-bottom: 12px;
background: var(--imgui-section-bg);
}
.imgui-section-header {
font-weight: 600;
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid var(--imgui-border);
color: var(--imgui-text);
}
.imgui-label {
display: block;
margin-bottom: 4px;
color: var(--imgui-text);
font-weight: 500;
}
`;
document.head.appendChild(styleEl);
this.container.classList.add('imgui-container');
}
}
_setupDragging() {
let isDragging = false;
let startX, startY, startLeft, startTop;
const isClickOnControl = (element) => {
if (!element) return false;
return !element.classList.contains('imgui-titlebar');
let current = element;
while (current && current !== this.container) {
if (
/* current.classList.contains('imgui-control') || */
current.tagName === 'INPUT' ||
current.tagName === 'BUTTON' ||
current.tagName === 'SELECT' ||
current.tagName === 'TEXTAREA'
) {
return true;
}
current = current.parentElement;
}
return false;
};
this.container.addEventListener('mousedown', (e) => {
if (e.button !== 0) return;
if (isClickOnControl(e.target)) {
return;
}
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const rect = this.container.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
document.body.style.cursor = 'move';
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const newLeft = startLeft + dx;
const newTop = startTop + dy;
this.container.style.left = `${newLeft}px`;
this.container.style.top = `${newTop}px`;
this.container.style.right = 'auto';
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
document.body.style.cursor = '';
this._keepInView();
}
});
document.addEventListener('mouseleave', () => {
if (isDragging) {
isDragging = false;
document.body.style.cursor = '';
}
});
}
_keepInView() {
const rect = this.container.getBoundingClientRect();
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const minVisiblePx = 50;
let newLeft = rect.left;
let newTop = rect.top;
if (rect.right < minVisiblePx) {
newLeft = minVisiblePx - rect.width;
} else if (rect.left > windowWidth - minVisiblePx) {
newLeft = windowWidth - minVisiblePx;
}
if (rect.bottom < minVisiblePx) {
newTop = minVisiblePx - rect.height;
} else if (rect.top > windowHeight - minVisiblePx) {
newTop = windowHeight - minVisiblePx;
}
if (newLeft !== rect.left || newTop !== rect.top) {
this.container.style.left = `${newLeft}px`;
this.container.style.top = `${newTop}px`;
}
}
// Section management
BeginSection(title, collapsible = false, collapsedByDefault = false) {
const section = document.createElement('div');
section.className = 'imgui-section';
section.id = ImmediateGUI.GenerateId('section_');
section.style.paddingBottom = 'var(--imgui-bottom-padding)';
if (title) {
const header = document.createElement('div');
header.className = 'imgui-section-header';
header.style.color = `var(--imgui-text)`;
header.style.borderBottom = `1px solid var(--imgui-border)`;
if (collapsible) {
header.style.cssText = `
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
margin-bottom: 8px;
padding-bottom: 6px;
`;
const indicator = document.createElement('span');
indicator.className = 'imgui-section-indicator';
indicator.textContent = '▼';
indicator.style.cssText = `
margin-right: 8px;
font-size: 10px;
transition: transform 0.2s ease;
`;
const titleSpan = document.createElement('span');
titleSpan.textContent = title;
titleSpan.style.flex = '1';
const content = document.createElement('div');
content.className = 'imgui-section-content';
content.style.cssText = `
overflow: hidden;
transition: max-height 0.3s ease;
`;
section.isCollapsed = false;
const toggleCollapse = () => {
section.isCollapsed = !section.isCollapsed;
if (section.isCollapsed) {
content.style.maxHeight = '0px';
indicator.textContent = '►';
// TODO: figure out why the ► character looks squished and skinny??
//indicator.style.fontSize = '14px';
indicator.style.transform = 'rotate(0deg)';
} else {
content.style.maxHeight = '2000px';
indicator.textContent = '▼';
indicator.style.transform = 'rotate(0deg)';
}
};
if (collapsedByDefault) toggleCollapse();
header.addEventListener('click', toggleCollapse);
header.appendChild(indicator);
header.appendChild(titleSpan);
section.appendChild(header);
section.appendChild(content);
section.contentContainer = content;
}
else {
header.textContent = title;
section.appendChild(header);
}
}
this._getTargetContainer().appendChild(section);
this.currentSection = section;
return this;
}
EndSection() {
this.currentSection = null;
return this;
}
// Indentation management
BeginIndentation(level = -1) {
if (level === -1) this.indentationLevel++;
else {
this.isCustomIndentationLevel = true;
this.indentationLevel = level;
}
return this;
}
EndIndentation() {
if (this.indentationLevel > 0) {
if (this.isCustomIndentationLevel) {
this.indentationLevel = 0;
this.isCustomIndentationLevel = false;
}
else this.indentationLevel--;
}
return this;
}
_applyIndentation(element) {
if (this.indentationLevel > 0) {
const currentIndent = this.indentationLevel * this.indentationSize;
element.style.marginLeft = `${currentIndent}px`;
}
return element;
}
// Utility to get current target container
_getTargetContainer() {
// if (this.currentSection) {
// // If current section is collapsible, use its content container
// if (this.currentSection.contentContainer) {
// return this.currentSection.contentContainer;
// }
// return this.currentSection;
// }
// return this.container;
if (this.currentSection) {
// If current section is collapsible, use its content container
if (this.currentSection.contentContainer) {
return this.currentSection.contentContainer;
}
return this.currentSection;
}
return this.contentContainer; // Changed from this.container
}
// Original API methods with improved implementation
GetControlContainer() {
return this.container;
}
GetControls() {
return this.GetControlContainer().querySelectorAll('.imgui-wrapper');
}
Separator() {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `
display: flex;
width: 100%;
margin-bottom: var(--imgui-bottom-padding);
`;
const separator = document.createElement('hr');
separator.id = ImmediateGUI.GenerateId("ctrl_");
separator.style.cssText = `
border: none;
border-top: 1px solid ${this.theme.border};
margin: 10px 0;
width: 100%;
`;
wrapper.appendChild(separator);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return separator;
}
Header(text, level = 1) {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `
display: flex;
width: 100%;
margin-bottom: var(--imgui-bottom-padding);
`;
const validLevel = Math.min(Math.max(level, 1), 6);
const header = document.createElement(`h${validLevel}`);
header.id = ImmediateGUI.GenerateId("ctrl_");
header.textContent = text;
header.style.cssText = `
margin: 0 0 10px 0;
padding: 0;
font-weight: ${validLevel <= 2 ? 'bold' : '600'};
color: ${this.theme.text};
font-size: ${24 - (validLevel * 2)}px;
font-family: inherit;
width: 100%;
`;
wrapper.appendChild(header);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return header;
}
Button(text, callback, tooltip = '') {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `
display: flex;
margin-bottom: var(--imgui-bottom-padding);
`;
const btn = document.createElement('button');
btn.id = ImmediateGUI.GenerateId("ctrl_");
btn.textContent = text;
btn.className = "imgui-button imgui-control";
if (typeof tooltip === 'string' && tooltip.length > 0) btn.title = tooltip;
btn.addEventListener('mouseenter', () => {
//btn.style.background = this.theme.buttonHover;
btn.style.background = 'var(--imgui-button-hover)';
})
btn.addEventListener('mouseout', () => {
//btn.style.background = this.theme.buttonBg;
btn.style.background = 'var(--imgui-button-bg)';
})
if (callback && typeof callback === 'function') {
btn.addEventListener('click', callback);
}
wrapper.appendChild(btn);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return btn;
}
Textbox(placeholder, defaultValue = "", tooltip = '') {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `
display: flex;
flex-direction: column;
margin-bottom: var(--imgui-bottom-padding);
`;
const input = document.createElement('input');
input.id = ImmediateGUI.GenerateId("ctrl_");
input.type = 'text';
input.placeholder = placeholder;
input.value = defaultValue;
input.style.background = 'var(--imgui-input-bg)';
input.style.border = '1px solid var(--imgui-border)';
input.className = "imgui-input imgui-control";
if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) input.title = tooltip;
wrapper.appendChild(input);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return input;
}
TextArea(placeholder = "", defaultValue = "", rows = 4, tooltip = '') {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `
display: flex;
flex-direction: column;
margin-bottom: var(--imgui-bottom-padding);
`;
const textarea = document.createElement('textarea');
textarea.id = ImmediateGUI.GenerateId("ctrl_");
textarea.placeholder = placeholder;
textarea.value = defaultValue;
textarea.rows = rows;
textarea.className = "imgui-input imgui-control";
textarea.style.cssText = `
background: var(--imgui-input-bg);
resize: vertical;
min-height: ${rows * 20}px;
font-family: inherit;
margin-bottom: 0;
max-height: calc(${this.maxHeight} - 50px); /* Limit max height to prevent overflowing */
`;
if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) textarea.title = tooltip;
textarea.addEventListener('mouseup', () => {
const container = this.container;
const containerMaxHeight = parseInt(getComputedStyle(container).maxHeight);
const containerRect = container.getBoundingClientRect();
const textareaRect = textarea.getBoundingClientRect();
// Calculate how much space is available in the container
const availableSpace = containerMaxHeight - (textareaRect.top - containerRect.top) - 20; // 20px buffer
// If textarea is too tall, limit its height
if (textarea.offsetHeight > availableSpace) {
textarea.style.height = `${availableSpace}px`;
}
});
wrapper.appendChild(textarea);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return textarea;
}
Label(text) {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `
display: flex;
margin-bottom: var(--imgui-bottom-padding);
`;
const label = document.createElement('label');
label.id = ImmediateGUI.GenerateId("ctrl_");
label.textContent = text;
label.className = "imgui-label imgui-control";
wrapper.appendChild(label);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return label;
}
ProgressBar(value = 0, min = 0, max = 100, showText = true, tooltip = '') {
// Create wrapper element
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `
display: flex;
flex-direction: column;
width: 100%;
margin-bottom: var(--imgui-bottom-padding);
`;
// Create progress container
const progressContainer = document.createElement('div');
progressContainer.style.cssText = `
width: 100%;
height: 20px;
background: var(--imgui-input-bg);
border: 1px solid var(--imgui-border);
border-radius: 4px;
overflow: hidden;
position: relative;
`;
// Create progress bar element
const progressBar = document.createElement('div');
progressBar.id = ImmediateGUI.GenerateId("ctrl_");
progressBar.className = "imgui-progressbar imgui-control";
if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) progressBar.title = tooltip;
// Calculate the percentage
const normalizedValue = Math.min(Math.max(value, min), max);
const percentage = ((normalizedValue - min) / (max - min)) * 100;
progressBar.style.cssText = `
width: ${percentage}%;
height: 100%;
background: var(--imgui-accent);
transition: width 0.3s ease;
`;
// Optional text display
let textElement = null;
if (showText) {
textElement = document.createElement('div');
textElement.textContent = `${Math.round(percentage)}%`;
textElement.style.cssText = `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
color: var(--imgui-text);
font-size: 12px;
font-weight: 500;
text-shadow: 0 0 2px rgba(0,0,0,0.5);
pointer-events: none;
`;
progressContainer.appendChild(textElement);
}
// Add elements to the DOM
progressContainer.appendChild(progressBar);
wrapper.appendChild(progressContainer);
// Add methods to update the progress bar
progressBar.setValue = (newValue) => {
const normalizedNewValue = Math.min(Math.max(newValue, min), max);
const newPercentage = ((normalizedNewValue - min) / (max - min)) * 100;
progressBar.style.width = `${newPercentage}%`;
if (textElement) {
textElement.textContent = `${Math.round(newPercentage)}%`;
}
};
// Store references
progressBar.textElement = textElement;
progressBar.min = min;
progressBar.max = max;
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return progressBar;
}
ColorPicker(defaultValue = '#000000', tooltip = '') {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `display: flex; align-items: center; padding-bottom: var(--imgui-bottom-padding);`;
const colorPicker = document.createElement('input');
colorPicker.id = ImmediateGUI.GenerateId("ctrl_");
colorPicker.type = 'color';
colorPicker.value = defaultValue;
colorPicker.className = "imgui-input";
colorPicker.style.cssText = `
margin-right: 8px;
width: 80px;
height: 40px;
border: 1px solid var(--imgui-border);
border-radius: 4px;
background: none;
background-color: var(--imgui-input-bg);
cursor: pointer;
`;
if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) colorPicker.title = tooltip;
const colorValue = document.createElement('span');
colorValue.textContent = defaultValue;
colorValue.style.cssText = `
font-family: monospace;
color: var(--imgui-text);
cursor: pointer;
`;
//colorValue.addEventListener('click', () => { alert('lel'); });
colorPicker.addEventListener('input', () => {
colorValue.textContent = colorPicker.value;
});
wrapper.appendChild(colorPicker);
wrapper.appendChild(colorValue);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return colorPicker;
}
DatePicker(defaultValue = new Date().toISOString().split('T')[0], tooltip = '') {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `
display: flex;
flex-direction: column;
margin-bottom: var(--imgui-bottom-padding);
`;
const datePicker = document.createElement('input');
datePicker.id = ImmediateGUI.GenerateId("ctrl_");
datePicker.type = 'date';
datePicker.value = defaultValue;
datePicker.className = "imgui-input imgui-control";
datePicker.style.cursor = "pointer";
if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) datePicker.title = tooltip;
wrapper.appendChild(datePicker);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return datePicker;
}
Dropdown(options = [], defaultValue = null, tooltip = '') {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `
display: flex;
flex-direction: column;
margin-bottom: var(--imgui-bottom-padding);
`;
const select = document.createElement('select');
select.id = ImmediateGUI.GenerateId("ctrl_");
select.className = "imgui-input imgui-dropdown imgui-control";
select.style.cssText = `
padding: 6px 10px;
border: 1px solid var(--imgui-border);
border-radius: 4px;
background: var(--imgui-input-bg);
color: var(--imgui-text);
font-family: inherit;
cursor: pointer;
appearance: auto;
`;
if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) select.title = tooltip;
// Add options to the select element
options.forEach(option => {
const optElement = document.createElement('option');
optElement.style.backgroundColor = 'var(--imgui-input-bg)';
optElement.style.color = 'var(--imgui-text)';
// Handle both simple strings and {text, value} objects
if (typeof option === 'object' && option !== null) {
optElement.textContent = option.text || option.label || '';
optElement.value = option.value !== undefined ? option.value : option.text || '';
} else {
optElement.textContent = option;
optElement.value = option;
}
// Set as selected if it matches the default value
if (defaultValue !== null && optElement.value === defaultValue) {
optElement.selected = true;
}
select.appendChild(optElement);
});
wrapper.appendChild(select);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return select;
}
NumberInput(label, defaultValue = 0, min = null, max = null, tooltip = '') {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `display: flex; align-items: center; margin-bottom: var(--imgui-bottom-padding);`;
const labelElem = document.createElement('label');
labelElem.textContent = label;
labelElem.style.cssText = `
margin-right: 10px;
margin-top:8px;
flex: 1;
color: var(--imgui-text);
`;
const input = document.createElement('input');
input.label = labelElem;
input.id = ImmediateGUI.GenerateId("ctrl_");
input.type = 'number';
input.value = defaultValue;
if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) input.title = tooltip;
if (min !== null) input.min = min;
if (max !== null) input.max = max;
// TODO: hacky solution to make input elements respect .min and .max values when inputting values manually using the keyboard
if (min !== null || max !== null) {
input.addEventListener('keyup', () => {
let currentValue = parseInt(input.value);
if (isNaN(currentValue)) {
input.value = Math.floor(min);
}
else {
if (min !== null && currentValue < min) {
input.value = Math.floor(min);
} else if (max !== null && currentValue > max) {
input.value = Math.floor(max);
}
}
});
}
input.className = 'imgui-input'
input.style.cssText = `
width: 80px;
padding: 6px;
border: 1px solid var(--imgui-border);
border-radius: 4px;
background: var(--imgui-input-bg);
color: var(--imgui-text);
font-family: inherit;
`;
wrapper.appendChild(labelElem);
wrapper.appendChild(input);
this._applyIndentation(labelElem);
this._getTargetContainer().appendChild(wrapper);
return input;
}
Slider(minValue = 0, maxValue = 100, defaultValue = 50, tooltip = '') {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `display: flex; flex-direction: column; margin-bottom: var(--imgui-bottom-padding);`;
const sliderContainer = document.createElement('div');
sliderContainer.style.cssText = `display: flex; align-items: center; width: 100%;`;
const slider = document.createElement('input');
slider.className = "imgui-slider";
slider.id = ImmediateGUI.GenerateId("ctrl_");
slider.type = 'range';
slider.min = minValue;
slider.max = maxValue;
slider.value = defaultValue;
slider.style.cssText = `
flex: 1;
margin-right: 8px;
/* accent-color: var(--imgui-accent); */
`;
if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) slider.title = tooltip;
const valueDisplay = document.createElement('span');
valueDisplay.textContent = defaultValue;
valueDisplay.style.cssText = `
min-width: 40px;
text-align: right;
color: var(--imgui-text);
font-family: inherit;
font-weight: 500;
`;
slider.addEventListener('input', () => {
valueDisplay.textContent = slider.value;
});
slider.label = valueDisplay;
sliderContainer.appendChild(slider);
sliderContainer.appendChild(valueDisplay);
wrapper.appendChild(sliderContainer);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return slider;
}
Checkbox(label, checked = false, tooltip = '') {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `display: flex; align-items: center; margin-bottom: var(--imgui-bottom-padding)`;
const checkbox = document.createElement('input');
checkbox.id = ImmediateGUI.GenerateId("ctrl_");
checkbox.type = 'checkbox';
checkbox.checked = checked;
checkbox.style.cssText = `
margin-right: 8px;
accent-color: var(--imgui-accent);
clip-path: circle(46% at 50% 50%);
width: 16px;
height: 16px;
`;
if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) wrapper.title = tooltip;
const labelElem = document.createElement('label');
labelElem.textContent = label;
labelElem.htmlFor = checkbox.id;
labelElem.style.cssText = `
cursor: pointer;
color: var(--imgui-text);
font-family: inherit;
margin-top: 6px;
`;
checkbox.label = labelElem;
wrapper.appendChild(checkbox);
wrapper.appendChild(labelElem);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return checkbox;
}
RadioButtons(options = [], defaultValue = null, tooltip = '') {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control imgui-wrapper";
wrapper.style.cssText = `
display: flex;
flex-direction: column;
margin-bottom: var(--imgui-bottom-padding);
`;
if (tooltip && typeof tooltip === 'string' && tooltip.length > 0) wrapper.title = tooltip;
// Generate a unique group name for this set of radio buttons
const groupName = ImmediateGUI.GenerateId("radio_group_");
// Create an object to store references to the radio buttons
const radioButtons = {};
// Add radio buttons to the wrapper
options.forEach(option => {
// Handle both simple strings and {text, value} objects
let text, value;
if (typeof option === 'object' && option !== null) {
text = option.text || option.label || '';
value = option.value !== undefined ? option.value : option.text || '';
} else {
text = option;
value = option;
}
const radioContainer = document.createElement('div');
radioContainer.style.cssText = `
display: flex;
align-items: center;
`;
const radio = document.createElement('input');
radio.id = ImmediateGUI.GenerateId("ctrl_");
radio.type = 'radio';
radio.name = groupName;
radio.value = value;
radio.checked = value === defaultValue;
radio.style.cssText = `
margin-right: 8px;
accent-color: var(--imgui-accent);
width: 16px;
height: 16px;
`;
const label = document.createElement('label');
label.textContent = text;
label.htmlFor = radio.id;
label.style.cssText = `
cursor: pointer;
color: var(--imgui-text);
font-family: inherit;
margin-top: ${radioContainer.style.marginBottom || '6px'};
`;
radio.label = label;
radioContainer.appendChild(radio);
radioContainer.appendChild(label);
wrapper.appendChild(radioContainer);
// Store reference to the radio button
radioButtons[value] = radio;
});
// Add helper methods to the radio group
radioButtons.getChecked = () => {
const selected = wrapper.querySelector(`input[name="${groupName}"]:checked`);
return selected ? selected.value : null;
};
radioButtons.setChecked = (value, checked) => {
const radio = wrapper.querySelector(`input[name="${groupName}"][value="${value}"]`);
if (radio) {
radio.checked = checked;
}
};
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return radioButtons;
}
Show() {
if (this.container.style.display === 'none') {
this.container.style.display = 'block';
return this;
} else {
if (this.indentationLevel > 0) {
console.warn("ImmediateGUI: Show() called while in indentation mode. Did you forget to call EndIndentation() somewhere?");
}
if (this.currentSection) {
console.warn("ImmediateGUI: Show() called while in section mode. Did you forget to call EndSection() somewhere?");
}
if (this.container.children.length === 0) return this;
if (!document.body.contains(this.container)) {
// Last control wrapper does not need bottom margin, strip that away
this.GetControls()[this.GetControls().length - 1].style.marginBottom = '0px';
document.body.appendChild(this.container);
}
return this;
}
}
Remove() {
this.container.remove();
}
Hide() {
this.container.style.display = "none";
return this;
}
ShowModal(message, title = '', options = {}) {
// Default options
const config = {
title: title || '',
type: 'info', // 'info', 'warning', 'error', 'success'
buttons: ['OK'],
closeOnBackdropClick: true,
width: 400,
...options
};
const backdrop = document.createElement('div');
backdrop.className = 'imgui-modal-backdrop';
backdrop.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s ease;
`;
const modal = document.createElement('div');
modal.className = 'imgui-modal';
modal.style.cssText = `
background: var(--imgui-bg);
border: 1px solid var(--imgui-border);
border-radius: 6px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
width: ${config.width}px;
max-width: 90vw;
max-height: 80vh;
overflow-y: auto;
padding: 16px;
transform: translateY(-20px);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
font-family: inherit;
`;
if (config.title) {
const title = document.createElement('div');
title.className = 'imgui-modal-title';
title.textContent = config.title;
title.style.cssText = `
font-size: 18px;
font-weight: bold;
color: var(--imgui-text);
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid var(--imgui-border);
`;
modal.appendChild(title);
}
let iconHtml = '';
if (config.type === 'warning') {
iconHtml = '<div style="color: #f0ad4e; margin-right: 10px; font-size: 24px;">⚠️</div>';
} else if (config.type === 'error') {
iconHtml = '<div style="color: #d9534f; margin-right: 10px; font-size: 24px;">❌</div>';
} else if (config.type === 'info') {
iconHtml = '<div style="color: #5bc0de; margin-right: 10px; font-size: 24px;">ℹ️</div>';
} else if (config.type === 'success') {
iconHtml = '<div style="color: #5cb85c; margin-right: 10px; font-size: 24px;">✅</div>';
}
const messageContainer = document.createElement('div');
messageContainer.className = 'imgui-modal-message';
messageContainer.style.cssText = `
color: var(--imgui-text);
margin-bottom: 16px;
line-height: 1.5;
display: flex;
align-items: flex-start;
`;
if (iconHtml) {
const iconElement = document.createElement('div');
iconElement.innerHTML = iconHtml;
messageContainer.appendChild(iconElement);
}
const messageText = document.createElement('div');
messageText.style.flex = '1';
if (typeof message === 'object' && message.nodeType) {
messageText.appendChild(message);
} else {
messageText.textContent = message;
}
messageContainer.appendChild(messageText);
modal.appendChild(messageContainer);
const buttonsContainer = document.createElement('div');
buttonsContainer.className = 'imgui-modal-buttons';
buttonsContainer.style.cssText = `
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 16px;
`;
const closeModal = () => {
modal.style.transform = 'translateY(-20px)';
modal.style.opacity = '0';
backdrop.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(backdrop);
}, 300);
};
const buttonsList = Array.isArray(config.buttons) ? config.buttons : [config.buttons];
buttonsList.forEach((buttonConfig) => {
const isObject = typeof buttonConfig === 'object';
const buttonText = isObject ? buttonConfig.text : buttonConfig;
const isPrimary = isObject ? buttonConfig.primary : false;
const callback = isObject ? buttonConfig.callback : null;
const button = document.createElement('button');
button.textContent = buttonText;
button.className = isPrimary ? 'imgui-button imgui-primary-button' : 'imgui-button';
if (isPrimary) {
button.style.background = 'var(--imgui-accent)';
button.style.color = '#ffffff';
button.style.borderColor = 'var(--imgui-accent)';
}
button.addEventListener('click', () => {
if (callback) callback();
closeModal();
});
buttonsContainer.appendChild(button);
});
modal.appendChild(buttonsContainer);
backdrop.appendChild(modal);
if (config.closeOnBackdropClick) {
backdrop.addEventListener('click', (e) => {
if (e.target === backdrop) {
closeModal();
}
});
}
const escHandler = (e) => {
if (e.key === 'Escape') {
closeModal();
document.removeEventListener('keydown', escHandler);
}
};
document.addEventListener('keydown', escHandler);
document.body.appendChild(backdrop);
setTimeout(() => {
backdrop.style.opacity = '1';
modal.style.transform = 'translateY(0)';
modal.style.opacity = '1';
}, 10);
return {
close: closeModal,
element: modal,
backdrop: backdrop
};
}
// New methods for better theming and layout
SetTheme(themeName) {
if (this.themes[themeName]) {
this.options.theme = themeName;
this.theme = this.themes[themeName];
this._updateCSSVariables();
this._applyThemeToElements();
}
return this;
}
_applyThemeToElements() {
// Update container
this.container.style.background = this.theme.background;
this.container.style.color = this.theme.text;
this.container.style.borderColor = this.theme.border;
// Update all controls
this.container.querySelectorAll('.imgui-button').forEach(el => {
el.style.background = this.theme.buttonBg;
el.style.color = this.theme.text;
el.style.borderColor = this.theme.border;
});
this.container.querySelectorAll('.imgui-input').forEach(el => {
el.style.background = this.theme.inputBg;
el.style.color = this.theme.text;
el.style.borderColor = this.theme.border;
});
this.container.querySelectorAll('.imgui-section').forEach(el => {
// Shitty hack to make the section header text color change for sections
// that are not collapsible
el.querySelectorAll('.imgui-section-header').forEach(h => { h.style.color = this.theme.text;});
el.style.background = this.theme.sectionBg;
el.style.borderColor = this.theme.border;
});
// this.container.querySelectorAll('.imgui-progressbar').forEach(el => {
// // el.style.background = this.theme.inputBg;
// // el.style.borderColor = this.theme.border;
// });
// Update text elements
this.container.querySelectorAll('label, h1, h2, h3, h4, h5, h6, span').forEach(el => {
el.style.color = this.theme.text;
});
return this;
}
}