From 018142d25713c88b18b635b0aeb5ab1912d2d5ed Mon Sep 17 00:00:00 2001 From: Mal Date: Sat, 14 Jan 2023 00:04:38 +0100 Subject: [PATCH] Color selection and animation implemented --- public/graphics/icon-dropdown.svg | 49 ++++++ public/index.html | 152 ++--------------- public/index.js | 269 +++++++++++++++++++++++++++++- public/style.css | 70 +++++++- 4 files changed, 394 insertions(+), 146 deletions(-) create mode 100644 public/graphics/icon-dropdown.svg diff --git a/public/graphics/icon-dropdown.svg b/public/graphics/icon-dropdown.svg new file mode 100644 index 0000000..65d9bf3 --- /dev/null +++ b/public/graphics/icon-dropdown.svg @@ -0,0 +1,49 @@ + + + + + + + + + + diff --git a/public/index.html b/public/index.html index d30097d..42fb06b 100644 --- a/public/index.html +++ b/public/index.html @@ -9,153 +9,37 @@ -
-
-
+ +
+
+
- - + + +
Frame: 1/1
- + - +
diff --git a/public/index.js b/public/index.js index ce0994c..58e4d6e 100644 --- a/public/index.js +++ b/public/index.js @@ -3,6 +3,61 @@ const Frame = function (duration) { this.duration = duration; } +const LED_COLOR = { + WHITE: 0, + RED: 1, + GREEN: 2, + BLUE: 3, +} + +const ANIMATION_FRAME_QUEUE = []; + +const COLOR_CLASS = []; +COLOR_CLASS[LED_COLOR.WHITE] = 'led-white'; +COLOR_CLASS[LED_COLOR.RED] = 'led-red'; +COLOR_CLASS[LED_COLOR.GREEN] = 'led-green'; +COLOR_CLASS[LED_COLOR.BLUE] = 'led-blue'; + +const Selector = function (id) { + this.items = []; + this.currentItem = 0; + this.html = document.getElementById(id); + this.htmlDisplay = this.html.querySelector('.selector-display'); + this.htmlItems = this.html.querySelector('.selector-items'); + + this.htmlItems.hidden = true; + + this.htmlDisplay.addEventListener( + 'click', + () => { + this.htmlItems.hidden = !this.htmlItems.hidden; + } + ); + + for (const i in this.htmlItems.children) { + this.htmlItems.children.item(i).addEventListener( + 'click', + () => { + this.selectItem(i); + } + ); + } + + this.onSelect = function () {}; + + this.selectItem = function (index) { + this.htmlItems.hidden = true; + + if (index !== this.currentItem) { + this.currentItem = index; + + this.htmlDisplay.innerHTML = this.htmlItems.children.item(index).innerHTML; + + this.onSelect(index); + } + }; +} + const FrameFactory = function (width, depth, height) { this.width = width; this.depth = depth; @@ -43,8 +98,11 @@ const Cube = function (width, depth, height) { this._loadFrame(frameIndex); } + this.addFrame = function (frame) { + this.frames.push(frame); + } + this.switchLed = function (layerIndex, ledIndex) { - console.log(layerIndex); this.frames[this.currentFrame].layers[layerIndex][ledIndex] = !this.frames[this.currentFrame].layers[layerIndex][ledIndex]; } @@ -53,20 +111,34 @@ const Cube = function (width, depth, height) { this.frames[this.currentFrame].layers[layerIndex][index] = state; } - for (const row of this.html.children[this.height - layerIndex - 1].children) { + for (const row of this.html.children.item(this.height - layerIndex - 1).children) { for (const led of row.children) { this._setLed(led, state); } } } + this.setLedColor = function (color) { + for (const layer of this.html.children) { + for (const row of layer.children) { + for (const led of row.children) { + for (const cssClass of COLOR_CLASS) { + led.classList.remove(cssClass); + } + + led.classList.add(COLOR_CLASS[color]); + } + } + } + } + this._loadFrame = function (frameIndex) { for (let layer = 0; layer < this.frames[frameIndex].layers.length; layer++) { for (let row = 0; row < this.depth; row++) { for (let led = 0; led < this.width; led++) { const state = this.frames[frameIndex].layers[layer][row * this.depth + led]; - this._setLed(this.html.children[layer].children[row].children[led], state); + this._setLed(this.html.children.item(this.height - layer - 1).children.item(row).children.item(led), state); } } } @@ -96,6 +168,19 @@ const Cube = function (width, depth, height) { } ); + led.addEventListener( + 'mouseover', + (event) => { + if (event.buttons === 1) { + const index = y * this.depth + x; + const layer = this.height - z - 1; + + this.switchLed(layer, index); + this._setLed(event.target, this.frames[this.currentFrame].layers[layer][index]); + } + } + ); + row.appendChild(led); } @@ -115,6 +200,110 @@ const Cube = function (width, depth, height) { } } +const FrameMenu = function (id) { + this.html = document.getElementById(id); + this.htmlSlider = this.html.querySelector('#frame-slider'); + this.htmlDisplayCurrentFrame = this.html.querySelector('#current-frame'); + this.htmlDisplayFrames = this.html.querySelector('#frame-number'); + this.htmlButtonAddFrameBefore = this.html.querySelector('#button-add-frame-before'); + this.htmlButtonAddFrameAfter = this.html.querySelector('#button-add-frame-after'); + this.htmlButtonToggleAnimation = this.html.querySelector('#button-toggle-animation'); + this.htmlInputDuration = this.html.querySelector('#frame-duration'); + + this.isAnimationPlaying = false; + + this.htmlSlider.addEventListener( + 'input', + () => { + this._updateFramePosition(); + } + ); + + this.htmlButtonAddFrameBefore.addEventListener( + 'click', + () => { + this._increaseSliderMax(); + this.onFramePrepend(); + } + ); + + this.htmlButtonAddFrameAfter.addEventListener( + 'click', + () => { + this._increaseSliderMax(); + this.onFrameAppend(); + } + ); + + this.htmlInputDuration.addEventListener( + 'input', + () => { + this.onInputDuration(this.htmlInputDuration.value); + } + ); + + this.htmlButtonToggleAnimation.addEventListener( + 'click', + () => { + this.isAnimationPlaying = !this.isAnimationPlaying; + + this._updateToggleButton(); + this.onToggleAnimation(this.isAnimationPlaying); + } + ); + + this.slideToFrame = function (frameIndex) { + this.htmlSlider.value = frameIndex + 1; + this._updateFramePosition(); + } + + this.setDuration = function (duration) { + this.htmlInputDuration.value = duration; + } + + this.stopAnimation = function () { + this.isAnimationPlaying = false; + + this._updateToggleButton(); + } + + this.onFramePrepend = function () {} + + this.onFrameAppend = function () {} + + this.onSlide = function () {} + + this.onInputDuration = function () {} + + this.onToggleAnimation = function () {} + + this._increaseSliderMax = function () { + const frameNumber = parseInt(this.htmlSlider.max) + 1; + + this.htmlSlider.max = frameNumber; + this.htmlDisplayFrames.innerText = frameNumber; + + if (frameNumber > 1) { + this.htmlButtonToggleAnimation.disabled = false; + } + } + + this._updateFramePosition = function () { + this.htmlDisplayCurrentFrame.innerText = this.htmlSlider.value; + this.onSlide(this.htmlSlider.value - 1); + } + + this._updateToggleButton = function () { + if (this.isAnimationPlaying) { + this.htmlButtonToggleAnimation.classList.add('icon-button-stop'); + this.htmlButtonToggleAnimation.classList.remove('icon-button-play'); + } else { + this.htmlButtonToggleAnimation.classList.add('icon-button-play'); + this.htmlButtonToggleAnimation.classList.remove('icon-button-stop'); + } + } +} + const WIDTH = 4; const DEPTH = 4; const HEIGHT = 4; @@ -122,10 +311,76 @@ const HEIGHT = 4; const cube = new Cube(WIDTH, DEPTH, HEIGHT); cube.setup(); -console.log(cube.html.children); - const frameFactory = new FrameFactory(WIDTH, DEPTH, HEIGHT); -cube.frames.push(frameFactory.getFrame(100)); +cube.frames.push(frameFactory.getFrame(500)); cube.setCurrentFrame(0); -cube.setLayer(2, true); \ No newline at end of file +cube.setLedColor(LED_COLOR.RED); + +const colorSelector = new Selector('color-selector'); +colorSelector.onSelect = (index) => { + cube.setLedColor(index); +}; +colorSelector.selectItem(LED_COLOR.RED); + +const frameMenu = new FrameMenu('frame-menu'); +frameMenu.onSlide = (frameIndex) => { + cube.setCurrentFrame(frameIndex); + frameMenu.setDuration(cube.frames[cube.currentFrame].duration); +} +frameMenu.onFramePrepend = () => { + cube.frames = cube.frames.slice(0, cube.currentFrame).concat( + [frameFactory.getFrame(cube.frames[cube.currentFrame].duration)].concat( + cube.frames.slice(cube.currentFrame) + ) + ); + + frameMenu.slideToFrame(cube.currentFrame - 1); +} +frameMenu.onFrameAppend = () => { + cube.frames = cube.frames.slice(0, cube.currentFrame + 1).concat( + [frameFactory.getFrame(cube.frames[cube.currentFrame].duration)].concat( + cube.frames.slice(cube.currentFrame + 1) + ) + ); + + frameMenu.slideToFrame(cube.currentFrame + 1); +} +frameMenu.onInputDuration = (duration) => { + if (+duration <= 0) { + return; + } + + cube.frames[cube.currentFrame].duration = +duration; +} +frameMenu.onToggleAnimation = (isPlaying) => { + if (isPlaying) { + let duration = 0; + + for (let frame = cube.currentFrame; frame < cube.frames.length; frame++) { + duration += cube.frames[frame].duration; + + if (frame === cube.frames.length - 1) { + setTimeout( + () => { + frameMenu.stopAnimation(); + }, + duration + ); + } + + ANIMATION_FRAME_QUEUE.push( + setTimeout( + () => { + frameMenu.slideToFrame(frame + 1); + }, + duration + ) + ) + } + } else { + for (const frame of ANIMATION_FRAME_QUEUE) { + clearTimeout(frame); + } + } +} \ No newline at end of file diff --git a/public/style.css b/public/style.css index 24df7ab..35ea6ec 100644 --- a/public/style.css +++ b/public/style.css @@ -16,7 +16,7 @@ body { font-family: sans-serif; } -input { +input, select { border: 2px solid white; background-color: transparent; color: white; @@ -26,6 +26,10 @@ input { outline: none; } +input[type=range] { + cursor: pointer; +} + input[type=number] { max-width: 100px; } @@ -42,6 +46,10 @@ button { height: 32px; } +button:disabled { + filter: grayscale(); +} + .suffix-ms::after { content: "ms"; } @@ -88,7 +96,7 @@ button:hover { align-items: center; } -#frame-menu { +#frame-menu, #header-menu { border: 2px solid white; border-radius: 20px; padding: 20px; @@ -96,6 +104,13 @@ button:hover { align-items: center; } +#header-menu { + border: none; + position: fixed; + top: 10px; + left: 10px; +} + #frame-menu > * { margin: 0 10px; } @@ -143,6 +158,48 @@ button:hover { width: 80px; } +.selector-title { + margin-bottom: 10px; + font-weight: bold; +} + +.selector-title::after { + content: ":"; +} + +.selector-display, .selector-items { + border: 2px solid white; + border-radius: 10px; + padding: 8px; + padding-bottom:12px; + cursor: pointer; +} + +.selector-display { + padding-right: 40px; + background-image: url("graphics/icon-dropdown.svg"); + background-repeat: no-repeat; + background-size: auto 8px; + background-position: right 10px top 15px; +} + +.selector-items { + border-radius: 0 0 10px 10px; + position: relative; + background-color: black; + top: -8px; + padding: 0; +} + +.selector-item { + padding: 4px 8px; +} + +.selector-item:hover { + background-color: white; + color: black; +} + .layer:hover .layer-submenu { visibility: visible; } @@ -164,7 +221,7 @@ button:hover { } .layer-button-on { - background-color: red; + background-color: white; } .layer-button-off { @@ -172,14 +229,14 @@ button:hover { } .led { + background-color: white; + box-shadow: 0 0 10px white; border-radius: 50%; - background-color: red; width: 32px; height: 48px; margin: 8px 16px; transform: rotate(45deg); cursor: pointer; - box-shadow: 0 0 10px red; } .led-off { @@ -193,14 +250,17 @@ button:hover { .led-red { background-color: red; + box-shadow: 0 0 10px red; } .led-blue { background-color: #3333ff; + box-shadow: 0 0 10px #3333ff; } .led-green { background-color: #00ff00; + box-shadow: 0 0 10px #00ff00; } .row {