@stevejtrettel/shader-sandbox 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +220 -23
- package/bin/cli.js +106 -14
- package/dist-lib/app/App.d.ts +143 -15
- package/dist-lib/app/App.d.ts.map +1 -1
- package/dist-lib/app/App.js +1343 -108
- package/dist-lib/app/app.css +349 -24
- package/dist-lib/app/types.d.ts +48 -5
- package/dist-lib/app/types.d.ts.map +1 -1
- package/dist-lib/editor/EditorPanel.d.ts +2 -2
- package/dist-lib/editor/EditorPanel.d.ts.map +1 -1
- package/dist-lib/editor/EditorPanel.js +1 -1
- package/dist-lib/editor/editor-panel.css +55 -32
- package/dist-lib/editor/prism-editor.css +16 -16
- package/dist-lib/embed.js +1 -1
- package/dist-lib/engine/{ShadertoyEngine.d.ts → ShaderEngine.d.ts} +134 -10
- package/dist-lib/engine/ShaderEngine.d.ts.map +1 -0
- package/dist-lib/engine/ShaderEngine.js +1523 -0
- package/dist-lib/engine/glHelpers.d.ts +24 -0
- package/dist-lib/engine/glHelpers.d.ts.map +1 -1
- package/dist-lib/engine/glHelpers.js +88 -0
- package/dist-lib/engine/std140.d.ts +47 -0
- package/dist-lib/engine/std140.d.ts.map +1 -0
- package/dist-lib/engine/std140.js +119 -0
- package/dist-lib/engine/types.d.ts +55 -5
- package/dist-lib/engine/types.d.ts.map +1 -1
- package/dist-lib/engine/types.js +1 -1
- package/dist-lib/index.d.ts +4 -3
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +2 -1
- package/dist-lib/layouts/SplitLayout.d.ts +2 -1
- package/dist-lib/layouts/SplitLayout.d.ts.map +1 -1
- package/dist-lib/layouts/SplitLayout.js +3 -0
- package/dist-lib/layouts/TabbedLayout.d.ts.map +1 -1
- package/dist-lib/layouts/UILayout.d.ts +55 -0
- package/dist-lib/layouts/UILayout.d.ts.map +1 -0
- package/dist-lib/layouts/UILayout.js +147 -0
- package/dist-lib/layouts/default.css +2 -2
- package/dist-lib/layouts/index.d.ts +11 -1
- package/dist-lib/layouts/index.d.ts.map +1 -1
- package/dist-lib/layouts/index.js +17 -1
- package/dist-lib/layouts/split.css +33 -31
- package/dist-lib/layouts/tabbed.css +127 -74
- package/dist-lib/layouts/types.d.ts +14 -3
- package/dist-lib/layouts/types.d.ts.map +1 -1
- package/dist-lib/main.js +33 -0
- package/dist-lib/project/configHelpers.d.ts +45 -0
- package/dist-lib/project/configHelpers.d.ts.map +1 -0
- package/dist-lib/project/configHelpers.js +196 -0
- package/dist-lib/project/generatedLoader.d.ts +2 -2
- package/dist-lib/project/generatedLoader.d.ts.map +1 -1
- package/dist-lib/project/generatedLoader.js +23 -5
- package/dist-lib/project/loadProject.d.ts +6 -6
- package/dist-lib/project/loadProject.d.ts.map +1 -1
- package/dist-lib/project/loadProject.js +396 -144
- package/dist-lib/project/loaderHelper.d.ts +4 -4
- package/dist-lib/project/loaderHelper.d.ts.map +1 -1
- package/dist-lib/project/loaderHelper.js +278 -116
- package/dist-lib/project/types.d.ts +292 -13
- package/dist-lib/project/types.d.ts.map +1 -1
- package/dist-lib/project/types.js +13 -1
- package/dist-lib/styles/base.css +5 -1
- package/dist-lib/uniforms/UniformControls.d.ts +60 -0
- package/dist-lib/uniforms/UniformControls.d.ts.map +1 -0
- package/dist-lib/uniforms/UniformControls.js +518 -0
- package/dist-lib/uniforms/UniformStore.d.ts +74 -0
- package/dist-lib/uniforms/UniformStore.d.ts.map +1 -0
- package/dist-lib/uniforms/UniformStore.js +145 -0
- package/dist-lib/uniforms/UniformsPanel.d.ts +53 -0
- package/dist-lib/uniforms/UniformsPanel.d.ts.map +1 -0
- package/dist-lib/uniforms/UniformsPanel.js +124 -0
- package/dist-lib/uniforms/index.d.ts +11 -0
- package/dist-lib/uniforms/index.d.ts.map +1 -0
- package/dist-lib/uniforms/index.js +8 -0
- package/package.json +1 -1
- package/src/app/App.ts +1469 -126
- package/src/app/app.css +349 -24
- package/src/app/types.ts +53 -5
- package/src/editor/EditorPanel.ts +5 -5
- package/src/editor/editor-panel.css +55 -32
- package/src/editor/prism-editor.css +16 -16
- package/src/embed.ts +1 -1
- package/src/engine/ShaderEngine.ts +1934 -0
- package/src/engine/glHelpers.ts +117 -0
- package/src/engine/std140.ts +136 -0
- package/src/engine/types.ts +69 -5
- package/src/index.ts +4 -3
- package/src/layouts/SplitLayout.ts +8 -3
- package/src/layouts/TabbedLayout.ts +3 -3
- package/src/layouts/UILayout.ts +185 -0
- package/src/layouts/default.css +2 -2
- package/src/layouts/index.ts +20 -1
- package/src/layouts/split.css +33 -31
- package/src/layouts/tabbed.css +127 -74
- package/src/layouts/types.ts +19 -3
- package/src/layouts/ui.css +289 -0
- package/src/main.ts +39 -1
- package/src/project/configHelpers.ts +225 -0
- package/src/project/generatedLoader.ts +27 -6
- package/src/project/loadProject.ts +459 -173
- package/src/project/loaderHelper.ts +377 -130
- package/src/project/types.ts +360 -14
- package/src/styles/base.css +5 -1
- package/src/styles/theme.css +292 -0
- package/src/uniforms/UniformControls.ts +660 -0
- package/src/uniforms/UniformStore.ts +166 -0
- package/src/uniforms/UniformsPanel.ts +163 -0
- package/src/uniforms/index.ts +13 -0
- package/src/uniforms/uniform-controls.css +342 -0
- package/src/uniforms/uniforms-panel.css +277 -0
- package/templates/shaders/example-buffer/config.json +1 -0
- package/dist-lib/engine/ShadertoyEngine.d.ts.map +0 -1
- package/dist-lib/engine/ShadertoyEngine.js +0 -704
- package/src/engine/ShadertoyEngine.ts +0 -929
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UniformStore - Simple state manager for custom uniforms
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates uniform values and provides clean get/set interface.
|
|
5
|
+
* Used by the engine to manage uniform state.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
UniformDefinitions,
|
|
10
|
+
UniformDefinition,
|
|
11
|
+
UniformValue,
|
|
12
|
+
UniformValues,
|
|
13
|
+
isArrayUniform,
|
|
14
|
+
} from '../project/types';
|
|
15
|
+
|
|
16
|
+
import { tightFloatCount } from '../engine/std140';
|
|
17
|
+
|
|
18
|
+
export class UniformStore {
|
|
19
|
+
private definitions: UniformDefinitions;
|
|
20
|
+
private values: UniformValues = {};
|
|
21
|
+
|
|
22
|
+
constructor(definitions: UniformDefinitions) {
|
|
23
|
+
this.definitions = definitions;
|
|
24
|
+
this.initializeDefaults();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Initialize all values to their definition defaults.
|
|
29
|
+
*/
|
|
30
|
+
private initializeDefaults(): void {
|
|
31
|
+
for (const [name, def] of Object.entries(this.definitions)) {
|
|
32
|
+
if (isArrayUniform(def)) {
|
|
33
|
+
// Array uniforms initialize to zeroed Float32Array
|
|
34
|
+
this.values[name] = new Float32Array(tightFloatCount(def.type, def.count));
|
|
35
|
+
} else {
|
|
36
|
+
this.values[name] = this.cloneValue((def as { value: UniformValue }).value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Clone a value to avoid mutation of arrays.
|
|
43
|
+
*/
|
|
44
|
+
private cloneValue(value: UniformValue): UniformValue {
|
|
45
|
+
if (value instanceof Float32Array) return value.slice();
|
|
46
|
+
return Array.isArray(value) ? [...value] : value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the definition for a uniform.
|
|
51
|
+
*/
|
|
52
|
+
getDefinition(name: string): UniformDefinition | undefined {
|
|
53
|
+
return this.definitions[name];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get all definitions.
|
|
58
|
+
*/
|
|
59
|
+
getDefinitions(): UniformDefinitions {
|
|
60
|
+
return this.definitions;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if a uniform exists.
|
|
65
|
+
*/
|
|
66
|
+
has(name: string): boolean {
|
|
67
|
+
return name in this.definitions;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get the current value of a uniform.
|
|
72
|
+
*/
|
|
73
|
+
get(name: string): UniformValue | undefined {
|
|
74
|
+
return this.values[name];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get all current values (returns a shallow copy).
|
|
79
|
+
*/
|
|
80
|
+
getAll(): UniformValues {
|
|
81
|
+
const result: UniformValues = {};
|
|
82
|
+
for (const [name, value] of Object.entries(this.values)) {
|
|
83
|
+
result[name] = this.cloneValue(value);
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Set the value of a uniform.
|
|
90
|
+
* Returns true if the value was set, false if the uniform doesn't exist.
|
|
91
|
+
*/
|
|
92
|
+
set(name: string, value: UniformValue): boolean {
|
|
93
|
+
if (!this.has(name)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
this.values[name] = this.cloneValue(value);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Set multiple values at once.
|
|
102
|
+
*/
|
|
103
|
+
setAll(values: Partial<UniformValues>): void {
|
|
104
|
+
for (const [name, value] of Object.entries(values)) {
|
|
105
|
+
if (value !== undefined) {
|
|
106
|
+
this.set(name, value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Reset a single uniform to its default value.
|
|
113
|
+
*/
|
|
114
|
+
reset(name: string): boolean {
|
|
115
|
+
const def = this.definitions[name];
|
|
116
|
+
if (!def) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (isArrayUniform(def)) {
|
|
120
|
+
this.values[name] = new Float32Array(tightFloatCount(def.type, def.count));
|
|
121
|
+
} else {
|
|
122
|
+
this.values[name] = this.cloneValue(def.value);
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Reset all uniforms to their default values.
|
|
129
|
+
*/
|
|
130
|
+
resetAll(): void {
|
|
131
|
+
this.initializeDefaults();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get the default value for a uniform.
|
|
136
|
+
*/
|
|
137
|
+
getDefault(name: string): UniformValue | undefined {
|
|
138
|
+
const def = this.definitions[name];
|
|
139
|
+
if (!def) return undefined;
|
|
140
|
+
if (isArrayUniform(def)) return new Float32Array(tightFloatCount(def.type, def.count));
|
|
141
|
+
return this.cloneValue(def.value);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Iterate over all uniforms (name, definition, current value).
|
|
146
|
+
*/
|
|
147
|
+
*entries(): IterableIterator<[string, UniformDefinition, UniformValue]> {
|
|
148
|
+
for (const [name, def] of Object.entries(this.definitions)) {
|
|
149
|
+
yield [name, def, this.values[name]];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get the number of uniforms.
|
|
155
|
+
*/
|
|
156
|
+
get size(): number {
|
|
157
|
+
return Object.keys(this.definitions).length;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Check if there are any uniforms.
|
|
162
|
+
*/
|
|
163
|
+
get isEmpty(): boolean {
|
|
164
|
+
return this.size === 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uniforms Panel - Floating overlay for uniform controls
|
|
3
|
+
*
|
|
4
|
+
* A compact panel that floats on the right side of the canvas.
|
|
5
|
+
* Includes a toggle button to show/hide. Starts closed by default.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import './uniforms-panel.css';
|
|
9
|
+
|
|
10
|
+
import { UniformDefinitions, UniformValue, UniformValues, hasUIControl } from '../project/types';
|
|
11
|
+
import { UniformControls } from './UniformControls';
|
|
12
|
+
|
|
13
|
+
export interface UniformsPanelOptions {
|
|
14
|
+
/** Parent container to attach the panel to */
|
|
15
|
+
container: HTMLElement;
|
|
16
|
+
/** Uniform definitions from project */
|
|
17
|
+
uniforms: UniformDefinitions;
|
|
18
|
+
/** Callback when uniform value changes */
|
|
19
|
+
onChange: (name: string, value: UniformValue) => void;
|
|
20
|
+
/** Initial values (optional) */
|
|
21
|
+
initialValues?: UniformValues;
|
|
22
|
+
/** Start with panel open (default: false) */
|
|
23
|
+
startOpen?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class UniformsPanel {
|
|
27
|
+
private wrapper: HTMLElement;
|
|
28
|
+
private panel: HTMLElement;
|
|
29
|
+
private toggleButton: HTMLElement;
|
|
30
|
+
private controls: UniformControls | null = null;
|
|
31
|
+
private isOpen: boolean;
|
|
32
|
+
|
|
33
|
+
constructor(opts: UniformsPanelOptions) {
|
|
34
|
+
this.isOpen = opts.startOpen ?? false;
|
|
35
|
+
|
|
36
|
+
// Create wrapper for both toggle button and panel
|
|
37
|
+
this.wrapper = document.createElement('div');
|
|
38
|
+
this.wrapper.className = 'uniforms-panel-wrapper';
|
|
39
|
+
|
|
40
|
+
// Create toggle button (always visible)
|
|
41
|
+
this.toggleButton = document.createElement('button');
|
|
42
|
+
this.toggleButton.className = 'uniforms-toggle-button';
|
|
43
|
+
this.toggleButton.title = 'Toggle Uniforms Panel';
|
|
44
|
+
this.toggleButton.innerHTML = `
|
|
45
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
46
|
+
<line x1="4" y1="21" x2="4" y2="14"></line>
|
|
47
|
+
<line x1="4" y1="10" x2="4" y2="3"></line>
|
|
48
|
+
<line x1="12" y1="21" x2="12" y2="12"></line>
|
|
49
|
+
<line x1="12" y1="8" x2="12" y2="3"></line>
|
|
50
|
+
<line x1="20" y1="21" x2="20" y2="16"></line>
|
|
51
|
+
<line x1="20" y1="12" x2="20" y2="3"></line>
|
|
52
|
+
<line x1="1" y1="14" x2="7" y2="14"></line>
|
|
53
|
+
<line x1="9" y1="8" x2="15" y2="8"></line>
|
|
54
|
+
<line x1="17" y1="16" x2="23" y2="16"></line>
|
|
55
|
+
</svg>
|
|
56
|
+
`;
|
|
57
|
+
this.toggleButton.addEventListener('click', () => this.toggle());
|
|
58
|
+
this.wrapper.appendChild(this.toggleButton);
|
|
59
|
+
|
|
60
|
+
// Create panel element
|
|
61
|
+
this.panel = document.createElement('div');
|
|
62
|
+
this.panel.className = 'uniforms-panel';
|
|
63
|
+
|
|
64
|
+
// Only create content if there are visible uniforms
|
|
65
|
+
const hasVisible = Object.values(opts.uniforms).some(def => hasUIControl(def));
|
|
66
|
+
if (!hasVisible) {
|
|
67
|
+
this.wrapper.style.display = 'none';
|
|
68
|
+
opts.container.appendChild(this.wrapper);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Header with close button
|
|
73
|
+
const header = document.createElement('div');
|
|
74
|
+
header.className = 'uniforms-panel-header';
|
|
75
|
+
|
|
76
|
+
const title = document.createElement('span');
|
|
77
|
+
title.textContent = 'Uniforms';
|
|
78
|
+
header.appendChild(title);
|
|
79
|
+
|
|
80
|
+
const closeButton = document.createElement('button');
|
|
81
|
+
closeButton.className = 'uniforms-panel-close';
|
|
82
|
+
closeButton.innerHTML = '×';
|
|
83
|
+
closeButton.title = 'Close';
|
|
84
|
+
closeButton.addEventListener('click', () => this.hide());
|
|
85
|
+
header.appendChild(closeButton);
|
|
86
|
+
|
|
87
|
+
this.panel.appendChild(header);
|
|
88
|
+
|
|
89
|
+
// Content area for controls
|
|
90
|
+
const content = document.createElement('div');
|
|
91
|
+
content.className = 'uniforms-panel-content';
|
|
92
|
+
this.panel.appendChild(content);
|
|
93
|
+
|
|
94
|
+
// Create uniform controls
|
|
95
|
+
this.controls = new UniformControls({
|
|
96
|
+
container: content,
|
|
97
|
+
uniforms: opts.uniforms,
|
|
98
|
+
initialValues: opts.initialValues,
|
|
99
|
+
onChange: opts.onChange,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.wrapper.appendChild(this.panel);
|
|
103
|
+
|
|
104
|
+
// Set initial state
|
|
105
|
+
if (!this.isOpen) {
|
|
106
|
+
this.panel.classList.add('closed');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Append to container
|
|
110
|
+
opts.container.appendChild(this.wrapper);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Update a uniform value from external source.
|
|
115
|
+
*/
|
|
116
|
+
setValue(name: string, value: UniformValue): void {
|
|
117
|
+
this.controls?.setValue(name, value);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Show the panel.
|
|
122
|
+
*/
|
|
123
|
+
show(): void {
|
|
124
|
+
this.isOpen = true;
|
|
125
|
+
this.toggleButton.classList.add('hidden');
|
|
126
|
+
this.panel.classList.remove('closed');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Hide the panel.
|
|
131
|
+
*/
|
|
132
|
+
hide(): void {
|
|
133
|
+
this.isOpen = false;
|
|
134
|
+
this.panel.classList.add('closed');
|
|
135
|
+
this.toggleButton.classList.remove('hidden');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Toggle panel visibility.
|
|
140
|
+
*/
|
|
141
|
+
toggle(): void {
|
|
142
|
+
if (this.isOpen) {
|
|
143
|
+
this.hide();
|
|
144
|
+
} else {
|
|
145
|
+
this.show();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check if panel is visible.
|
|
151
|
+
*/
|
|
152
|
+
isVisible(): boolean {
|
|
153
|
+
return this.isOpen;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Destroy the panel.
|
|
158
|
+
*/
|
|
159
|
+
destroy(): void {
|
|
160
|
+
this.controls?.destroy();
|
|
161
|
+
this.wrapper.remove();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uniform Controls Module
|
|
3
|
+
*
|
|
4
|
+
* UI components and state management for custom uniforms.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { UniformControls } from './UniformControls';
|
|
8
|
+
export type { UniformControlsOptions } from './UniformControls';
|
|
9
|
+
|
|
10
|
+
export { UniformsPanel } from './UniformsPanel';
|
|
11
|
+
export type { UniformsPanelOptions } from './UniformsPanel';
|
|
12
|
+
|
|
13
|
+
export { UniformStore } from './UniformStore';
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uniform Controls Styles
|
|
3
|
+
* Theme-aware styling for uniform sliders and controls
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.uniform-controls {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: 16px;
|
|
10
|
+
padding: 16px;
|
|
11
|
+
height: 100%;
|
|
12
|
+
overflow-y: auto;
|
|
13
|
+
background: var(--bg-secondary);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.uniform-controls-empty {
|
|
17
|
+
color: var(--text-muted);
|
|
18
|
+
font-size: 13px;
|
|
19
|
+
text-align: center;
|
|
20
|
+
padding: 20px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Header with reset button */
|
|
24
|
+
.uniform-controls-header {
|
|
25
|
+
display: flex;
|
|
26
|
+
justify-content: flex-end;
|
|
27
|
+
padding-bottom: 8px;
|
|
28
|
+
border-bottom: 1px solid var(--border-primary);
|
|
29
|
+
margin-bottom: 8px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.uniform-controls-reset {
|
|
33
|
+
font-family: inherit;
|
|
34
|
+
font-size: 11px;
|
|
35
|
+
padding: 4px 10px;
|
|
36
|
+
background: var(--bg-tertiary);
|
|
37
|
+
color: var(--text-secondary);
|
|
38
|
+
border: 1px solid var(--border-primary);
|
|
39
|
+
border-radius: 4px;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.uniform-controls-reset:hover {
|
|
45
|
+
background: var(--border-primary);
|
|
46
|
+
color: var(--text-primary);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.uniform-controls-reset:active {
|
|
50
|
+
transform: translateY(1px);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Control list container */
|
|
54
|
+
.uniform-controls-list {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
gap: 16px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Individual control wrapper */
|
|
61
|
+
.uniform-control {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: 8px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Label row with name and value */
|
|
68
|
+
.uniform-control-label-row {
|
|
69
|
+
display: flex;
|
|
70
|
+
justify-content: space-between;
|
|
71
|
+
align-items: center;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.uniform-control-label {
|
|
75
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
76
|
+
font-size: 12px;
|
|
77
|
+
font-weight: 500;
|
|
78
|
+
color: var(--text-primary);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.uniform-control-value {
|
|
82
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
83
|
+
font-size: 11px;
|
|
84
|
+
color: var(--text-muted);
|
|
85
|
+
background: var(--bg-tertiary);
|
|
86
|
+
padding: 2px 6px;
|
|
87
|
+
border-radius: 3px;
|
|
88
|
+
min-width: 50px;
|
|
89
|
+
text-align: right;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Slider styling */
|
|
93
|
+
.uniform-control-slider {
|
|
94
|
+
-webkit-appearance: none;
|
|
95
|
+
appearance: none;
|
|
96
|
+
width: 100%;
|
|
97
|
+
height: 6px;
|
|
98
|
+
background: var(--border-primary);
|
|
99
|
+
border-radius: 3px;
|
|
100
|
+
outline: none;
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Slider track - WebKit */
|
|
105
|
+
.uniform-control-slider::-webkit-slider-runnable-track {
|
|
106
|
+
height: 6px;
|
|
107
|
+
background: var(--border-primary);
|
|
108
|
+
border-radius: 3px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Slider thumb - WebKit */
|
|
112
|
+
.uniform-control-slider::-webkit-slider-thumb {
|
|
113
|
+
-webkit-appearance: none;
|
|
114
|
+
appearance: none;
|
|
115
|
+
width: 14px;
|
|
116
|
+
height: 14px;
|
|
117
|
+
background: var(--accent-primary);
|
|
118
|
+
border-radius: 50%;
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
margin-top: -4px;
|
|
121
|
+
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.uniform-control-slider::-webkit-slider-thumb:hover {
|
|
125
|
+
transform: scale(1.1);
|
|
126
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.uniform-control-slider::-webkit-slider-thumb:active {
|
|
130
|
+
transform: scale(0.95);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Slider track - Firefox */
|
|
134
|
+
.uniform-control-slider::-moz-range-track {
|
|
135
|
+
height: 6px;
|
|
136
|
+
background: var(--border-primary);
|
|
137
|
+
border-radius: 3px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Slider thumb - Firefox */
|
|
141
|
+
.uniform-control-slider::-moz-range-thumb {
|
|
142
|
+
width: 14px;
|
|
143
|
+
height: 14px;
|
|
144
|
+
background: var(--accent-primary);
|
|
145
|
+
border: none;
|
|
146
|
+
border-radius: 50%;
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.uniform-control-slider::-moz-range-thumb:hover {
|
|
152
|
+
transform: scale(1.1);
|
|
153
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.uniform-control-slider::-moz-range-thumb:active {
|
|
157
|
+
transform: scale(0.95);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Focus state */
|
|
161
|
+
.uniform-control-slider:focus {
|
|
162
|
+
outline: none;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.uniform-control-slider:focus::-webkit-slider-thumb {
|
|
166
|
+
box-shadow: 0 0 0 3px var(--code-selection);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.uniform-control-slider:focus::-moz-range-thumb {
|
|
170
|
+
box-shadow: 0 0 0 3px var(--code-selection);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* ===========================================================================
|
|
174
|
+
* Bool Toggle Switch
|
|
175
|
+
* =========================================================================== */
|
|
176
|
+
|
|
177
|
+
.uniform-control-toggle {
|
|
178
|
+
position: relative;
|
|
179
|
+
display: inline-block;
|
|
180
|
+
width: 40px;
|
|
181
|
+
height: 22px;
|
|
182
|
+
cursor: pointer;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.uniform-control-toggle input {
|
|
186
|
+
opacity: 0;
|
|
187
|
+
width: 0;
|
|
188
|
+
height: 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.uniform-control-toggle-slider {
|
|
192
|
+
position: absolute;
|
|
193
|
+
top: 0;
|
|
194
|
+
left: 0;
|
|
195
|
+
right: 0;
|
|
196
|
+
bottom: 0;
|
|
197
|
+
background: var(--border-primary);
|
|
198
|
+
border-radius: 22px;
|
|
199
|
+
transition: background 0.2s ease;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.uniform-control-toggle-slider::before {
|
|
203
|
+
content: '';
|
|
204
|
+
position: absolute;
|
|
205
|
+
width: 16px;
|
|
206
|
+
height: 16px;
|
|
207
|
+
left: 3px;
|
|
208
|
+
bottom: 3px;
|
|
209
|
+
background: var(--text-muted);
|
|
210
|
+
border-radius: 50%;
|
|
211
|
+
transition: transform 0.2s ease, background 0.2s ease;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.uniform-control-toggle input:checked + .uniform-control-toggle-slider {
|
|
215
|
+
background: var(--accent-primary);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.uniform-control-toggle input:checked + .uniform-control-toggle-slider::before {
|
|
219
|
+
transform: translateX(18px);
|
|
220
|
+
background: white;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.uniform-control-toggle input:focus + .uniform-control-toggle-slider {
|
|
224
|
+
box-shadow: 0 0 0 2px var(--code-selection);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* ===========================================================================
|
|
228
|
+
* Vec2 XY Pad
|
|
229
|
+
* =========================================================================== */
|
|
230
|
+
|
|
231
|
+
.uniform-control-xy-pad {
|
|
232
|
+
position: relative;
|
|
233
|
+
width: 100%;
|
|
234
|
+
height: 120px;
|
|
235
|
+
background: var(--bg-tertiary);
|
|
236
|
+
border: 1px solid var(--border-primary);
|
|
237
|
+
border-radius: 4px;
|
|
238
|
+
cursor: crosshair;
|
|
239
|
+
overflow: hidden;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* Grid lines */
|
|
243
|
+
.uniform-control-xy-pad::before,
|
|
244
|
+
.uniform-control-xy-pad::after {
|
|
245
|
+
content: '';
|
|
246
|
+
position: absolute;
|
|
247
|
+
background: var(--border-primary);
|
|
248
|
+
opacity: 0.5;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.uniform-control-xy-pad::before {
|
|
252
|
+
/* Vertical center line */
|
|
253
|
+
left: 50%;
|
|
254
|
+
top: 0;
|
|
255
|
+
bottom: 0;
|
|
256
|
+
width: 1px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.uniform-control-xy-pad::after {
|
|
260
|
+
/* Horizontal center line */
|
|
261
|
+
top: 50%;
|
|
262
|
+
left: 0;
|
|
263
|
+
right: 0;
|
|
264
|
+
height: 1px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.uniform-control-xy-handle {
|
|
268
|
+
position: absolute;
|
|
269
|
+
width: 14px;
|
|
270
|
+
height: 14px;
|
|
271
|
+
background: var(--accent-primary);
|
|
272
|
+
border: 2px solid white;
|
|
273
|
+
border-radius: 50%;
|
|
274
|
+
transform: translate(-50%, -50%);
|
|
275
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
276
|
+
pointer-events: none;
|
|
277
|
+
z-index: 1;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* ===========================================================================
|
|
281
|
+
* Vec3 Color Picker
|
|
282
|
+
* =========================================================================== */
|
|
283
|
+
|
|
284
|
+
.uniform-control-color-wrapper {
|
|
285
|
+
display: flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
gap: 8px;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.uniform-control-color-swatch {
|
|
291
|
+
width: 100%;
|
|
292
|
+
height: 32px;
|
|
293
|
+
border-radius: 4px;
|
|
294
|
+
border: 1px solid var(--border-primary);
|
|
295
|
+
cursor: pointer;
|
|
296
|
+
transition: box-shadow 0.15s ease;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.uniform-control-color-swatch:hover {
|
|
300
|
+
box-shadow: 0 0 0 2px var(--accent-primary);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.uniform-control-color-input {
|
|
304
|
+
/* Hide the native input but keep it functional */
|
|
305
|
+
position: absolute;
|
|
306
|
+
width: 0;
|
|
307
|
+
height: 0;
|
|
308
|
+
opacity: 0;
|
|
309
|
+
pointer-events: none;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* ===========================================================================
|
|
313
|
+
* Vec3 Sliders (non-color)
|
|
314
|
+
* =========================================================================== */
|
|
315
|
+
|
|
316
|
+
.uniform-control-vec3 {
|
|
317
|
+
gap: 6px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.uniform-control-vec-slider-row {
|
|
321
|
+
display: flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
gap: 8px;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.uniform-control-vec-component {
|
|
327
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
328
|
+
font-size: 10px;
|
|
329
|
+
font-weight: 600;
|
|
330
|
+
color: var(--text-muted);
|
|
331
|
+
width: 14px;
|
|
332
|
+
text-align: center;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.uniform-control-vec-slider {
|
|
336
|
+
flex: 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.uniform-control-vec-value {
|
|
340
|
+
min-width: 40px;
|
|
341
|
+
font-size: 10px;
|
|
342
|
+
}
|