This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/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;
}
}