Skip to content

Commit 0ef47f8

Browse files
authored
Rhythm Maker: Replace Number Input with Pie Menu (#4998)
* Added a Pie menu for Rhythm Maker * Remove accidentally committed .DS_Store files * Enable Type Functionality for Rhythm Maker * Moved the Piemenu code to piemenus.js * Ignore macOS .DS_Store files
1 parent cf250ca commit 0ef47f8

File tree

4 files changed

+174
-46
lines changed

4 files changed

+174
-46
lines changed

.DS_Store

-6 KB
Binary file not shown.

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
node_modules/
22
*~
3-
coverage/
3+
coverage/
4+
.DS_Store
5+
**/.DS_Store

js/piemenus.js

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
piemenuModes, piemenuPitches, piemenuCustomNotes, piemenuGrid,
5858
piemenuBlockContext, piemenuIntervals, piemenuVoices, piemenuBoolean,
5959
piemenuBasic, piemenuColor, piemenuNumber, piemenuNthModalPitch,
60-
piemenuNoteValue, piemenuAccidentals, piemenuKey, piemenuChords
60+
piemenuNoteValue, piemenuAccidentals, piemenuKey, piemenuChords,
61+
piemenuDissectNumber
6162
*/
6263

6364
const setWheelSize = i => {
@@ -4017,3 +4018,141 @@ const piemenuKey = activity => {
40174018
modenameWheel.navigateWheel(j);
40184019
}
40194020
};
4021+
4022+
/**
4023+
* Shows a pie menu for selecting the rhythm dissect number.
4024+
* Displays different options based on beginner mode.
4025+
* @param {Object} widget - The widget instance (RhythmRuler).
4026+
* @returns {void}
4027+
*/
4028+
const piemenuDissectNumber = widget => {
4029+
// Use activity.beginnerMode as the global beginnerMode variable references the DOM element
4030+
const isBeginnerMode = widget.activity.beginnerMode;
4031+
4032+
// Determine wheel values based on beginner mode
4033+
const wheelValues = isBeginnerMode ? [2, 3, 4] : [2, 3, 4, 5, 7];
4034+
4035+
const currentValue = parseInt(widget._dissectNumber.value) || 2;
4036+
4037+
// Show the wheel div
4038+
docById("wheelDiv").style.display = "";
4039+
4040+
// Create the number wheel
4041+
const numberWheel = new wheelnav("wheelDiv", null, 600, 600);
4042+
const exitWheel = new wheelnav("_exitWheel", numberWheel.raphael);
4043+
4044+
// Prepare labels with spacer
4045+
const wheelLabels = wheelValues.map(v => v.toString());
4046+
wheelLabels.push(null); // spacer
4047+
4048+
wheelnav.cssMode = true;
4049+
numberWheel.keynavigateEnabled = false;
4050+
numberWheel.colors = platformColor.numberWheelcolors;
4051+
numberWheel.slicePathFunction = slicePath().DonutSlice;
4052+
numberWheel.slicePathCustom = slicePath().DonutSliceCustomization();
4053+
numberWheel.slicePathCustom.minRadiusPercent = 0.2;
4054+
numberWheel.slicePathCustom.maxRadiusPercent = 0.6;
4055+
numberWheel.sliceSelectedPathCustom = numberWheel.slicePathCustom;
4056+
numberWheel.sliceInitPathCustom = numberWheel.slicePathCustom;
4057+
numberWheel.animatetime = 0;
4058+
numberWheel.createWheel(wheelLabels);
4059+
4060+
// Create exit wheel with close, minus, and plus buttons
4061+
exitWheel.colors = platformColor.exitWheelcolors2;
4062+
exitWheel.slicePathFunction = slicePath().DonutSlice;
4063+
exitWheel.slicePathCustom = slicePath().DonutSliceCustomization();
4064+
exitWheel.slicePathCustom.minRadiusPercent = 0.0;
4065+
exitWheel.slicePathCustom.maxRadiusPercent = 0.2;
4066+
exitWheel.sliceSelectedPathCustom = exitWheel.slicePathCustom;
4067+
exitWheel.sliceInitPathCustom = exitWheel.slicePathCustom;
4068+
exitWheel.clickModeRotate = false;
4069+
exitWheel.initWheel(["×", "-", "+"]); // Close, minus, plus
4070+
exitWheel.navItems[0].sliceSelectedAttr.cursor = "pointer";
4071+
exitWheel.navItems[0].sliceHoverAttr.cursor = "pointer";
4072+
exitWheel.navItems[0].titleSelectedAttr.cursor = "pointer";
4073+
exitWheel.navItems[0].titleHoverAttr.cursor = "pointer";
4074+
exitWheel.navItems[1].sliceSelectedAttr.cursor = "pointer";
4075+
exitWheel.navItems[1].sliceHoverAttr.cursor = "pointer";
4076+
exitWheel.navItems[1].titleSelectedAttr.cursor = "pointer";
4077+
exitWheel.navItems[1].titleHoverAttr.cursor = "pointer";
4078+
exitWheel.navItems[2].sliceSelectedAttr.cursor = "pointer";
4079+
exitWheel.navItems[2].sliceHoverAttr.cursor = "pointer";
4080+
exitWheel.navItems[2].titleSelectedAttr.cursor = "pointer";
4081+
exitWheel.navItems[2].titleHoverAttr.cursor = "pointer";
4082+
exitWheel.createWheel();
4083+
4084+
// Handle selection
4085+
const __selectionChanged = () => {
4086+
const selectedIndex = numberWheel.selectedNavItemIndex;
4087+
const newValue = wheelValues[selectedIndex];
4088+
widget._dissectNumber.value = newValue;
4089+
};
4090+
4091+
// Handle exit
4092+
const __exitMenu = () => {
4093+
docById("wheelDiv").style.display = "none";
4094+
numberWheel.removeWheel();
4095+
exitWheel.removeWheel();
4096+
};
4097+
4098+
// Get button position for positioning the pie menu
4099+
const buttonRect = widget._dissectNumber.getBoundingClientRect();
4100+
const canvasLeft = widget.activity.canvas.offsetLeft + 28;
4101+
const canvasTop = widget.activity.canvas.offsetTop + 6;
4102+
4103+
// Position the wheel
4104+
docById("wheelDiv").style.position = "absolute";
4105+
setWheelSize(300);
4106+
4107+
const left = Math.round(buttonRect.left - canvasLeft);
4108+
const top = Math.round(buttonRect.top - canvasTop);
4109+
4110+
// Position to the left of the button as shown in user image
4111+
// left - 300 (wheel size) - 10px padding
4112+
// top + half button height - 150 (half wheel size) for vertical centering
4113+
docById("wheelDiv").style.left = Math.max(0, left - 300 - 10) + "px";
4114+
docById("wheelDiv").style.top = Math.max(0, top + buttonRect.height / 2 - 150) + "px";
4115+
4116+
// Navigate to current value
4117+
const currentIndex = wheelValues.indexOf(currentValue);
4118+
if (currentIndex !== -1) {
4119+
numberWheel.navigateWheel(currentIndex);
4120+
}
4121+
4122+
// Set up click handlers for number selections
4123+
for (let i = 0; i < wheelValues.length; i++) {
4124+
numberWheel.navItems[i].navigateFunction = () => {
4125+
__selectionChanged();
4126+
__exitMenu();
4127+
};
4128+
}
4129+
4130+
// Set up exit button (×)
4131+
exitWheel.navItems[0].navigateFunction = () => {
4132+
__exitMenu();
4133+
};
4134+
4135+
// Set up decrement button (-)
4136+
exitWheel.navItems[1].navigateFunction = () => {
4137+
const currentVal = parseInt(widget._dissectNumber.value);
4138+
const currentIdx = wheelValues.indexOf(currentVal);
4139+
4140+
// Move to previous value in the array, or stay at first
4141+
if (currentIdx > 0) {
4142+
const newValue = wheelValues[currentIdx - 1];
4143+
widget._dissectNumber.value = newValue;
4144+
}
4145+
};
4146+
4147+
// Set up increment button (+)
4148+
exitWheel.navItems[2].navigateFunction = () => {
4149+
const currentVal = parseInt(widget._dissectNumber.value);
4150+
const currentIdx = wheelValues.indexOf(currentVal);
4151+
4152+
// Move to next value in the array, or stay at last
4153+
if (currentIdx < wheelValues.length - 1) {
4154+
const newValue = wheelValues[currentIdx + 1];
4155+
widget._dissectNumber.value = newValue;
4156+
}
4157+
};
4158+
};

js/widgets/rhythmruler.js

Lines changed: 31 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -522,57 +522,34 @@ class RhythmRuler {
522522
// An input for setting the dissect number
523523
this._dissectNumber = widgetWindow.addInputButton("2");
524524

525-
this._dissectNumber.onfocus = () => {
526-
// this._piemenuNumber(['2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'], numberInput.value);
527-
};
528-
529-
this._dissectNumber.onkeydown = event => {
530-
if (event.keyCode === RhythmRuler.DEL) {
531-
this._dissectNumber.value = this._dissectNumber.value.substring(
532-
0,
533-
this._dissectNumber.value.length - 1
534-
);
535-
}
536-
if (event.keyCode === RhythmRuler.BACK) {
537-
// Get the cursor position
538-
const cursorPosition = this._dissectNumber.selectionStart;
539-
// If there is a selection, delete the selected text
540-
if (this._dissectNumber.selectionStart !== this._dissectNumber.selectionEnd) {
541-
const start = this._dissectNumber.selectionStart;
542-
const end = this._dissectNumber.selectionEnd;
543-
this._dissectNumber.value =
544-
this._dissectNumber.value.substring(0, start) +
545-
this._dissectNumber.value.substring(start, end + 1);
546-
} else if (this._dissectNumber.value.length == 1 && cursorPosition == 1) {
547-
// If there is only a single digit in the input then replace it with an empty string
548-
this._dissectNumber.value = "";
549-
} else if (cursorPosition > 0) {
550-
// If there is no selection and the cursor is not at the beginning, delete the character before the cursor
551-
const newValue =
552-
this._dissectNumber.value.substring(0, cursorPosition) +
553-
this._dissectNumber.value.substring(
554-
cursorPosition,
555-
this._dissectNumber.value
556-
);
557-
this._dissectNumber.value = newValue;
525+
// Make the input editable and handle keyboard input
526+
this._dissectNumber.readOnly = false;
527+
this._dissectNumber.classList.add("hasKeyboard");
528+
this._dissectNumber.style.cursor = "text";
529+
530+
// Handle Enter key to validate and blur (prevent any play action)
531+
this._dissectNumber.addEventListener("keydown", event => {
532+
if (event.keyCode === 13 || event.key === "Enter") {
533+
event.preventDefault();
534+
event.stopPropagation();
535+
const inputValue = parseInt(this._dissectNumber.value);
536+
if (!isNaN(inputValue) && inputValue > 0) {
537+
// Validate the input value - allow any number from 2 to 128
538+
const validatedValue = Math.min(Math.max(inputValue, 2), 128);
539+
this._dissectNumber.value = validatedValue;
558540
}
559-
// If the cursor is at the beginning, do nothing
541+
this._dissectNumber.blur();
560542
}
561-
};
543+
});
562544

563545
/**
564-
* Event handler for the input event of the dissect number input.
565-
* Limits the dissect number value to the range 2 to 128.
546+
* Event handler for the click event of the dissect number input.
547+
* Shows a pie menu for selecting the rhythm division number.
566548
* @private
567549
* @returns {void}
568550
*/
569-
this._dissectNumber.oninput = () => {
570-
// Put a limit on the size (2 <--> 128).
571-
this._dissectNumber.onmouseout = () => {
572-
this._dissectNumber.value = Math.max(this._dissectNumber.value, 2);
573-
};
574-
575-
this._dissectNumber.value = Math.max(Math.min(this._dissectNumber.value, 128), 2);
551+
this._dissectNumber.onclick = () => {
552+
this._showDissectNumberPieMenu();
576553
};
577554

578555
/**
@@ -864,6 +841,16 @@ class RhythmRuler {
864841
});
865842
}
866843

844+
/**
845+
* Shows a pie menu for selecting the rhythm dissect number.
846+
* Displays different options based on beginner mode.
847+
* @private
848+
* @returns {void}
849+
*/
850+
_showDissectNumberPieMenu() {
851+
piemenuDissectNumber(this);
852+
}
853+
867854
/**
868855
* Calculates and applies zebra stripes to the ruler cells for visual differentiation.
869856
* @private

0 commit comments

Comments
 (0)