Skip to content

Commit 37cde6d

Browse files
authored
Added Pie Menu for Lego Bricks (#4755)
* pie-menu-added * minor lint fixes
1 parent 8f113a3 commit 37cde6d

File tree

1 file changed

+202
-51
lines changed

1 file changed

+202
-51
lines changed

js/widgets/legobricks.js

Lines changed: 202 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
exported LegoWidget
33
*/
44

5+
/*
6+
global
7+
8+
_, piemenuVoices, docById, platformColor, noteToFrequency
9+
*/
10+
511
/**
612
* Represents a LEGO Bricks Widget with Phrase Maker functionality.
713
* @constructor
@@ -41,6 +47,7 @@ function LegoWidget() {
4147
this.imageWrapper = null;
4248
this.synth = null;
4349
this.selectedInstrument = "electronic synth";
50+
this.hasGeneratedVisualization = false; // Flag to prevent double PNG downloads
4451

4552
// Pitch block handling properties (similar to PhraseMaker)
4653
this.blockNo = null;
@@ -490,52 +497,22 @@ function LegoWidget() {
490497
this.zoomControls.style.gap = "8px";
491498
this.zoomControls.style.zIndex = "20"; // Ensure it's above the grid
492499

493-
// Instrument selector
500+
// Instrument selector (pie menu button)
494501
const instrumentLabel = document.createElement("span");
495502
instrumentLabel.textContent = "Instrument:";
496503
instrumentLabel.style.fontSize = "12px";
497504
instrumentLabel.style.fontWeight = "bold";
498505

499-
this.instrumentSelect = document.createElement("select");
500-
this.instrumentSelect.style.fontSize = "12px";
501-
this.instrumentSelect.style.marginRight = "16px";
502-
503-
// Add instrument options
504-
const instruments = [
505-
"electronic synth",
506-
"piano",
507-
"guitar",
508-
"acoustic guitar",
509-
"electric guitar",
510-
"violin",
511-
"viola",
512-
"cello",
513-
"bass",
514-
"flute",
515-
"clarinet",
516-
"saxophone",
517-
"trumpet",
518-
"trombone",
519-
"oboe",
520-
"tuba",
521-
"banjo",
522-
"sine",
523-
"square",
524-
"sawtooth",
525-
"triangle"
526-
];
527-
528-
instruments.forEach(instrument => {
529-
const option = document.createElement("option");
530-
option.value = instrument;
531-
option.textContent = instrument.charAt(0).toUpperCase() + instrument.slice(1);
532-
if (instrument === this.selectedInstrument) {
533-
option.selected = true;
534-
}
535-
this.instrumentSelect.appendChild(option);
536-
});
537-
538-
this.instrumentSelect.onchange = () => this._changeInstrument();
506+
this.instrumentButton = document.createElement("button");
507+
this.instrumentButton.textContent = this.selectedInstrument.charAt(0).toUpperCase() + this.selectedInstrument.slice(1);
508+
this.instrumentButton.style.fontSize = "12px";
509+
this.instrumentButton.style.marginRight = "16px";
510+
this.instrumentButton.style.padding = "4px 8px";
511+
this.instrumentButton.style.border = "1px solid #ccc";
512+
this.instrumentButton.style.borderRadius = "4px";
513+
this.instrumentButton.style.backgroundColor = "#f8f8f8";
514+
this.instrumentButton.style.cursor = "pointer";
515+
this.instrumentButton.onclick = () => this._createInstrumentPieMenu();
539516

540517
const zoomLabel = document.createElement("span");
541518
zoomLabel.textContent = "Zoom:";
@@ -597,7 +574,7 @@ function LegoWidget() {
597574
this.spacingValue.style.minWidth = "40px";
598575

599576
this.zoomControls.appendChild(instrumentLabel);
600-
this.zoomControls.appendChild(this.instrumentSelect);
577+
this.zoomControls.appendChild(this.instrumentButton);
601578
this.zoomControls.appendChild(zoomLabel);
602579
this.zoomControls.appendChild(zoomOut);
603580
this.zoomControls.appendChild(this.zoomSlider);
@@ -826,6 +803,7 @@ function LegoWidget() {
826803
else if (duration < 3000) noteValue = 2; // half note (1500-3000ms)
827804
else noteValue = 1; // whole note (3000ms+)
828805
let hasNonGreenColor = false;
806+
let pitches = []; // Array to collect pitches for this time column
829807

830808
// Check each row for non-green colors in this time range
831809
this.colorData.forEach((rowData, rowIndex) => {
@@ -1391,6 +1369,169 @@ function LegoWidget() {
13911369
this.activity.textMsg(_("Instrument changed to: ") + this.selectedInstrument);
13921370
};
13931371

1372+
/**
1373+
* Creates a pie menu for instrument selection.
1374+
* @private
1375+
* @returns {void}
1376+
*/
1377+
this._createInstrumentPieMenu = function() {
1378+
// Define instrument options
1379+
const voiceLabels = [
1380+
_("Electronic Synth"),
1381+
_("Piano"),
1382+
_("Guitar"),
1383+
_("Acoustic Guitar"),
1384+
_("Electric Guitar"),
1385+
_("Violin"),
1386+
_("Viola"),
1387+
_("Cello"),
1388+
_("Bass"),
1389+
_("Flute"),
1390+
_("Clarinet"),
1391+
_("Saxophone"),
1392+
_("Trumpet"),
1393+
_("Trombone"),
1394+
_("Oboe"),
1395+
_("Tuba"),
1396+
_("Banjo"),
1397+
_("Sine"),
1398+
_("Square"),
1399+
_("Sawtooth"),
1400+
_("Triangle")
1401+
];
1402+
1403+
const voiceValues = [
1404+
"electronic synth",
1405+
"piano",
1406+
"guitar",
1407+
"acoustic guitar",
1408+
"electric guitar",
1409+
"violin",
1410+
"viola",
1411+
"cello",
1412+
"bass",
1413+
"flute",
1414+
"clarinet",
1415+
"saxophone",
1416+
"trumpet",
1417+
"trombone",
1418+
"oboe",
1419+
"tuba",
1420+
"banjo",
1421+
"sine",
1422+
"square",
1423+
"sawtooth",
1424+
"triangle"
1425+
];
1426+
1427+
const categories = []; // No categories needed for instruments
1428+
1429+
// Create a mock block object for the pie menu
1430+
const mockBlock = {
1431+
// Position the pie menu near the button
1432+
container: {
1433+
x: this.instrumentButton.offsetLeft + this.instrumentButton.offsetWidth / 2,
1434+
y: this.instrumentButton.offsetTop + this.instrumentButton.offsetHeight / 2,
1435+
children: [], // Mock children array for setChildIndex
1436+
setChildIndex: (child, index) => {} // Mock function
1437+
},
1438+
1439+
// Mock text object that the pie menu expects
1440+
text: {
1441+
_text: this.selectedInstrument,
1442+
get text() {
1443+
return this._text;
1444+
},
1445+
set text(value) {
1446+
this._text = value;
1447+
// Update the button text when the pie menu updates the text
1448+
if (this._updateCallback) {
1449+
this._updateCallback(value);
1450+
}
1451+
},
1452+
_updateCallback: null
1453+
},
1454+
1455+
value: this.selectedInstrument,
1456+
1457+
activity: {
1458+
canvas: {
1459+
offsetLeft: 0,
1460+
offsetTop: 0
1461+
},
1462+
blocksContainer: {
1463+
x: 0,
1464+
y: 0
1465+
},
1466+
getStageScale: () => 1,
1467+
logo: {
1468+
synth: this.synth
1469+
},
1470+
turtles: {
1471+
ithTurtle: (index) => {
1472+
return {
1473+
singer: {
1474+
instrumentNames: [this.selectedInstrument]
1475+
}
1476+
};
1477+
}
1478+
}
1479+
},
1480+
1481+
blocks: {
1482+
blockScale: 1,
1483+
turtles: {
1484+
_canvas: {
1485+
width: window.innerWidth,
1486+
height: window.innerHeight
1487+
}
1488+
}
1489+
},
1490+
1491+
// Mock methods needed by piemenu
1492+
updateCache: () => {},
1493+
updateValue: (newValue) => {
1494+
// Update the instrument when selection is made
1495+
this.selectedInstrument = newValue;
1496+
this.instrumentButton.textContent = newValue.charAt(0).toUpperCase() + newValue.slice(1);
1497+
1498+
// Recreate the synth with the new instrument
1499+
if (this.synth) {
1500+
this.synth.createSynth(0, this.selectedInstrument, this.selectedInstrument, null);
1501+
}
1502+
1503+
// Show a message indicating the instrument change
1504+
this.activity.textMsg(_("Instrument changed to: ") + this.selectedInstrument);
1505+
1506+
// Update the mock block's value and text
1507+
mockBlock.value = newValue;
1508+
mockBlock.text.text = newValue;
1509+
}
1510+
};
1511+
1512+
// Set up the text update callback to update our button
1513+
mockBlock.text._updateCallback = (newText) => {
1514+
// Update the instrument when text is set by pie menu
1515+
const newInstrument = voiceValues[voiceLabels.findIndex(label =>
1516+
label.toLowerCase() === newText.toLowerCase()
1517+
)] || newText.toLowerCase();
1518+
1519+
this.selectedInstrument = newInstrument;
1520+
this.instrumentButton.textContent = newInstrument.charAt(0).toUpperCase() + newInstrument.slice(1);
1521+
1522+
// Recreate the synth with the new instrument
1523+
if (this.synth) {
1524+
this.synth.createSynth(0, this.selectedInstrument, this.selectedInstrument, null);
1525+
}
1526+
1527+
// Show a message indicating the instrument change
1528+
this.activity.textMsg(_("Instrument changed to: ") + this.selectedInstrument);
1529+
};
1530+
1531+
// Call the pie menu function
1532+
piemenuVoices(mockBlock, voiceLabels, voiceValues, categories, this.selectedInstrument, false);
1533+
};
1534+
13941535

13951536
/**
13961537
* Handles zoom changes.
@@ -1506,6 +1647,9 @@ function LegoWidget() {
15061647
// Clear any existing animation
15071648
this._stopPlayback();
15081649
this.activity.textMsg(_("Scanning image with vertical lines..."));
1650+
1651+
// Reset the visualization flag to allow new download
1652+
this.hasGeneratedVisualization = false;
15091653

15101654
// Get all grid lines (sorted by position)
15111655
const gridLines = Array.from(this.gridOverlay.querySelectorAll("div"))
@@ -1701,7 +1845,7 @@ function LegoWidget() {
17011845
*/
17021846
this._stopPlayback = function() {
17031847
this.isPlaying = false;
1704-
1848+
17051849
// Save final color segments for all lines
17061850
if (this.scanningLines) {
17071851
const now = performance.now();
@@ -1721,15 +1865,22 @@ function LegoWidget() {
17211865
});
17221866
this.scanningLines = null;
17231867
}
1724-
1725-
// Generate color visualization PNG
1726-
setTimeout(() => {
1727-
this._generateColorVisualization();
1728-
this._drawColumnLinesOnCanvas(); // Draw column lines on the overlay
1729-
}, 100); // Small delay to ensure all data is processed
1730-
};
1731-
17321868

1869+
// Only generate color visualization PNG if scanning was actually completed and not generated yet
1870+
// This prevents the double download issue where _stopPlayback() is called at the start for cleanup
1871+
if (!this.hasGeneratedVisualization && this.colorData && this.colorData.length > 0) {
1872+
// Check if any colorData actually has color segments (indicating scanning occurred)
1873+
const hasScannedData = this.colorData.some(row => row.colorSegments && row.colorSegments.length > 0);
1874+
1875+
if (hasScannedData) {
1876+
this.hasGeneratedVisualization = true; // Set flag to prevent double generation
1877+
setTimeout(() => {
1878+
this._generateColorVisualization();
1879+
this._drawColumnLinesOnCanvas(); // Draw column lines on the overlay
1880+
}, 100); // Small delay to ensure all data is processed
1881+
}
1882+
}
1883+
};
17331884
/**
17341885
* Samples and detects colors along a vertical line
17351886
* @private

0 commit comments

Comments
 (0)