Skip to content

Commit fcaa00b

Browse files
committed
feat: implement live-editing slider
1 parent 381498f commit fcaa00b

File tree

3 files changed

+195
-16
lines changed

3 files changed

+195
-16
lines changed

pages/live-coding.html

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,15 @@
3434
<main id="app" class="live-coding-main">
3535
<canvas id="offscreenCanvas" style="display: none"></canvas>
3636
<textarea id="live-coding-editor" spellcheck="false"> </textarea>
37-
<div id="slider-popup">
38-
<label for="slider">Adjust value: </label>
39-
<input
40-
type="range"
41-
id="slider"
42-
min="0"
43-
max="1"
44-
step="0.1"
45-
value="0.3"
46-
/>
47-
</div>
4837
</main>
4938
</div>
5039

40+
<div id="slider-popup">
41+
<label for="slider">Adjust value: </label>
42+
<input type="range" id="slider" min="0" max="1" step="0.1" value="0.3" />
43+
<span id="slider-value"></span>
44+
</div>
45+
5146
<div id="live-coding-controls">
5247
<button id="run-button">Run</button>
5348
<div id="preset-container">

pages/live-coding.js

Lines changed: 155 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ document.addEventListener("DOMContentLoaded", () => {
2020
const closeHelpButton = document.getElementById("close-help");
2121
const slider = document.getElementById("slider");
2222
const sliderPopup = document.getElementById("slider-popup");
23+
const sliderValueLabel = document.getElementById("slider-value");
24+
25+
let activePattern = null;
2326

2427
const maxRows = Math.floor(editor.getBoundingClientRect().height / 16); // Assuming 16px line height
2528
const maxCols = Math.floor(editor.getBoundingClientRect().width / 10); // Assuming 10px character width
@@ -122,14 +125,151 @@ document.addEventListener("DOMContentLoaded", () => {
122125
min = Math.min(val1, val2);
123126
max = Math.max(val1, val2);
124127
const numericValue = convertHexOrFloat(value);
125-
console.log(`Minimum Value (min): ${min}`);
126-
console.log(`Maximum Value (max): ${max}`);
127-
console.log("Numeric Value:", numericValue);
128+
// console.log(`Minimum Value (min): ${min}`);
129+
// console.log(`Maximum Value (max): ${max}`);
130+
// console.log("Numeric Value:", numericValue);
128131
}
129132
}
130133
}
131134
}
132135
});
136+
editor.addEventListener("click", (event) => {
137+
const patternInfo = getPatternAtPosition(event);
138+
if (patternInfo) {
139+
activePattern = patternInfo;
140+
const { min, max, numericValue, event: evt } = patternInfo;
141+
142+
slider.min = min;
143+
slider.max = max;
144+
// For float values, we need to set a step
145+
if (max === 1 && min === 0) {
146+
slider.step = 0.1;
147+
} else if (
148+
Number.isFinite(min) &&
149+
Number.isFinite(max) &&
150+
(min % 1 !== 0 || max % 1 !== 0)
151+
) {
152+
slider.step = (max - min) / 10;
153+
} else {
154+
slider.step = 1;
155+
}
156+
slider.value = numericValue;
157+
sliderValueLabel.textContent = numericValue;
158+
159+
sliderPopup.style.display = "block";
160+
sliderPopup.style.left = `${event.pageX + 10}px`;
161+
sliderPopup.style.top = `${event.pageY}px`;
162+
} else {
163+
sliderPopup.style.display = "none";
164+
activePattern = null;
165+
}
166+
});
167+
168+
slider.addEventListener("input", () => {
169+
if (activePattern) {
170+
const { originalValue, start, end, lineIndex } = activePattern;
171+
let newValue = slider.value;
172+
173+
// Preserve original format (e.g. float with specific precision)
174+
if (originalValue.includes(".")) {
175+
const precision = (originalValue.split(".")[1] || "").length;
176+
newValue = parseFloat(newValue).toFixed(precision);
177+
}
178+
179+
sliderValueLabel.textContent = newValue;
180+
181+
const lines = editor.value.split("\n");
182+
const line = lines[lineIndex];
183+
184+
const updatedLine =
185+
line.substring(0, start) + newValue + line.substring(end);
186+
lines[lineIndex] = updatedLine;
187+
188+
// To avoid losing cursor position, we can try to restore it.
189+
const { selectionStart, selectionEnd } = editor;
190+
editor.value = lines.join("\n");
191+
192+
// After updating the value, we need to update the end position for the active pattern
193+
activePattern.end = start + String(newValue).length;
194+
195+
// Restore cursor position
196+
editor.setSelectionRange(selectionStart, selectionEnd);
197+
198+
if (renderer.run) {
199+
renderer.run();
200+
}
201+
}
202+
});
203+
204+
function getPatternAtPosition(event) {
205+
const textarea = editor;
206+
const rect = textarea.getBoundingClientRect();
207+
const mouseX = event.clientX - rect.left;
208+
const mouseY = event.clientY - rect.top;
209+
210+
const adjustedMouseY = mouseY + textarea.scrollTop;
211+
const lineHeight = 20;
212+
const targetRow = Math.floor(adjustedMouseY / lineHeight);
213+
214+
const lines = textarea.value.split("\n");
215+
if (targetRow >= lines.length) return null;
216+
217+
const line = lines[targetRow];
218+
const charWidth = ctx.measureText("M").width;
219+
let charIndex = Math.floor(mouseX / charWidth);
220+
charIndex = Math.max(0, Math.min(charIndex, line.length - 1));
221+
222+
let start = -1,
223+
end = -1;
224+
225+
// Find word boundaries
226+
for (let i = charIndex; i >= 0; i--) {
227+
if (/\s/.test(line[i])) break;
228+
start = i;
229+
}
230+
for (let i = charIndex; i < line.length; i++) {
231+
if (/\s/.test(line[i])) break;
232+
end = i + 1;
233+
}
234+
235+
if (start === -1 || end === -1) return null;
236+
237+
const word = line.substring(start, end);
238+
const regex =
239+
/([-+]?(?:0x[a-fA-F0-9]+|#?[a-fA-F0-9]+|\d*\.?\d+))\s*\/\*\[(.*?)\]\*\//;
240+
const match = line.match(regex);
241+
242+
if (match) {
243+
const matchedValue = match[1];
244+
const rangeString = match[2];
245+
const valueIndex = line.indexOf(matchedValue);
246+
const valueEnd = valueIndex + matchedValue.length;
247+
248+
const rangeMatch = rangeString.match(
249+
/^\s*([-+]?\d*\.?\d+)\s*\.\.\s*([-+]?\d*\.?\d+)\s*$/
250+
);
251+
252+
if (rangeMatch) {
253+
const min = convertHexOrFloat(rangeMatch[1]);
254+
const max = convertHexOrFloat(rangeMatch[2]);
255+
const numericValue = convertHexOrFloat(matchedValue);
256+
257+
return {
258+
originalValue: matchedValue,
259+
numericValue,
260+
min,
261+
max,
262+
start: valueIndex,
263+
end: valueEnd,
264+
lineIndex: targetRow,
265+
event: event,
266+
};
267+
}
268+
}
269+
270+
return null;
271+
}
272+
133273
function convertHexOrFloat(valString) {
134274
if (valString.startsWith("0x")) {
135275
// Handle 0x hex format (e.g., 0xFF)
@@ -144,8 +284,18 @@ document.addEventListener("DOMContentLoaded", () => {
144284
}
145285
}
146286

147-
editor.addEventListener("mouseleave", () => {
148-
sliderPopup.style.display = "none"; // Hide the slider when mouse leaves
287+
editor.addEventListener("mouseleave", (e) => {
288+
// If the mouse is not moving to the slider popup, hide it.
289+
if (!sliderPopup.contains(e.relatedTarget)) {
290+
sliderPopup.style.display = "none";
291+
}
292+
});
293+
294+
sliderPopup.addEventListener("mouseleave", (e) => {
295+
// If the mouse is not moving back into the editor, hide the popup.
296+
if (e.relatedTarget !== editor) {
297+
sliderPopup.style.display = "none";
298+
}
149299
});
150300

151301
// --- State ---

style.css

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,40 @@ select:hover {
412412
display: none;
413413
}
414414

415+
#slider-popup {
416+
display: none;
417+
position: absolute;
418+
background: rgba(20, 20, 20, 0.8);
419+
border: 1px solid #0f0;
420+
padding: 15px;
421+
z-index: 100;
422+
border-radius: 8px;
423+
box-shadow: 0 0 15px rgba(0, 255, 0, 0.6);
424+
color: #0f0;
425+
font-family: "VT323", monospace;
426+
backdrop-filter: blur(5px);
427+
-webkit-backdrop-filter: blur(5px);
428+
font-size: 1.2rem;
429+
}
430+
431+
#slider-popup label {
432+
margin-right: 10px;
433+
vertical-align: middle;
434+
}
435+
436+
#slider-popup input[type="range"] {
437+
vertical-align: middle;
438+
/* Custom styling for the range input to match the theme can be added here */
439+
}
440+
441+
#slider-popup #slider-value {
442+
margin-left: 10px;
443+
font-weight: bold;
444+
display: inline-block;
445+
min-width: 40px; /* Ensure space for the value */
446+
text-align: right;
447+
}
448+
415449
/* Responsive Design */
416450
@media (max-width: 768px) {
417451
.menu-toggle {

0 commit comments

Comments
 (0)