class ImmediateGUI {
static OccupiedElementIds = [];
static GenerateId(prefix = "gui_") {
if (typeof prefix !== 'string' || prefix.length === 0) {
prefix = "gui_";
}
const timestamp = Date.now().toString(36);
const randomPart = Math.random().toString(36).substring(2, 15);
const generatedId = prefix + timestamp + randomPart;
const exists = ImmediateGUI.OccupiedElementIds.includes(generatedId);
if (exists) return ImmediateGUI.GenerateId(prefix);
ImmediateGUI.OccupiedElementIds.push(generatedId);
return generatedId;
}
constructor(options = {}) {
this.options = {
theme: 'light',
position: 'right',
width: 300,
draggable: true,
...options
};
this.themes = {
light: {
background: '#ffffff',
text: '#333333',
border: '#cccccc',
accent: '#4285f4',
buttonBg: '#f5f5f5',
buttonHover: '#e0e0e0',
inputBg: '#ffffff',
sectionBg: '#f9f9f9'
},
dark: {
background: '#2d2d2d',
text: '#e0e0e0',
border: '#555555',
accent: '#4d90fe',
buttonBg: '#444444',
buttonHover: '#555555',
inputBg: '#3d3d3d',
sectionBg: '#333333'
}
};
this.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;
`;
this._applyGlobalStyles();
this.currentSection = null;
this.indentationLevel = 0;
this.indentationSize = 10; // pixels per level
this.isCustomIndentationLevel = false;
if (this.options.draggable) {
this._setupDragging();
}
}
_applyGlobalStyles() {
if (!document.getElementById('imgui-global-styles')) {
const styleEl = document.createElement('style');
styleEl.id = 'imgui-global-styles';
styleEl.textContent = `
.imgui-control {
margin-bottom: 2px;
width: 100%;
box-sizing: border-box;
}
.imgui-button {
background: ${this.theme.buttonBg};
color: ${this.theme.text};
border: 1px solid ${this.theme.border};
border-radius: 4px;
padding: 8px 12px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
outline: none;
width: auto;
font-family: inherit;
margin-right: 5px;
}
.imgui-button:hover {
background: ${this.theme.buttonHover};
}
.imgui-button:active {
transform: translateY(1px);
}
.imgui-input {
background: ${this.theme.inputBg};
color: ${this.theme.text};
border: 1px solid ${this.theme.border};
border-radius: 4px;
padding: 8px 10px;
font-size: 14px;
width: 100%;
box-sizing: border-box;
outline: none;
transition: border-color 0.2s ease;
font-family: inherit;
}
.imgui-input:focus {
border-color: ${this.theme.accent};
}
.imgui-section {
border: 1px solid ${this.theme.border};
border-radius: 4px;
padding: 10px;
margin-bottom: 12px;
background: ${this.theme.sectionBg};
}
.imgui-section-header {
font-weight: 600;
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid ${this.theme.border};
color: ${this.theme.text};
}
.imgui-label {
display: block;
margin-bottom: 4px;
color: ${this.theme.text};
font-weight: 500;
}
`;
document.head.appendChild(styleEl);
}
}
_setupDragging() {
let isDragging = false;
let startX, startY, startLeft, startTop;
const isClickOnControl = (element) => {
if (!element) return false;
let current = element;
while (current && current !== this.container) {
if (
current.classList.contains('imgui-control') ||
current.tagName === 'INPUT' ||
current.tagName === 'BUTTON' ||
current.tagName === 'SELECT' ||
current.tagName === 'TEXTAREA'
) {
return true;
}
current = current.parentElement;
}
return false;
};
this.container.addEventListener('mousedown', (e) => {
if (e.button !== 0) return;
if (isClickOnControl(e.target)) {
return;
}
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const rect = this.container.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
document.body.style.cursor = 'move';
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const newLeft = startLeft + dx;
const newTop = startTop + dy;
this.container.style.left = `${newLeft}px`;
this.container.style.top = `${newTop}px`;
this.container.style.right = 'auto';
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
document.body.style.cursor = '';
this._keepInView();
}
});
document.addEventListener('mouseleave', () => {
if (isDragging) {
isDragging = false;
document.body.style.cursor = '';
}
});
}
_keepInView() {
const rect = this.container.getBoundingClientRect();
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const minVisiblePx = 50;
let newLeft = rect.left;
let newTop = rect.top;
if (rect.right < minVisiblePx) {
newLeft = minVisiblePx - rect.width;
} else if (rect.left > windowWidth - minVisiblePx) {
newLeft = windowWidth - minVisiblePx;
}
if (rect.bottom < minVisiblePx) {
newTop = minVisiblePx - rect.height;
} else if (rect.top > windowHeight - minVisiblePx) {
newTop = windowHeight - minVisiblePx;
}
if (newLeft !== rect.left || newTop !== rect.top) {
this.container.style.left = `${newLeft}px`;
this.container.style.top = `${newTop}px`;
}
}
// Section management
BeginSection(title, collapsible = false, collapsedByDefault = false) {
const section = document.createElement('div');
section.className = 'imgui-section';
section.id = ImmediateGUI.GenerateId('section_');
if (title) {
const header = document.createElement('div');
header.className = 'imgui-section-header';
if (collapsible) {
header.style.cssText = `
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid ${this.theme.border};
color: ${this.theme.text};
`;
const indicator = document.createElement('span');
indicator.className = 'imgui-section-indicator';
indicator.textContent = '▼';
indicator.style.cssText = `
margin-right: 8px;
font-size: 10px;
transition: transform 0.2s ease;
`;
const titleSpan = document.createElement('span');
titleSpan.textContent = title;
titleSpan.style.flex = '1';
const content = document.createElement('div');
content.className = 'imgui-section-content';
content.style.cssText = `
overflow: hidden;
transition: max-height 0.3s ease;
`;
section.isCollapsed = false;
const toggleCollapse = () => {
section.isCollapsed = !section.isCollapsed;
if (section.isCollapsed) {
content.style.maxHeight = '0px';
indicator.textContent = '►';
// TODO: figure out why the ► character looks squished and skinny??
//indicator.style.fontSize = '14px';
indicator.style.transform = 'rotate(0deg)';
} else {
content.style.maxHeight = '2000px';
indicator.textContent = '▼';
indicator.style.transform = 'rotate(0deg)';
}
};
if (collapsedByDefault) toggleCollapse();
header.addEventListener('click', toggleCollapse);
header.appendChild(indicator);
header.appendChild(titleSpan);
section.appendChild(header);
section.appendChild(content);
section.contentContainer = content;
} else {
header.textContent = title;
section.appendChild(header);
}
}
this.container.appendChild(section);
this.currentSection = section;
return this;
}
EndSection() {
this.currentSection = null;
return this;
}
// Indentation management
BeginIndentation(level = -1) {
if (level === -1) this.indentationLevel++;
else {
this.isCustomIndentationLevel = true;
this.indentationLevel = level;
}
return this;
}
EndIndentation() {
if (this.indentationLevel > 0) {
if (this.isCustomIndentationLevel) {
this.indentationLevel = 0;
this.isCustomIndentationLevel = false;
}
else this.indentationLevel--;
}
return this;
}
_applyIndentation(element) {
if (this.indentationLevel > 0) {
const currentIndent = this.indentationLevel * this.indentationSize;
element.style.marginLeft = `${currentIndent}px`;
}
return element;
}
// Utility to get current target container
_getTargetContainer() {
if (this.currentSection) {
// If current section is collapsible, use its content container
if (this.currentSection.contentContainer) {
return this.currentSection.contentContainer;
}
return this.currentSection;
}
return this.container;
}
// Original API methods with improved implementation
GetControlContainer() {
return this.container;
}
GetControls() {
return this.container.querySelectorAll('.imgui-control');
}
Separator() {
const separator = document.createElement('hr');
separator.id = ImmediateGUI.GenerateId("ctrl_");
separator.style.cssText = `
border: none;
border-top: 1px solid ${this.theme.border};
margin: 10px 0;
width: 100%;
`;
this._applyIndentation(separator);
this._getTargetContainer().appendChild(separator);
return separator;
}
Header(text, level = 1) {
const validLevel = Math.min(Math.max(level, 1), 6);
const header = document.createElement(`h${validLevel}`);
header.id = ImmediateGUI.GenerateId("ctrl_");
header.textContent = text;
header.style.cssText = `
margin: 0 0 10px 0;
padding: 0;
font-weight: ${validLevel <= 2 ? 'bold' : '600'};
color: ${this.theme.text};
font-size: ${24 - (validLevel * 2)}px;
font-family: inherit;
`;
this._applyIndentation(header);
this._getTargetContainer().appendChild(header);
return header;
}
LabelControlPair(text, controlGeneratorFunction) {
const wrapper = document.createElement("div");
wrapper.className = "imgui-control";
wrapper.style.cssText = `
display: flex;
flex-direction: column;
margin-bottom: 10px;
`;
const label = document.createElement('label');
label.id = ImmediateGUI.GenerateId("ctrl_");
label.textContent = text;
label.className = "imgui-label";
const control = typeof controlGeneratorFunction === "function"
? controlGeneratorFunction()
: controlGeneratorFunction;
control.label = label;
wrapper.appendChild(label);
wrapper.appendChild(control);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return control;
}
Button(text, callback) {
const btn = document.createElement('button');
btn.id = ImmediateGUI.GenerateId("ctrl_");
btn.textContent = text;
btn.className = "imgui-button imgui-control";
if (callback && typeof callback === 'function') {
btn.addEventListener('click', callback);
}
this._applyIndentation(btn);
this._getTargetContainer().appendChild(btn);
return btn;
}
Textbox(placeholder, defaultValue = "") {
const input = document.createElement('input');
input.id = ImmediateGUI.GenerateId("ctrl_");
input.type = 'text';
input.placeholder = placeholder;
input.value = defaultValue;
input.className = "imgui-input imgui-control";
this._applyIndentation(input);
this._getTargetContainer().appendChild(input);
return input;
}
TextArea(placeholder = "", defaultValue = "", rows = 4) {
const textarea = document.createElement('textarea');
textarea.id = ImmediateGUI.GenerateId("ctrl_");
textarea.placeholder = placeholder;
textarea.value = defaultValue;
textarea.rows = rows;
textarea.className = "imgui-input imgui-control";
textarea.style.cssText = `
resize: vertical;
min-height: ${rows * 20}px;
font-family: inherit;
margin-bottom: 10px;
`;
this._applyIndentation(textarea);
this._getTargetContainer().appendChild(textarea);
return textarea;
}
Label(text) {
const label = document.createElement('label');
label.id = ImmediateGUI.GenerateId("ctrl_");
label.textContent = text;
label.className = "imgui-label imgui-control";
this._applyIndentation(label);
this._getTargetContainer().appendChild(label);
return label;
}
ProgressBar(value = 0, min = 0, max = 100, showText = true) {
// Create wrapper element
const wrapper = document.createElement('div');
wrapper.className = "imgui-control";
wrapper.style.cssText = `
display: flex;
flex-direction: column;
width: 100%;
margin-bottom: 10px;
`;
// Create progress container
const progressContainer = document.createElement('div');
progressContainer.style.cssText = `
width: 100%;
height: 20px;
background: ${this.theme.inputBg};
border: 1px solid ${this.theme.border};
border-radius: 4px;
overflow: hidden;
position: relative;
`;
// Create progress bar element
const progressBar = document.createElement('div');
progressBar.id = ImmediateGUI.GenerateId("ctrl_");
progressBar.className = "imgui-progressbar imgui-control";
// Calculate the percentage
const normalizedValue = Math.min(Math.max(value, min), max);
const percentage = ((normalizedValue - min) / (max - min)) * 100;
progressBar.style.cssText = `
width: ${percentage}%;
height: 100%;
background: ${this.theme.accent};
transition: width 0.3s ease;
`;
// Optional text display
let textElement = null;
if (showText) {
textElement = document.createElement('div');
textElement.textContent = `${Math.round(percentage)}%`;
textElement.style.cssText = `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
color: ${this.theme.text};
font-size: 12px;
font-weight: bold;
text-shadow: 0 0 2px rgba(0,0,0,0.5);
pointer-events: none;
`;
progressContainer.appendChild(textElement);
}
// Add elements to the DOM
progressContainer.appendChild(progressBar);
wrapper.appendChild(progressContainer);
// Add methods to update the progress bar
progressBar.setValue = (newValue) => {
const normalizedNewValue = Math.min(Math.max(newValue, min), max);
const newPercentage = ((normalizedNewValue - min) / (max - min)) * 100;
progressBar.style.width = `${newPercentage}%`;
if (textElement) {
textElement.textContent = `${Math.round(newPercentage)}%`;
}
};
// Store references
progressBar.textElement = textElement;
progressBar.min = min;
progressBar.max = max;
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return progressBar;
}
ColorPicker(defaultValue = '#000000') {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control";
wrapper.style.cssText = `display: flex; align-items: center;`;
const colorPicker = document.createElement('input');
colorPicker.id = ImmediateGUI.GenerateId("ctrl_");
colorPicker.type = 'color';
colorPicker.value = defaultValue;
colorPicker.style.cssText = `
margin-right: 8px;
width: 40px;
height: 40px;
border: 1px solid ${this.theme.border};
border-radius: 4px;
background: none;
cursor: pointer;
`;
const colorValue = document.createElement('span');
colorValue.textContent = defaultValue;
colorValue.style.cssText = `
font-family: monospace;
color: ${this.theme.text};
`;
colorPicker.addEventListener('input', () => {
colorValue.textContent = colorPicker.value;
});
wrapper.appendChild(colorPicker);
wrapper.appendChild(colorValue);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return colorPicker;
}
DatePicker(defaultValue = new Date().toISOString().split('T')[0]) {
const datePicker = document.createElement('input');
datePicker.id = ImmediateGUI.GenerateId("ctrl_");
datePicker.type = 'date';
datePicker.value = defaultValue;
datePicker.className = "imgui-input imgui-control";
this._applyIndentation(datePicker);
this._getTargetContainer().appendChild(datePicker);
return datePicker;
}
Dropdown(options = [], defaultValue = null) {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control";
wrapper.style.cssText = `
display: flex;
flex-direction: column;
margin-bottom: 10px;
`;
const select = document.createElement('select');
select.id = ImmediateGUI.GenerateId("ctrl_");
select.className = "imgui-input imgui-dropdown imgui-control";
select.style.cssText = `
padding: 6px 10px;
border: 1px solid ${this.theme.border};
border-radius: 4px;
background: ${this.theme.inputBg};
color: ${this.theme.text};
font-family: inherit;
cursor: pointer;
appearance: auto;
`;
// Add options to the select element
options.forEach(option => {
const optElement = document.createElement('option');
// Handle both simple strings and {text, value} objects
if (typeof option === 'object' && option !== null) {
optElement.textContent = option.text || option.label || '';
optElement.value = option.value !== undefined ? option.value : option.text || '';
} else {
optElement.textContent = option;
optElement.value = option;
}
// Set as selected if it matches the default value
if (defaultValue !== null && optElement.value === defaultValue) {
optElement.selected = true;
}
select.appendChild(optElement);
});
wrapper.appendChild(select);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return select;
}
NumberInput(label, defaultValue = 0, min = null, max = null) {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control";
wrapper.style.cssText = `display: flex; align-items: center;`;
const labelElem = document.createElement('label');
labelElem.textContent = label;
labelElem.style.cssText = `
margin-right: 10px;
flex: 1;
color: ${this.theme.text};
`;
const input = document.createElement('input');
input.label = labelElem;
input.id = ImmediateGUI.GenerateId("ctrl_");
input.type = 'number';
input.value = defaultValue;
if (min !== null) input.min = min;
if (max !== null) input.max = max;
// TODO: hacky solution to make input elements respect .min and .max values when inputting values manually using the keyboard
// if (min !== null || max !== null) {
// input.onkeyup = (e) => {
// const currentValue = parseInt(input.value);
// if (isNaN(currentValue)) {
// input.value = Math.floor(min);
// return;
// }
// // Clamp input.value to a value between input.min and input.max
// if (min !== null && currentValue < min) {
// input.value = Math.floor(min);
// } else if (max !== null && currentValue > max) {
// input.value = Math.floor(max);
// }
// };
// }
input.style.cssText = `
width: 80px;
padding: 6px;
border: 1px solid ${this.theme.border};
border-radius: 4px;
background: ${this.theme.inputBg};
color: ${this.theme.text};
font-family: inherit;
`;
wrapper.appendChild(labelElem);
wrapper.appendChild(input);
//this._applyIndentation(wrapper);
this._applyIndentation(labelElem);
this._getTargetContainer().appendChild(wrapper);
return input;
}
Slider(minValue = 0, maxValue = 100, defaultValue = 50) {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control";
wrapper.style.cssText = `display: flex; flex-direction: column;`;
const sliderContainer = document.createElement('div');
sliderContainer.style.cssText = `display: flex; align-items: center; width: 100%;`;
const slider = document.createElement('input');
slider.id = ImmediateGUI.GenerateId("ctrl_");
slider.type = 'range';
slider.min = minValue;
slider.max = maxValue;
slider.value = defaultValue;
slider.style.cssText = `
flex: 1;
margin-right: 8px;
accent-color: ${this.theme.accent};
`;
const valueDisplay = document.createElement('span');
valueDisplay.textContent = defaultValue;
valueDisplay.style.cssText = `
min-width: 40px;
text-align: right;
color: ${this.theme.text};
font-family: inherit;
`;
slider.addEventListener('input', () => {
valueDisplay.textContent = slider.value;
});
slider.label = valueDisplay;
sliderContainer.appendChild(slider);
sliderContainer.appendChild(valueDisplay);
wrapper.appendChild(sliderContainer);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return slider;
}
Checkbox(label, checked = false) {
const wrapper = document.createElement('div');
wrapper.className = "imgui-control";
wrapper.style.cssText = `display: flex; align-items: center;`;
const checkbox = document.createElement('input');
checkbox.id = ImmediateGUI.GenerateId("ctrl_");
checkbox.type = 'checkbox';
checkbox.checked = checked;
checkbox.style.cssText = `
margin-right: 8px;
accent-color: ${this.theme.accent};
width: 16px;
height: 16px;
`;
const labelElem = document.createElement('label');
labelElem.textContent = label;
labelElem.htmlFor = checkbox.id;
labelElem.style.cssText = `
cursor: pointer;
color: ${this.theme.text};
font-family: inherit;
margin-top: 6px;
`;
checkbox.label = labelElem;
wrapper.appendChild(checkbox);
wrapper.appendChild(labelElem);
this._applyIndentation(wrapper);
this._getTargetContainer().appendChild(wrapper);
return checkbox;
}
Show() {
if (this.container.children.length === 0) return this;
document.body.appendChild(this.container);
return this;
}
Remove() {
this.container.remove();
}
Hide() {
this.container.style.display = "none";
return this;
}
ShowModal(message, title = '', options = {}) {
// Default options
const config = {
title: title || '',
type: 'info', // 'info', 'warning', 'error'
buttons: ['OK'],
closeOnBackdropClick: true,
width: 400,
...options
};
const backdrop = document.createElement('div');
backdrop.className = 'imgui-modal-backdrop';
backdrop.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s ease;
`;
const modal = document.createElement('div');
modal.className = 'imgui-modal';
modal.style.cssText = `
background: ${this.theme.background};
border: 1px solid ${this.theme.border};
border-radius: 6px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
width: ${config.width}px;
max-width: 90vw;
max-height: 80vh;
overflow-y: auto;
padding: 16px;
transform: translateY(-20px);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
font-family: inherit;
`;
if (config.title) {
const title = document.createElement('div');
title.className = 'imgui-modal-title';
title.textContent = config.title;
title.style.cssText = `
font-size: 18px;
font-weight: bold;
color: ${this.theme.text};
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid ${this.theme.border};
`;
modal.appendChild(title);
}
let iconHtml = '';
if (config.type === 'warning') {
iconHtml = '<div style="color: #f0ad4e; margin-right: 10px; font-size: 24px;">⚠️</div>';
} else if (config.type === 'error') {
iconHtml = '<div style="color: #d9534f; margin-right: 10px; font-size: 24px;">❌</div>';
} else if (config.type === 'info') {
iconHtml = '<div style="color: #5bc0de; margin-right: 10px; font-size: 24px;">ℹ️</div>';
} else if (config.type === 'success') {
iconHtml = '<div style="color: #5cb85c; margin-right: 10px; font-size: 24px;">✅</div>';
}
const messageContainer = document.createElement('div');
messageContainer.className = 'imgui-modal-message';
messageContainer.style.cssText = `
color: ${this.theme.text};
margin-bottom: 16px;
line-height: 1.5;
display: flex;
align-items: flex-start;
`;
if (iconHtml) {
const iconElement = document.createElement('div');
iconElement.innerHTML = iconHtml;
messageContainer.appendChild(iconElement);
}
const messageText = document.createElement('div');
messageText.style.flex = '1';
if (typeof message === 'object' && message.nodeType) {
messageText.appendChild(message);
} else {
messageText.textContent = message;
}
messageContainer.appendChild(messageText);
modal.appendChild(messageContainer);
const buttonsContainer = document.createElement('div');
buttonsContainer.className = 'imgui-modal-buttons';
buttonsContainer.style.cssText = `
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 16px;
`;
const closeModal = () => {
modal.style.transform = 'translateY(-20px)';
modal.style.opacity = '0';
backdrop.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(backdrop);
}, 300);
};
const buttonsList = Array.isArray(config.buttons) ? config.buttons : [config.buttons];
buttonsList.forEach((buttonConfig) => {
const isObject = typeof buttonConfig === 'object';
const buttonText = isObject ? buttonConfig.text : buttonConfig;
const isPrimary = isObject ? buttonConfig.primary : false;
const callback = isObject ? buttonConfig.callback : null;
const button = document.createElement('button');
button.textContent = buttonText;
button.className = isPrimary ? 'imgui-button imgui-primary-button' : 'imgui-button';
if (isPrimary) {
button.style.background = this.theme.accent;
button.style.color = '#ffffff';
button.style.borderColor = this.theme.accent;
}
button.addEventListener('click', () => {
if (callback) callback();
closeModal();
});
buttonsContainer.appendChild(button);
});
modal.appendChild(buttonsContainer);
backdrop.appendChild(modal);
if (config.closeOnBackdropClick) {
backdrop.addEventListener('click', (e) => {
if (e.target === backdrop) {
closeModal();
}
});
}
const escHandler = (e) => {
if (e.key === 'Escape') {
closeModal();
document.removeEventListener('keydown', escHandler);
}
};
document.addEventListener('keydown', escHandler);
document.body.appendChild(backdrop);
setTimeout(() => {
backdrop.style.opacity = '1';
modal.style.transform = 'translateY(0)';
modal.style.opacity = '1';
}, 10);
return {
close: closeModal,
element: modal,
backdrop: backdrop
};
}
// New methods for better theming and layout
SetTheme(themeName) {
if (this.themes[themeName]) {
this.options.theme = themeName;
this.theme = this.themes[themeName];
this._applyThemeToElements();
}
return this;
}
_applyThemeToElements() {
// Update container
this.container.style.background = this.theme.background;
this.container.style.color = this.theme.text;
this.container.style.borderColor = this.theme.border;
// Update all controls
this.container.querySelectorAll('.imgui-button').forEach(el => {
el.style.background = this.theme.buttonBg;
el.style.color = this.theme.text;
el.style.borderColor = this.theme.border;
});
this.container.querySelectorAll('.imgui-input').forEach(el => {
el.style.background = this.theme.inputBg;
el.style.color = this.theme.text;
el.style.borderColor = this.theme.border;
});
this.container.querySelectorAll('.imgui-section').forEach(el => {
el.style.background = this.theme.sectionBg;
el.style.borderColor = this.theme.border;
});
// Update text elements
this.container.querySelectorAll('label, h1, h2, h3, h4, h5, h6, span').forEach(el => {
el.style.color = this.theme.text;
});
return this;
}
}