diff --git a/src/file.js b/src/file.js index f475318..7fcb1f6 100644 --- a/src/file.js +++ b/src/file.js @@ -2,7 +2,7 @@ function generateDownloadFile() { const save = {} - save.version = 'canvasCraft-v2.0.0-beta.5' + save.version = 'canvasCraft-v2.0.0-beta.1.6' save.codes = codes save.layers = [] diff --git a/src/functions.js b/src/functions.js index d2bcd4a..3922664 100644 --- a/src/functions.js +++ b/src/functions.js @@ -208,14 +208,13 @@ function syntaxHighlightJavaScriptCode(code) { const main = code => { const data = { multi: /`[\s\S]*?`/, - regex: /\/.+?\/[gim]*/, comment: /\/\*[\s\S]*?\*\/|\/\/.*/, string: /".*?"|'.*?'/} - const total = new RegExp('(' + data.multi.source + '|' + data.string.source + '|' + data.comment.source + '|' + data.regex.source + ')') + const total = new RegExp('(' + data.multi.source + '|' + data.string.source + '|' + data.comment.source + ')') return code.split(total).map((e, i) => i % 2 ? data.multi.test(e) ? e.split(/(\${.*?})/).map( - (e, i) => i % 2 ? loop(e) : span('string', e)).join('') : data.comment.test(e) ? span('comment', e) : - data.regex.test(e) ? span('regex', e) : span('string', e) : loop(e)).join('') + (e, i) => i % 2 ? loop(e) : span('string', e)).join('') : + data.comment.test(e) ? span('comment', e) : span('string', e) : loop(e)).join('') } const loop = (code, index = 0) => { @@ -225,7 +224,7 @@ function syntaxHighlightJavaScriptCode(code) { {class: 'builtin', regex: /\b(eval|isFinite|isNaN|parseFloat|parseInt|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|escape|unescape)\b/}, {class: 'function', regex: /\b(\w+\()/, item: /\w+/}, {class: 'variable', regex: /\b(\w+)\b/}, - {class: 'operator', regex: /([\/*+<>%=-?:]+)/} + {class: 'operator', regex: /([\/\\*+<>%=?:\{\}\(\)\-]+)/} ] return code.split(list[index].regex).map((e, i) => i % 2 ? list[index].item ? e.replace( @@ -501,7 +500,7 @@ function addNewLayer(insertIndex) { collapse.textContent = 'Collapse' hide.textContent = 'Hide' insert.textContent = 'New Layer' - move.textContent = 'Sink' + move.textContent = 'Drop' layer.sink = move name.value = 'Layer ' + layers.length @@ -1098,6 +1097,7 @@ function applyInfoToShapePanel(div, update = false) { div.shape.activeRemix = i div.shape.activePreset = code.preset applyInfoToShapePanel(div, true) + div.shape.drawOnShape() } popupChoice.appendChild(button) } @@ -1115,12 +1115,12 @@ function applyInfoToShapePanel(div, update = false) { asDefault.onmousedown = () => { popup.classList.add('open') - popupText.textContent = 'Set "' + name + '" as Default' + popupText.textContent = 'Set "'+name+'" as Default' popupChoice.innerHTML = '' // Confirm text const confirm = document.createElement('div') - confirm.textContent = 'From now on, all shapes will be given the "' + name + '" template as default.' + confirm.innerHTML = 'From now on, all shapes will be given the '+name +' template by default.' popupChoice.appendChild(confirm) // Confirm @@ -1148,7 +1148,7 @@ function applyInfoToShapePanel(div, update = false) { // Function text const codeName = document.createElement('div') - codeName.innerHTML = 'function draw(x, y, color) {' + codeName.innerHTML = 'function draw(x, y, color, size) {' codeContain.appendChild(codeName) // Textarea code box @@ -1166,12 +1166,14 @@ function applyInfoToShapePanel(div, update = false) { // Syntax highlight codePre.innerHTML = syntaxHighlightJavaScriptCode(codeTextarea.value + '\n') + // Set height + codeTextarea.style.height = '3px' + codeTextarea.style.height = (codePre.scrollHeight + 3) + 'px' + // Set background code div.shape.remixes[div.shape.activeRemix].code = codeTextarea.value } codeTextarea.oninput = () => change() - change() - codeTextarea.onscroll = () => { codePre.scrollTop = codeTextarea.scrollTop codePre.scrollLeft = codeTextarea.scrollLeft @@ -1187,6 +1189,8 @@ function applyInfoToShapePanel(div, update = false) { ending.innerHTML = '}' codeContain.appendChild(ending) + change() + // Error const error = document.createElement('div') error.className = 'error' @@ -1220,11 +1224,18 @@ function applyInfoToShapePanel(div, update = false) { ok.style.padding = '0' ok.style.transitionDuration = '.6s' + // Confirm text + const confirmText = document.createElement('div') + const DIVNAME = 'You are adding this texture to the list of templates. Any shape in CanvasCraft will be able to access it.' + confirmText.textContent = DIVNAME + // Name box const input = document.createElement('input') input.style.backgroundColor = '#111' input.className = 'wide' input.placeholder = 'Name this template' + let nameExists = {} + let shapesArr = [] input.oninput = () => { if (input.value.length) { ok.style.opacity = '1' @@ -1234,19 +1245,65 @@ function applyInfoToShapePanel(div, update = false) { ok.style.opacity = '0' ok.style.padding = '0' } + + nameExists = {} + for (let i = 0; i < codes.length; i ++) { + const code = codes[i] + if (input.value == code.name) { + nameExists = {name: code.name, idx: i} + break + } + } + if (nameExists.name) { + shapesArr = [] + for (let i = 0; i < layers.length; i ++) { + const layer = layers[i].arr + for (let j = 0; j < layer.length; j ++) { + const shape = layer[j] + if (input.value == codes[shape.activePreset].name) + shapesArr.push(shape) + } + } + + popupText.textContent = 'Just one minute...' + let shapesWithThisPreset = shapesArr.length + const many = shapesWithThisPreset > 1 + confirmText.innerHTML = 'It seems like this preset already exists. You can override the '+nameExists.name+' preset by pressing Confirm, but remember that this cannot be undone. '+(shapesWithThisPreset?'There '+(many?'are':'is')+' currently '+shapesWithThisPreset+' shape'+(many?'s':'')+' that '+(many?'have':'has')+' this preset. By continuing, '+(many?'these shapes':'the shape')+' will be updated with the new code. Do you really want to do this?':'There are no shapes that are currently using this preset.') + } + else { + popupText.textContent = 'Save as a template' + confirmText.innerHTML = DIVNAME + } } popupChoice.appendChild(input) - - // Confirm text - const div = document.createElement('div') - div.textContent = 'You are adding this texture to the list of templates. Any shape in CanvasCraft will be able to access it.' - popupChoice.appendChild(div) + popupChoice.appendChild(confirmText) // Confirm button ok.onmousedown = () => { if (!input.value.length) return popup.classList.remove('open') - codes.push({name: input.value, code: codeTextarea.value}) + + if (nameExists.name) { + const presetToChange = codes[nameExists.idx] + presetToChange.code = codeTextarea.value + + // Update all shapes with new preset + for (let i = 0; i < shapesArr.length; i ++) { + const shape = shapesArr[i] + shape.addRemix(presetToChange, nameExists.idx) + shape.drawOnShape() + } + } + + else { + codes.push({name: input.value, code: codeTextarea.value}) + div.shape.activePreset = codes.length - 1 + + const activeRemix = div.shape.remixes[div.shape.activeRemix] + activeRemix.name = input.value + ' ' + div.shape.remixes.length + 1 + activeRemix.preset = div.shape.activePreset + applyInfoToShapePanel(div, true) + } } popupChoice.appendChild(ok) diff --git a/src/help.js b/src/help.js index 272e479..787aa7e 100644 --- a/src/help.js +++ b/src/help.js @@ -3,6 +3,9 @@ const help = document.getElementById('helpMenu') const helpButton = document.getElementById('helpButton') +const querySearch = document.getElementById('querySearch') +const quesryResult = document.getElementById('queryResult') + helpButton.onmouseover = () => helpChange('help') const conversation = { @@ -46,14 +49,75 @@ const conversation = { transform: '*Transform Layer* Move the contents of the layer by a certain amount', layerUp: '*Move Back* Push the layer back', layerDown: '*Move Down* Pull the layer forward', - merge: '*Merge Up* Merge this layer with the one above', + merge: '*Merge Up* Merge this layer into the one above', image: '*Image Mode* This allows you to insert an image from your device. You can switch back at any time', shapeMode: '*Shape Mode* Go back to the original texture mode. You can switch back at any time', chooseShapeImage: '*Select Image* Upload to an image from your device to assign it to this shape. If you do not want to upload an image, just switch back to Shape Mode', opacitySlider: '*Opacity* Control the opacity of this image', imagexPos: '*X Offset* The x offset of the image in this shape', imageyPos: '*Y Offset* The y offset of the image in this shape', - imageScale: '*Scale* Control the size of the image' + imageScale: '*Scale* Control the size of the image', + pixelX: '*Pixel X* The x position of the pixel. (0, 0) is the top left of the shape', + pixelY: '*Pixel Y* The y position of the pixel. (0, 0) is the top left of the shape', + pixelColor: '*Shape Colour* Access the RGB colour values of this shape as {r, g, b}', + pixelSize: '*Size* Access the width and height of this shape as {w, h}', +} + +const queries = [ + ['whatiscanvascraft','
CanvasCraft is a free tool designed to help creators create game worlds and images. You can learn more about us by clicking the "About" button on the homepage.
'], + ['whatarecodetemplatespresets', 'Code templates allow you to store useful code snippets that can be accessed by other shapes. Just press the "Save code as template" button and choose its name!
'], + ['whatarecoderemixes', 'Code remixes are ways of storing multiple codes for a single shape. If you want to experiment with new textures without losing your original creation, just select a new template, and CanvasCraft will generate a fresh remix for you. This allows you to experiment with different textures while preserving your previous designs.
• Write some code to create a texture
• When you\'re happy with how it looks, select a new texture from the Templates list. This will create a new Remix. If you ever want to go back to the old texture that you made, simply select the right one in the Remixes list!
'], + ['whatlanguageisthecodeinuse', 'When you\'re creating textures in CanvasCraft, you will be coding in pure JavaScript. You are given the coordinates of each pixel in the shape, the shape\'s base colour, and the width and height of the shape.
'], + ['howdoiusecanvascraft', 'At the time of writing, CanvasCraft does not support touch-screen devices. For PCs, you can start CanvasCraft by clicking on the big icon on the homepage. The basic help assistant will tell you how to get started. If you need further help, simply hover over something and the assistant will explain it.
'], + ['howdoiusejavascriptcodetomaketexturesincanvascraftcodebox', 'Making a texture is very simple, provided you know a little about code and RGB colours. Here\'s a breakdown of how it works.
• The first time you create a shape, it will be given the Basic template. A template is a small code texture that you can work from.
• At the start of the code, it defines four variables. r - the red value - is a number between 0 and 255. The Green and Blue values are stored in the same way. Curiously, the alpha variable a is stored as a number between 0 and 1. We added this feature to CanvasCraft because that\'s how standard JavaScript workds when creating RGB colours.
• The last part of the code returns a string from the function. What does that mean? To put it into simple words, imagine the code snippet as a machine. You put stuff into the machine, and after doing a few calculations, it spits something back out. When we\'re coding textures in CanvasCraft, we are in essence telling the machine how to process its given information. The parameters of the function (or, the stuff that\'s put into the machine) is x, y, color and size. To learn more about them, search "What are the code arguments?" into the query bar at the top of this page. Back to the machine. So we have all these inputs, and we need to give an output. The output of this function is a standard JavaScript string. This can be in Hexadecimal format, HSL, or - in our instance - RGB.
• Now we can start coding! Remember, we have the RGB variables defined earlier, so why not try making the colours change based on the coordinates? Paste the following code below the let a = 1 line and see what happens: r = r + x * 20. After pressing "Apply Texture," the shape should gradually get redder from left to right. This is a very basic example of how to make textures, but it should be enough to get you started.
'], + ['whatarethecodeparametersargumentsforboxxycolorsize', 'The arguments for the code box function are x, y, color and size. The x and y values represent the positions of a pixel. When you apply the texture, the function will run for every pixel in the shape. This means that each pixel can be given a different colour, based on the arguments provided. The final two arguments color {r, g, b} and size {w, h} are objects.
'] +] + +querySearch.oninput = () => { + const queryWords = querySearch.value.split(' ') + quesryResult.innerHTML = queryWords + + const arr = [] + + // Iterate through stored queries + for (let i = 0; i < queries.length; i ++) { + const dic = {name: queries[i][0], score: 0} + const name = dic.name + + // Iterate through words of searches query + for (let j = 0; j < queryWords.length; j ++) { + const word = queryWords[j].toLowerCase().replace(/[^a-z]/g,'') + if (name.includes(word)) dic.score += word.length + + // If the word doesn't match, find a match inside the word + else { + for (let k = 0; k < word.length; k ++) { + const wordSoFar = word.slice(0, k) + if (name.includes(wordSoFar)) dic.score ++ + } + } + } + + arr.push(dic) + } + + // Get largest score + let score = -1 + let chosen = -1 + for (let i = 0; i < arr.length; i ++) { + if (arr[i].score > score) { + score = arr[i].score + chosen = i + } + } + if (chosen > -1) { + quesryResult.innerHTML = queries[chosen][1] + const spans = document.getElementsByClassName('code') + for (let i = 0; i < spans.length; i ++) { + const span = spans[i] + span.innerHTML = syntaxHighlightJavaScriptCode(span.textContent) + } + } } let createdAShape = false diff --git a/src/main.js b/src/main.js index 1fc8bcc..4b0d54d 100644 --- a/src/main.js +++ b/src/main.js @@ -122,7 +122,7 @@ let cell = 1 let time = 0 let camX = 0 let camY = 0 -let drawIterations = 500 +let drawIterations = 10000 let defaultColor = [50, 230, 90] const cvs = document.getElementById('cvs') @@ -169,12 +169,14 @@ const splashClose = document.getElementById('splashClose') const changelog = document.getElementById('changelog') const aboutSection = document.getElementById('aboutSection') const changelogSection = document.getElementById('changelogSection') +const helpSplash = document.getElementById('helpSplash') +const helpSection = document.getElementById('helpSection') const openSplash = document.getElementById('openSplash') openSplash.onmousedown = () => splash.classList.remove('closed') centerSplash.onmousedown = () => { - if (performance.now() < 2500) return + if (performance.now() < 1000) return splash.classList.add('closed') if (settings.classList.contains('open')) return @@ -206,6 +208,7 @@ about.onmousedown = () => { splashPanel.scrollTo(0, 0) aboutSection.style.display = 'block' changelogSection.style.display = 'none' + helpSection.style.display = 'none' } changelog.onmousedown = () => { @@ -213,6 +216,15 @@ changelog.onmousedown = () => { splashPanel.scrollTo(0, 0) changelogSection.style.display = 'flex' aboutSection.style.display = 'none' + helpSection.style.display = 'none' +} + +helpSplash.onmousedown = () => { + splashPanel.classList.add('open') + splashPanel.scrollTo(0, 0) + helpSection.style.display = 'flex' + aboutSection.style.display = 'none' + changelogSection.style.display = 'none' } splashClose.onmousedown = () => { @@ -345,8 +357,8 @@ textureRender.value = drawIterations textureRender.oninput = () => { let val = Number(textureRender.value) if (!val) return - if (val > 10000) val = 50000 - textureRender.value = val + if (val > 50000) val = 50000 + if (val > 1) textureRender.value = val } textureRender.onchange = () => { let val = Number(textureRender.value) @@ -369,8 +381,8 @@ copyCodeButton.onmousedown = () => { } let codes = [ - {name: 'Basic', code: 'const r = color.r\nconst g = color.g\nconst b = color.b\nconst a = 1\n\nreturn \'rgb(\'+r+\',\'+g+\',\'+b+\',\'+a+\')\''}, - {name: 'Random', code: 'const r = color.r + Math.random() * 50 - 25\nconst g = color.g + Math.random() * 50 - 25\nconst b = color.b + Math.random() * 50 - 25\nconst a = 1\n\nreturn \'rgb(\'+r+\',\'+g+\',\'+b+\',\'+a+\')\''} + {name: 'Basic', code: 'let r = color.r\nlet g = color.g\nlet b = color.b\nlet a = 1\n\nreturn \'rgb(\'+r+\',\'+g+\',\'+b+\',\'+a+\')\''}, + {name: 'Random', code: 'let r = color.r + Math.random() * 50 - 25\nlet g = color.g + Math.random() * 50 - 25\nlet b = color.b + Math.random() * 50 - 25\nlet a = 1\n\nreturn \'rgb(\'+r+\',\'+g+\',\'+b+\',\'+a+\')\''} ] const m = { diff --git a/src/shape.js b/src/shape.js index 409d10d..4b6b785 100644 --- a/src/shape.js +++ b/src/shape.js @@ -22,7 +22,7 @@ class Shape { this.imageOftY = 0 this.imageScale = 1 - this.shorthand = {} + this.shorthand = [] this.div = 0 this.hoverCommand = false this.layer = selectedLayer @@ -172,8 +172,18 @@ class Shape { const box = (X, icon, func) => { let sh = 70 * progress const shrink = .47 - const x = pos.x + this.w / 2 * scale + X * (gap + sh) * shrink - const y = pos.y - progress * (gap + sh / 2) + + let Xx = pos.x + this.w / 2 * scale + let Yy = pos.y - progress * (gap + sh / 2) + + if (Xx < sh * 2) Xx = sh * 2 + if (Yy < sh) Yy = sh + if (Xx > cvs.width - 3 * (sh + gap) * shrink) Xx = cvs.width - 3 * (sh + gap) * shrink + if (Yy > cvs.height - sh) Yy = cvs.height - sh + + const x = Xx + X * (gap + sh) * shrink + const y = Yy + const BOX = {} if (progress >= 1 && @@ -287,20 +297,20 @@ class Shape { } // Default function without code applied - let fillFunction = (x, y, color) => {return plainColor} + let fillFunction = (x, y, color, size) => {return plainColor} // Add code to function const calculateFillFunction = () => { try { - let func = new Function('x', 'y', 'color', this.remixes[this.activeRemix].code) - fillFunction = (x, y, color) => { + let func = new Function('x', 'y', 'color', 'size', this.remixes[this.activeRemix].code) + fillFunction = (x, y, color, size) => { try { - return func(x, y, color) + return func(x, y, color, size) } // Runtime error check catch (error) { - func = (x, y, color) => {return plainColor} + func = (x, y, color, size) => {return plainColor} this.error = true drawPlain() this.div.error.textContent = 'Error: ' + error.message @@ -330,7 +340,8 @@ class Shape { // Run the function for every pixel this.ctx.fillStyle = fillFunction( this.runX, this.runY, - {r: this.color[0], g: this.color[1], b: this.color[2]}) + {r: this.color[0], g: this.color[1], b: this.color[2]}, + {w: this.w, h: this.h}) this.ctx.fillRect(this.runX, this.runY, 1, 1) // Canel process if at end @@ -402,15 +413,11 @@ class Shape { for (let i = 0; i < this.shorthand.length; i ++) { const box = this.shorthand[i] - ctx.fillStyle = box.color + const imgSize = Math.max(0, box.s - pad * 2) + ctx.fillStyle = box.color ctx.fillRect(box.x, box.y, box.s, box.s) - - const imgSize = Math.max(0, box.s - pad * 2) - ctx.drawImage( - icons[box.icon], - box.x + pad, box.y + pad, - imgSize, imgSize) + ctx.drawImage(icons[box.icon], box.x + pad, box.y + pad, imgSize, imgSize) } } } \ No newline at end of file diff --git a/src/styles.css b/src/styles.css index aaea369..7f9af12 100644 --- a/src/styles.css +++ b/src/styles.css @@ -27,7 +27,6 @@ --variable: #f88; --string: #0f8; --comment: #999; - --regex: #ff0; } html {height: 100%} @@ -232,7 +231,7 @@ canvas { #base { position: relative; width: 100%; - height: 60%; + height: 65%; flex: 1 0 auto; border-top: var(--border); background-color: #111; @@ -474,7 +473,19 @@ pre { resize: none; color: transparent; caret-color: #fff; - background: none + background: none; + overflow-y: hidden; +} + +.param { + color: var(--variable); + background-color: #333; + border-radius: 3px; +} + +.param:hover { + background-color: #444; + cursor: pointer; } .error { @@ -908,7 +919,7 @@ h3.center { display: flex; align-items: center; justify-content: center; - background-color: #333; + background-color: #222; animation: openScreen 1s; flex-direction: column; cursor: pointer; @@ -928,7 +939,7 @@ h3.center { left: 0; width: 100%; height: 100%; - animation: pushUp 3s cubic-bezier(.6,0,.3,1); + animation: pushUp 3s cubic-bezier(.7,0,.3,1); transform: translateY(-10%); } @@ -959,6 +970,18 @@ h3.center { font-size: 20px; } +#splashPanel .code { + background-color: #222; + font-size: 17px; + display: inline-block; + border-style: solid; + border-width: 1.3px; + border-top-color: #444; + border-left-color: #222; + border-right-color: #000; + border-bottom-color: #000; +} + #splashClose { position: fixed; top: 10px; @@ -1037,7 +1060,7 @@ a { text-shadow: 4px 4px 10px #0009; } -#changelogSection { +#changelogSection, #helpSection { display: flex; flex-direction: column; gap: 10px; @@ -1069,13 +1092,14 @@ a { } .start { - margin-top: 20px; + margin: 15px 0 3px 3px; border-radius: 10px; font-family: code; - font-size: 23px; + font-size: 20px; border: none; padding: 15px; background-color: #0006; + border: 1.5px #069 solid; color: #fff; transform: translateY(0); transition: .1s; @@ -1084,7 +1108,7 @@ a { } .start:hover { - background-color: #0008; + background-color: #000; transform: translateY(-.5vh); cursor: pointer; } @@ -1131,7 +1155,7 @@ a { @keyframes openScreen { from {background-color: #000} - to {background-color: #333} + to {background-color: #222} } @keyframes openLogo {