From d138931758be4cf7dd7a5e74339ec655203df214 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Mon, 3 Aug 2020 12:17:01 +1200 Subject: [PATCH] :sparkles: Bitmap fonts --- .editorconfig | 18 ++ .gitignore | 2 +- .vscode/launch.json | 16 ++ README.md | 4 +- app/data/ct.release/inputs.js | 2 + app/data/i18n/English.json | 29 ++- app/package-lock.json | 21 +- app/package.json | 1 + gulpfile.js | 20 +- src/js/gulpWatch.js | 8 +- src/js/loadProject.js | 6 + src/js/migration/1.3.2.js | 11 + src/node_requires/exporter/fonts.js | 97 +++++++- src/node_requires/exporter/index.js | 6 +- src/node_requires/i18n.js | 1 + .../monaco-themes/lucasdracula.json | 4 +- .../fonts/bitmapFontGenerator/index.js | 153 ++++++++++++ .../fonts/bitmapFontGenerator/util.js | 113 +++++++++ src/node_requires/resources/projects/index.js | 14 +- .../debugger/debugger-screen-embedded.tag | 231 ++++++++++++++++++ src/riotTags/font-editor.tag | 79 ++++-- src/riotTags/fonts-panel.tag | 7 +- src/riotTags/main-menu.tag | 64 ++++- src/riotTags/shared/asset-viewer.tag | 2 +- src/riotTags/shared/copy-icon.tag | 19 ++ src/styl/common.styl | 7 + src/styl/inputs.styl | 7 +- src/styl/tags/debugger/debugger-screen.styl | 7 +- src/styl/tags/debugger/debugger-toolbar.styl | 2 +- src/styl/themeDay.styl | 7 - src/styl/themeHorizon.styl | 8 - src/styl/themeLucasDracula.styl | 9 - src/styl/themeNight.styl | 7 - src/styl/themeSpringStream.styl | 7 - src/styl/typography.styl | 11 +- 35 files changed, 913 insertions(+), 87 deletions(-) create mode 100644 .editorconfig create mode 100644 .vscode/launch.json create mode 100644 src/node_requires/resources/fonts/bitmapFontGenerator/index.js create mode 100644 src/node_requires/resources/fonts/bitmapFontGenerator/util.js create mode 100644 src/riotTags/debugger/debugger-screen-embedded.tag create mode 100644 src/riotTags/shared/copy-icon.tag diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..1fd1a5a7f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# EditorConfig: https://EditorConfig.org + +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +[*.{js,ts,styl,pug}] +indent_style = space +indent_size = 4 +charset = utf-8 + +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 +charset = utf-8 diff --git a/.gitignore b/.gitignore index 7e012c86b..750862889 100644 --- a/.gitignore +++ b/.gitignore @@ -78,7 +78,7 @@ app/export *.ict.recovery # editor-specific -/.vscode +.vscode/settings.json # docs docs/db.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..77c276ac7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "nwjs", + "request": "launch", + "name": "Launch ct.js", + "nwjsVersion": "0.34.5", + "webRoot": "${workspaceFolder}/app", + "reloadAfterAttached": true + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index a741c5b98..a4cce1568 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ gulp -f devSetup.gulpfile.js gulp ``` +VSCode can use [this extension](https://marketplace.visualstudio.com/items?itemName=ruakr.vsc-nwjs) to run ct.js with an attached debugger. Use `gulp dev` instead of just `gulp` to run a dev service with live-reloading without opening ct.js in its default manner. + ## Releasing ct.js This is left for emergencies only, as Travis should prepare binaries for github and send them to itch.io as well @@ -91,4 +93,4 @@ The first run will be slow as it will download nw.js binaries. Next runs will us ## Naming conventions for commits -Use [Gitmoji-flavored Comigoji](https://comigo.gitlab.io/comigoji/#gitmoji) for naming your commits. \ No newline at end of file +Use [Gitmoji-flavored Comigoji](https://comigo.gitlab.io/comigoji/#gitmoji) for naming your commits. diff --git a/app/data/ct.release/inputs.js b/app/data/ct.release/inputs.js index e5f6980b6..5313b8702 100644 --- a/app/data/ct.release/inputs.js +++ b/app/data/ct.release/inputs.js @@ -71,7 +71,9 @@ class CtAction { if (ind !== -1) { this.methodMultipliers[ind] = multiplier; } else { + // eslint-disable-next-line no-console console.warning(`[ct.inputs] An attempt to change multiplier of a non-existent method "${code}" at event ${this.name}`); + // eslint-disable-next-line no-console console.trace(); } } diff --git a/app/data/i18n/English.json b/app/data/i18n/English.json index b3697fc7e..742a7d77c 100644 --- a/app/data/i18n/English.json +++ b/app/data/i18n/English.json @@ -126,15 +126,16 @@ }, "menu": { "ctIDE": "ct.IDE", - "exportDesktop": "Export for desktop", + "exportDesktop": "Export for desktop…", "texture": "Textures", - "launch": "Compile and run", + "launch": "Launch", "launchHotkeys": "(F5; Alt+F5 to run in your default browser)", "license": "License", "min": "Windowed", "modules": "Catmods", "patrons": "Patrons", "recentProjects": "Recent projects", + "restart": "Restart", "rooms": "Rooms", "save": "Save project", "startScreen": "Return to the starting screen", @@ -170,7 +171,9 @@ "disableAcceleration": "Disable graphics acceleration (needs restart)", "disableBuiltInDebugger": "Disable built-in debugger", "visitDiscordForGamedevSupport": "Join Discord server for gamedev support", - "postAnIssue": "Post an issue on Github" + "postAnIssue": "Post an issue on Github…", + "openProject": "Open a project…", + "openExample": "Open an example project…" }, "onboarding": { "hoorayHeader": "Wow! You've just created a project!", @@ -349,7 +352,23 @@ "typefacename": "Typeface name:", "fontweight": "Font weight:", "italic": "Is italic?", - "reimport": "Reimport" + "reimport": "Reimport", + "generateBitmapFont": "Also generate a bitmap font", + "bitmapFont": "Bitmap font", + "bitmapFontSize": "Font size:", + "bitmapFontLineHeight": "Line height:", + "resultingBitmapFontName": "Resource name", + "charset": "Charset:", + "charsets": { + "punctuation": "Digits and punctuation (you usually do need this)", + "basicLatin": "Basic Latin", + "latinExtended": "Latin extended", + "cyrillic": "Cyrillic", + "greekCoptic": "Greek and Coptic", + "custom": "Custom", + "allInFont": "Draw everything the font supports" + }, + "customCharsetHint": "Type all the letters you want to include, both in upper and lower case." }, "particleEmitters": { "emittersHeading": "Particle emitters", @@ -543,4 +562,4 @@ "is elegant and beautiful 🎩" ] } -} \ No newline at end of file +} diff --git a/app/package-lock.json b/app/package-lock.json index 1e99c58b4..d47c824ec 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -2863,6 +2863,15 @@ "resolved": "https://registry.npmjs.org/onecolor/-/onecolor-3.0.5.tgz", "integrity": "sha1-Nu/zIgE3nv3xGA+0ReUajiQl+fY=" }, + "opentype.js": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.3.tgz", + "integrity": "sha512-/qIY/+WnKGlPIIPhbeNjynfD2PO15G9lA/xqlX2bDH+4lc3Xz5GCQ68mqxj3DdUv6AJqCeaPvuAoH8mVL0zcuA==", + "requires": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + } + }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", @@ -3263,6 +3272,11 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, + "string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -3307,6 +3321,11 @@ "has-flag": "^3.0.0" } }, + "tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, "tmp": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", @@ -3524,7 +3543,7 @@ }, "xmlbuilder": { "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, "xmldom": { diff --git a/app/package.json b/app/package.json index 4ec0f7516..7a4443e8f 100644 --- a/app/package.json +++ b/app/package.json @@ -67,6 +67,7 @@ "monaco-editor": "^0.20.0", "monaco-themes": "^0.3.3", "node-static": "^0.7.11", + "opentype.js": "^1.3.3", "pixi-particles": "^4.2.1", "pixi.js-legacy": "5.1.2", "png2icons": "^2.0.1", diff --git a/gulpfile.js b/gulpfile.js index bda975fc3..60b7aa59e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -23,7 +23,7 @@ const path = require('path'), spawnise = require('./node_requires/spawnise'); -const nwVersion = '0.34.5', +const nwVersion = '0.45.6', platforms = ['osx64', 'win32', 'win64', 'linux32', 'linux64'], nwFiles = ['./app/**', '!./app/export/**', '!./app/projects/**', '!./app/exportDesktop/**', '!./app/cache/**', '!./app/.vscode/**', '!./app/JamGames/**']; @@ -118,7 +118,9 @@ const concatScripts = () => gulp.src(['./src/js/**', '!./src/js/3rdparty/riot.min.js']), gulp.src('./temp/riot.js') ) - .pipe(sourcemaps.init()) + .pipe(sourcemaps.init({ + largeFile: true + })) .pipe(concat('bundle.js')) .pipe(sourcemaps.write()) .pipe(gulp.dest('./app/data/')) @@ -135,12 +137,18 @@ const concatScripts = () => .on('change', fileChangeNotifier); const copyRequires = () => gulp.src('./src/node_requires/**/*') + .pipe(sourcemaps.init()) + // ¯\_(ツ)_/¯ + .pipe(sourcemaps.mapSources((sourcePath) => '../../src/' + sourcePath)) + .pipe(sourcemaps.write()) .pipe(gulp.dest('./app/data/node_requires')); const compileScripts = gulp.series(compileRiot, concatScripts); const icons = () => - gulp.src('./src/icons/**/*.svg') + gulp.src('./src/icons/**/*.svg', { + base: './src/icons' + }) .pipe(sprite()) .pipe(gulp.dest('./app/data')); @@ -528,7 +536,12 @@ const launchDevMode = done => { launchApp(); done(); }; +const launchDevModeNoNW = done => { + watch(); + done(); +}; const defaultTask = gulp.series(build, launchDevMode); +const devNoNW = gulp.series(build, launchDevModeNoNW); exports.lintJS = lintJS; exports.lintTags = lintTags; @@ -542,5 +555,6 @@ exports.build = build; exports.deploy = deploy; exports.deployOnly = deployOnly; exports.default = defaultTask; +exports.dev = devNoNW; exports.bakeCompletions = bakeCompletions; exports.bakeTypedefs = bakeTypedefs; diff --git a/src/js/gulpWatch.js b/src/js/gulpWatch.js index 066183416..bf43a852a 100644 --- a/src/js/gulpWatch.js +++ b/src/js/gulpWatch.js @@ -5,7 +5,13 @@ const reload = () => { if (!reloading) { reloading = true; - nw.App.quit(); + if (nw.App.fullArgv.find(arg => arg.indexOf('--remote-debugging-port') !== -1)) { + // Seems that we have an external debugger attached. Reload. + nw.Window.get().reload(); + } else { + // Quit and let gulp handle the reload. + nw.App.quit(); + } } }; gulp.watch(['./data/theme*.css', './index.html', './data/bundle.js', './data/js/**.js', './data/node_requires/**/*.js'], reload); diff --git a/src/js/loadProject.js b/src/js/loadProject.js index 03c79f2bf..ea52921f8 100644 --- a/src/js/loadProject.js +++ b/src/js/loadProject.js @@ -176,6 +176,12 @@ }; window.loadProject = proj => { + if (!proj) { + const baseMessage = 'An attempt to open a project with an empty path.'; + alertify.error(baseMessage + ' See the console for the call stack.'); + const err = new Error(baseMessage); + throw err; + } sessionStorage.projname = path.basename(proj); global.projdir = path.dirname(proj) + path.sep + path.basename(proj, '.ict'); diff --git a/src/js/migration/1.3.2.js b/src/js/migration/1.3.2.js index 87ce9e17f..4c2952f24 100644 --- a/src/js/migration/1.3.2.js +++ b/src/js/migration/1.3.2.js @@ -48,6 +48,17 @@ window.migrationProcess.push({ } } + /** + * Fonts can now be exported as bitmap fonts + */ + for (const font of project.fonts) { + font.bitmapFont = font.bitmapFont || false; + font.bitmapFontSize = font.bitmapFontSize || 16; + font.bitmapFontLineHeight = font.bitmapFontLineHeight || 18; + font.charsets = font.charsets || ['allInFont']; + font.customCharset = font.customCharset || ''; + } + resolve(); }) }); diff --git a/src/node_requires/exporter/fonts.js b/src/node_requires/exporter/fonts.js index a6e491256..8cf83facf 100644 --- a/src/node_requires/exporter/fonts.js +++ b/src/node_requires/exporter/fonts.js @@ -31,6 +31,7 @@ const bundleFonts = async function (proj, projdir, writeDir) { css += stringifyFont(font); })); } + await Promise.all(writePromises); return { css, @@ -38,7 +39,101 @@ const bundleFonts = async function (proj, projdir, writeDir) { }; }; +const charSets = { + punctuation: ' !"#$%&\'()*+,-./0123456789:;<=>?@[\\]^_`{|}~', + basicLatin: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', + latinExtended: 'ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀǁǂǃDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏ', + cyrillic: '«»ЀЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљњћќѝўџѠѡѢѣѤѥѦѧѨѩѪѫѬѭѮѯѰѱѲѳѴѵѶѷѸѹѺѻѼѽѾѿҀҁ҂о҃о҄о҅о҆о҇о҈о҉ҊҋҌҍҎҏҐґҒғҔҕҖҗҘҙҚқҜҝҞҟҠҡҢңҤҥҦҧҨҩҪҫҬҭҮүҰұҲҳҴҵҶҷҸҹҺһҼҽҾҿӀӁӂӃӄӅӆӇӈӉӊӋӌӍӎӏӐӑӒӓӔӕӖӗӘәӚӛӜӝӞӟӠӡӢӣӤӥӦӧӨөӪӫӬӭӮӯӰӱӲӳӴӵӶӷӸӹӺӻӼӽӾӿԀԁԂԃԄԅԆԇԈԉԊԋԌԍԎԏԔԕԖԗԘԙԚԛԜԝԞԟԠԡԢԣԤԥԦԧԨԩԪԫԬԭԮԯ', + greekCoptic: 'ͰͱͲͳʹ͵Ͷͷͺͻͼͽ;΄΅Ά·ΈΉΊΌΎΏΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩΪΫάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώϏϐϑϒϓϔϕϖϗϘϙϚϛϜϝϞϟϠϡϢϣϤϥϦϧϨϩϪϫϬϭϮϯϰϱϲϳϴϵ϶ϷϸϹϺϻϼϽϾϿ', + custom: '' +}; + +const specialCharMap = { + 38: '&', + 60: '<', + 32: 'space', + 62: '>', + 34: '"' +}; +const charCodeToXMLChar = code => { + if (code in specialCharMap) { + return specialCharMap[code]; + } + return String.fromCharCode(code); +}; + +const generateXML = function generateXML(fontData, ctFont, typefaceName) { + let XMLTemplate = ` + + + + + + `; + + for (const key in fontData.map) { + const c = fontData.map[key]; + XMLTemplate += `\n `; + } + + XMLTemplate += ` + + +`; + + return XMLTemplate; +}; + +const bakeBitmapFonts = function bakeBitmapFonts(proj, projdir, writeDir) { + const generator = require('./../resources/fonts/bitmapFontGenerator'); + const path = require('path'); + return Promise.all(proj.fonts.filter(font => font.bitmapFont) + .map(async font => { + const fCharsets = font.charsets || ['basicLatin']; + let letterList; + if (fCharsets.length === '1' && fCharsets[0] === 'allInFont') { + letterList = false; + } else { + letterList = fCharsets.reduce((acc, charset) => acc + (charSets[charset] || ''), ''); + } + if (fCharsets.indexOf('custom') !== -1) { + letterList += font.customCharset; + } + const settings = { + fill: '#ffffff', + // stroke: '#000000', + list: letterList, + height: font.bitmapFontSize, + margin: 2 + }; + const typefaceName = `${font.typefaceName}_${font.weight}${font.italic ? '_Italic' : ''}`, + xmlPath = `${font.uid}.xml`, + pngPath = `${font.uid}.png`; + const fontPath = path.join(projdir, 'fonts', font.origname); + const drawData = await generator(fontPath, path.join(writeDir, `${font.uid}.png`), settings); + + const xml = generateXML(drawData, font, typefaceName); + + await fs.writeFile(path.join(writeDir, `${font.uid}.xml`), xml, 'utf8'); + + return { + xmlPath, + pngPath, + typefaceName + }; + })) + .then(fontsMetadata => { + const loaderScript = fontsMetadata.reduce((acc, fontMeta) => + acc + `\n.add('${fontMeta.typefaceName}', '${fontMeta.xmlPath}')`, 'PIXI.Loader.shared'); + return { + loaderScript + }; + }); +}; + module.exports = { stringifyFont, - bundleFonts + bundleFonts, + bakeBitmapFonts, + generateXML }; diff --git a/src/node_requires/exporter/index.js b/src/node_requires/exporter/index.js index c2d5e5a77..4b139f91c 100644 --- a/src/node_requires/exporter/index.js +++ b/src/node_requires/exporter/index.js @@ -11,7 +11,7 @@ const {stringifyRooms, getStartingRoom} = require('./rooms'); const {stringifyStyles} = require('./styles'); const {stringifyTandems} = require('./emitterTandems'); const {stringifyTypes} = require('./types'); -const {bundleFonts} = require('./fonts'); +const {bundleFonts, bakeBitmapFonts} = require('./fonts'); const {bakeFavicons} = require('./icons'); const parseKeys = function (catmod, str, lib) { @@ -213,14 +213,16 @@ const exportCtProject = async (project, projdir) => { /* assets — run in parallel */ const texturesTask = packImages(project, writeDir); const skeletonsTask = packSkeletons(project, projdir, writeDir); + const bitmapFontsTask = bakeBitmapFonts(project, projdir, writeDir); const favicons = bakeFavicons(project, writeDir); const textures = await texturesTask; const skeletons = await skeletonsTask; + const bitmapFonts = await bitmapFontsTask; await favicons; buffer += (await sources['res.js']) .replace('/*@sndtotal@*/', project.sounds.length) - .replace('/*@res@*/', textures.res + '\n' + skeletons.loaderScript) + .replace('/*@res@*/', textures.res + '\n' + skeletons.loaderScript + '\n' + bitmapFonts.loaderScript) .replace('/*@textureregistry@*/', textures.registry) .replace('/*@textureatlases@*/', JSON.stringify(textures.atlases)) .replace('/*@skeletonregistry@*/', skeletons.registry) diff --git a/src/node_requires/i18n.js b/src/node_requires/i18n.js index 73bd187da..be0e70d8f 100644 --- a/src/node_requires/i18n.js +++ b/src/node_requires/i18n.js @@ -6,6 +6,7 @@ var i18n; const loadLanguage = lang => { var voc; + console.log('hello'); try { voc = fs.readJSONSync(`./data/i18n/${lang}.json`); } catch (e) { diff --git a/src/node_requires/monaco-themes/lucasdracula.json b/src/node_requires/monaco-themes/lucasdracula.json index d6f819e73..5ed913ee5 100644 --- a/src/node_requires/monaco-themes/lucasdracula.json +++ b/src/node_requires/monaco-themes/lucasdracula.json @@ -6,7 +6,7 @@ "background": "#11111f", "foreground": "#DAD6DA" }, { - "foreground": "#595d68", + "foreground": "#4f4c58", "fontStyle": "italic", "token": "comment" }, @@ -135,4 +135,4 @@ "textLink.foreground": "#f07178", "editorLink.activeForeground": "#f07178" } -} \ No newline at end of file +} diff --git a/src/node_requires/resources/fonts/bitmapFontGenerator/index.js b/src/node_requires/resources/fonts/bitmapFontGenerator/index.js new file mode 100644 index 000000000..8a4ff4a67 --- /dev/null +++ b/src/node_requires/resources/fonts/bitmapFontGenerator/index.js @@ -0,0 +1,153 @@ +/* eslint-disable max-len */ +const util = require('./util'); +const opentype = require('opentype.js'); + +const draw = function draw(ctx, glyphList, descend, options) { + var dict = {}; + + var drawX = 0; + var drawY = 0; + var drawHeight = options.baseline + descend; + var mg; + + glyphList.forEach((g, index) => { + if (g.glyph === void 0) { + if (options.width !== void 0) { + g.width = options.width; + } + ctx.drawImage(options.missingGlyph, drawX, drawY, g.width, drawHeight); + mg = { + x: drawX, y: drawY, width: g.width, height: drawHeight + }; + } else { + var drawWidth = options.width; + if (drawWidth === void 0) { + drawWidth = g.width; + } + if (drawX + drawWidth > ctx.canvas.width) { + drawX = 0; + drawY += drawHeight + options.margin; + } + var path = g.glyph.getPath(drawX + (drawWidth / 2) - (g.width / 2), drawY + options.baseline, options.height); + path.fill = options.fill; + path.stroke = options.stroke; + path.draw(ctx); + if (index === glyphList.length - 1) { + mg = { + x: drawX, y: drawY, width: drawWidth, height: drawHeight + }; + } else { + g.glyph.unicodes.forEach((unicode) => { + dict[unicode] = { + x: drawX, y: drawY, width: drawWidth, height: drawHeight + }; + }); + } + drawX += drawWidth + options.margin; + } + }); + + return { + map: dict, missingGlyph: mg + }; +}; + +const generateBitmapFont = async function generateBitmapFont(fontSrc, outputPath, options, callback) { + const fs = require('fs-extra'); + const buffer = await fs.readFile(fontSrc); + const font = opentype.parse(buffer.buffer); + + if (!options.list || options.list.length === 0) { + options.list = Object.keys(font.glyphs.glyphs) + .map(code => String.fromCharCode(font.glyphs.glyphs[code].unicode) || ' ') + .join(''); + } + + + var lostChars = []; + var glyphList = []; + Array.from(options.list).forEach((char) => { + const [glyph] = font.stringToGlyphs(char); + glyph.font = font; + if (glyph.unicodes.length === 0) { + lostChars.push(char); + } + const scale = 1 / font.unitsPerEm * options.height; + glyphList.push({ + glyph, + width: Math.ceil(glyph.advanceWidth * scale) + }); + }); + + if (isNaN(options.baseline)) { + options.baseline = util.getMaxBaseline(glyphList, options.height); + } + + // Update baseline value while adding missingGlyph to glyphList + if (options.missingGlyph === void 0 || typeof options.missingGlyph === 'string') { + // eslint-disable-next-line prefer-destructuring + var g = font.glyphs.glyphs[0]; + if (options.missingGlyph) { + g = font.charToGlyph(options.missingGlyph); + } + var scale = 1 / font.unitsPerEm * options.height; + g.font = font; + glyphList.push({ + glyph: g, + width: Math.ceil(g.advanceWidth * scale) + }); + if (options.baseline < (g.yMax || 0) * scale) { + options.baseline = Math.ceil((g.yMax || 0) * scale); + } + } + + var descend = util.getMinDescend(glyphList, options.height); + var adjustedHeight = util.getAdjustedHeight(descend, options.height, options.baseline); + + // Calculate the required canvas size + var canvasSize; + if (options.width === void 0) { + canvasSize = util.calculateCanvasSizeProp(options.list, glyphList, adjustedHeight, options.baseline + descend); + } else { + canvasSize = util.calculateCanvasSize(options.list, options.width, adjustedHeight); + } + + // Check if the created canvas size is valid + if (canvasSize.width > 8192 || canvasSize.height > 8192) { + callback('list is too long'); + return false; + } + if (canvasSize.width === -1 || canvasSize.height === -1) { + callback('char size is too small'); + return false; + } + + var canvas = document.createElement('canvas'); + canvas.width = canvasSize.width; + canvas.height = canvasSize.height; + var ctx = canvas.getContext('2d'); + + /* if (options.noAntiAlias) + ctx.antialias = "none";*/ + + // drawing + var drawResult = draw(ctx, glyphList, descend, options); + + // Notify about characters that could not be drawn + if (lostChars.length > 0) { + // eslint-disable-next-line no-console + console.warn('Cannot find ' + lostChars.join(',') + ' from the given font. ' + + 'Generated image does not include these characters. ' + + 'Try Using other font or characters.'); + } + await util.outputBitmapFont(outputPath, canvas, callback); + return { + map: drawResult.map, + missingGlyph: drawResult.missingGlyph, + width: options.width, + height: adjustedHeight, + canvas + }; +}; + +module.exports = generateBitmapFont; diff --git a/src/node_requires/resources/fonts/bitmapFontGenerator/util.js b/src/node_requires/resources/fonts/bitmapFontGenerator/util.js new file mode 100644 index 000000000..ebbd03c92 --- /dev/null +++ b/src/node_requires/resources/fonts/bitmapFontGenerator/util.js @@ -0,0 +1,113 @@ +/* eslint-disable max-len */ +const fs = require('fs').promises; + +const calculateCanvasSize = function calculateCanvasSize(text, charWidth, charHeight) { + if (charWidth <= 0 || charHeight <= 0) { + return { + width: -1, height: -1 + }; + } + + var textSize = text.split('').length + 1; // +1 is for the missing glyph + var canvasSquareSideSize = 1; + + // Find the length of the side of a square that can contain characters + while ((canvasSquareSideSize / charWidth) * (canvasSquareSideSize / charHeight) < textSize) { + canvasSquareSideSize *= 2; + } + var canvasWidth = canvasSquareSideSize; + + // CanvasSquareSideSize cannot be used because it may not be square + var tmpCanvasHeight = Math.ceil(textSize / Math.floor(canvasWidth / charWidth)) * charHeight; + var canvasHeight = 1; + while (canvasHeight < tmpCanvasHeight) { + canvasHeight *= 2; + } + + return { + width: canvasWidth, height: canvasHeight + }; +}; + +const canGoIn = function canGoIn(canvasSize, glyphList, charHeight) { + var drawX = 0; + var drawY = 0; + + glyphList.forEach(glyph => { + if (drawX + glyph.width > canvasSize.width) { + drawX = 0; + drawY += charHeight; + } + drawX += glyph.width; + }); + + return drawY + charHeight < canvasSize.height; +}; + +const calculateCanvasSizeProp = function calculateCanvasSizeProp( + text, + glyphList, + height, + charHeight +) { + var widthAverage = 0; + var widthMax = 0; + glyphList.forEach(glyph => { + if (glyph.width > widthMax) { + widthMax = glyph.width; + } + widthAverage += glyph.width; + }); + widthAverage /= glyphList.length; + + if (height <= 0) { + return { + width: -1, height: -1 + }; + } + // Use the average value to calculate the approximate size + var canvasSize = calculateCanvasSize(text, widthAverage, height); + // Increase the vertical width until the text can be entered + while (!canGoIn(canvasSize, glyphList, charHeight)) { + canvasSize.height *= 2; + } + return canvasSize; +}; + +const outputBitmapFont = function outputBitmapFont(outputPath, canvas) { + const buffer = Buffer.from(canvas.toDataURL().replace(/^data:image\/\w+;base64,/, ''), 'base64'); + return fs.writeFile(outputPath, buffer); +}; + +const getMaxBaseline = function getMaxBaseline(glyphList, height) { + const baseline = Math.ceil(Math.max(-Infinity, ...glyphList.map(glyph => { + var scale = 1 / glyph.glyph.font.unitsPerEm * height; + return (glyph.glyph.yMax || 0) * scale; + }))); + return baseline; +}; + +const getMinDescend = function getMinDescend(glyphList, height) { + var descend = Math.min(Infinity, ...glyphList.map((g) => { + var scale = 1 / g.glyph.font.unitsPerEm * height; + return (g.glyph.yMin || 0) * scale; + })); + return Math.ceil(Math.abs(descend)); +}; + +const getAdjustedHeight = function getAdjustedHeight(descend, height, baseline) { + var extraDescend = Math.ceil(descend - (height - baseline)); + var adjustedHeight = height; + if (extraDescend > 0) { + adjustedHeight += extraDescend; + } + return adjustedHeight; +}; + +module.exports = { + calculateCanvasSizeProp, + getAdjustedHeight, + outputBitmapFont, + getMaxBaseline, + getMinDescend +}; diff --git a/src/node_requires/resources/projects/index.js b/src/node_requires/resources/projects/index.js index 80cb06232..aa0aaee20 100644 --- a/src/node_requires/resources/projects/index.js +++ b/src/node_requires/resources/projects/index.js @@ -5,7 +5,19 @@ const getDefaultProjectDir = function () { return path.join(nw.App.startPath, 'projects'); }; +const getExamplesDir = function () { + const path = require('path'); + try { + require('gulp'); + // Most likely, we are in a dev environment + return path.join(nw.App.startPath, 'src/examples'); + } catch (e) { + return path.join(nw.App.startPath, 'examples'); + } +}; + module.exports = { defaultProject, - getDefaultProjectDir + getDefaultProjectDir, + getExamplesDir }; diff --git a/src/riotTags/debugger/debugger-screen-embedded.tag b/src/riotTags/debugger/debugger-screen-embedded.tag new file mode 100644 index 000000000..7a91b7a3c --- /dev/null +++ b/src/riotTags/debugger/debugger-screen-embedded.tag @@ -0,0 +1,231 @@ +// + Exposes this.reloadGame +debugger-screen-embedded(class="{opts.class} {flexrow: verticalLayout, flexcol: !verticalLayout}") + webview.tall#thePreview( + partition="persist:trusted" + ref="gameView" allownw + ) + .aResizer(ref="gutter" onmousedown="{gutterMouseDown}" class="{vertical: verticalLayout, horizontal: !verticalLayout}") + .flexfix( + style="flex: 0 0 auto; {verticalLayout? 'width:'+width+'px' : 'height:'+height+'px'}" + ) + webview.tall.flexfix-body( + partition="persist:trusted" src="empty.html" + ref="devtoolsView" allownw nwfaketop + style="overflow: hidden;" + ) + .flexfix-footer.aDebuggerToolbar.noshrink( + class="{vertical: verticalLayout} {tight: (verticalLayout && width < 1000) || (!verticalLayout && window.innerWidth < 1000)}" + ) + .debugger-toolbar-aButton(onclick="{togglePause}" title="{gamePaused? voc.resume : voc.pause}") + svg.feather + use(xlink:href="data/icons.svg#{gamePaused? 'play' : 'pause'}") + span {gamePaused? voc.resume : voc.pause} + .debugger-toolbar-aButton(onclick="{restartGame}" title="{voc.restartGame}") + svg.feather + use(xlink:href="data/icons.svg#rotate-cw") + span {voc.restartGame} + .debugger-toolbar-aButton(onclick="{restartRoom}" title="{voc.restartRoom}") + svg.feather + use(xlink:href="data/icons.svg#room-reload") + span {voc.restartRoom} + .debugger-toolbar-aButton(onclick="{displayRoomSelector}" title="{voc.switchRoom}") + svg.feather + use(xlink:href="data/icons.svg#room-switch") + span {voc.switchRoom} + + .debugger-toolbar-aDivider + + .debugger-toolbar-aButton(onclick="{makeScreenshot}" title="{voc.screenshot}") + svg.feather + use(xlink:href="data/icons.svg#camera") + //.debugger-toolbar-aButton(onclick="{toggleFullscreen}" title="{gameFullscreen? voc.exitFullscreen : voc.enterFullscreen}") + // svg.feather + // use(xlink:href="data/icons.svg#{gameFullscreen? 'minimize' : 'maximize'}-2") + .debugger-toolbar-aButton(onclick="{openQrCodes}" title="{voc.links}") + svg.feather + use(xlink:href="data/icons.svg#smartphone") + .debugger-toolbar-aButton(onclick="{openExternal}" title="{voc.openExternal}") + svg.feather + use(xlink:href="data/icons.svg#external-link") + + .debugger-toolbar-aDivider + + .debugger-toolbar-aButton(onclick="{flipLayout}") + svg.feather + use(xlink:href="data/icons.svg#layout-{verticalLayout? 'horizontal' : 'vertical'}") + debugger-modal(if="{showNetworkingModal}") + script. + this.namespace = 'debuggerToolbar'; + this.mixin(window.riotVoc); + + this.showNetworkingModal = false; + + const passedParams = this.opts.params; + if (passedParams.title) { + document.title = passedParams.title + ' — ct.js'; + } + + /* Gutter logic */ + const minSizeW = 400; + const minSizeH = 200; // This includes the height of all buttons + const getMaxSizeW = () => window.innerWidth - 300; + const getMaxSizeH = () => window.innerHeight - 300; + + this.verticalLayout = localStorage.debuggerLayour !== 'horizontal'; + this.width = Math.max(minSizeW, Math.min(getMaxSizeW(), localStorage.debuggerWidth || 500)); + this.height = Math.max(minSizeH, Math.min(getMaxSizeH(), localStorage.debuggerHeight || 300)); + + // iframes and webviews capture mousemove events needed for resize gutter; + // this overlay will prevent it + const catcher = document.createElement('div'); + const s = catcher.style; + s.position = 'fixed'; + s.left = s.right = s.top = s.bottom = '0'; + s.zIndex = 100; + s.cursor = 'ew-resize'; + + this.gutterMouseDown = () => { + this.dragging = true; + s.cursor = this.verticalLayout ? 'ew-resize' : 'ns-resize'; + document.body.appendChild(catcher); + }; + document.addEventListener('mousemove', e => { + if (!this.dragging) { + return; + } + if (this.verticalLayout) { + this.width = Math.max(minSizeW, Math.min(getMaxSizeW(), window.innerWidth - e.clientX)); + localStorage.debuggerWidth = this.width; + } else { + this.height = Math.max(minSizeH, Math.min(getMaxSizeH(), window.innerHeight - e.clientY)); + localStorage.debuggerHeight = this.height; + } + this.update(); + }); + document.addEventListener('mouseup', () => { + if (this.dragging) { + this.dragging = false; + document.body.removeChild(catcher); + } + }); + this.flipLayout = () => { + this.verticalLayout = !this.verticalLayout; + }; + + /* Bootstrap preview and debug views */ + this.on('mount', () => { + this.refs.gameView.addEventListener('contentload', () => { + this.refs.gameView.showDevTools(true, this.refs.devtoolsView); + setTimeout(() => { + this.refs.devtoolsView.executeScript({ + code: 'DevToolsAPI.showPanel(\'console\')', + mainWorld: true + }); + }, 1000); + this.refs.gameView.focus(); + }); + this.refs.gameView.setAttribute('src', passedParams.link); + }); + + /* Helper methods for buttons */ + this.switchRoom = room => { + this.refs.gameView.executeScript({ + code: `ct.rooms.switch('${room}')`, + mainWorld: true + }); + }; + this.displayRoomSelector = e => { + const menu = new nw.Menu(); + // Query for in-game rooms + this.refs.gameView.executeScript({ + code: 'JSON.stringify(Object.keys(ct.rooms.templates));', + mainWorld: true + }, rooms => { + JSON.parse(rooms).map(room => ({ + label: room, + click: () => { + this.switchRoom(room); + } + })) + .forEach(entry => menu.append(new nw.MenuItem(entry))); + menu.popup(e.clientX, e.clientY); + }); + }; + + /* Buttons' event listeners */ + this.togglePause = () => { + this.refs.gameView.executeScript({ + code: ` + if (PIXI.Ticker.shared.started) { + PIXI.Ticker.shared.stop(); + } else { + PIXI.Ticker.shared.start(); + } + !PIXI.Ticker.shared.started; + `, + mainWorld: true + }, paused => { + if (paused === 'false' || paused === false) { + this.gamePaused = false; + } else { + this.gamePaused = true; + } + this.update(); + }); + }; + this.restartGame = () => { + this.refs.gameView.reload(); + }; + this.restartRoom = () => { + this.refs.gameView.executeScript({ + code: 'ct.rooms.switch(ct.room.name);', + mainWorld: true + }); + }; + this.makeScreenshot = () => { + this.refs.gameView.executeScript({ + code: ` + var renderTexture = PIXI.RenderTexture.create({ + width: ct.pixiApp.renderer.width, + height: ct.pixiApp.renderer.height + }); + ct.pixiApp.renderer.render(ct.pixiApp.stage, renderTexture); + var canvas = ct.pixiApp.renderer.extract.canvas(renderTexture); + var dataURL = canvas.toDataURL('image/png'); + dataURL; + `, + mainWorld: true + }, dataURL => { + [dataURL] = dataURL; + window.showSaveDialog({ + filter: 'image/png', + defaultName: `${passedParams.title || 'Screenshot'}.png` + }).then(filename => { + const fs = require('fs'); + if (!filename) { + return; + } + const screenshotBase64 = dataURL.replace(/^data:image\/\w+;base64,/, ''); + const buf = Buffer.from(screenshotBase64, 'base64'); + const stream = fs.createWriteStream(filename); + stream.end(buf); + }); + }); + }; + this.toggleFullscreen = () => { + this.gameFullscreen = !this.gameFullscreen; + if (this.gameFullscreen) { + this.previewWindow.enterFullscreen(); + } else { + this.previewWindow.leaveFullscreen(); + } + }; + this.openQrCodes = () => { + this.showNetworkingModal = !this.showNetworkingModal; + }; + this.openExternal = () => { + if (passedParams.link) { + nw.Shell.openExternal(passedParams.link); + } + }; diff --git a/src/riotTags/font-editor.tag b/src/riotTags/font-editor.tag index 4e164c65e..40ab97843 100644 --- a/src/riotTags/font-editor.tag +++ b/src/riotTags/font-editor.tag @@ -1,19 +1,53 @@ font-editor.panel.view .panel.pad.left.tall.flexfix .flexfix-body - label.block - b {voc.typefacename} - br - input.wide(type="text" onchange="{wire('this.fontobj.typefaceName')}" value="{fontobj.typefaceName}") - label.block - b {voc.fontweight} - br - select(value="{fontobj.weight}" onchange="{wire('this.fontobj.weight')}") - each val in [100, 200, 300, 400, 500, 600, 700, 800, 900] - option(value=val)= val - label.checkbox - input(type="checkbox" checked="{fontobj.italic}" onchange="{wire('this.fontobj.italic')}") - b {voc.italic} + fieldset + label.block + b {voc.typefacename} + br + input.wide(type="text" onchange="{wire('this.fontobj.typefaceName')}" value="{fontobj.typefaceName}") + label.block + b {voc.fontweight} + br + select(value="{fontobj.weight}" onchange="{wire('this.fontobj.weight')}") + each val in [100, 200, 300, 400, 500, 600, 700, 800, 900] + option(value=val)= val + label.checkbox + input(type="checkbox" checked="{fontobj.italic}" onchange="{wire('this.fontobj.italic')}") + b {voc.italic} + fieldset + label.checkbox + input(type="checkbox" checked="{fontobj.bitmapFont}" onchange="{wire('this.fontobj.bitmapFont')}") + b {voc.generateBitmapFont} + h3(if="{fontobj.bitmapFont}") {voc.bitmapFont} + fieldset(if="{fontobj.bitmapFont}") + label.block + b {voc.bitmapFontSize} + br + input.wide(value="{fontobj.bitmapFontSize || 16}" onchange="{wire('this.fontobj.bitmapFontSize')}" type="number" min="1" max="144") + label.block + b {voc.bitmapFontLineHeight} + br + input.wide(value="{fontobj.bitmapFontLineHeight || 18}" onchange="{wire('this.fontobj.bitmapFontLineHeight')}" type="number" min="1" max="300") + .block + b {voc.charset} + label.checkbox(each="{val in charsetOptions}") + input( + type="checkbox" value="{val}" + checked="{fontobj.charsets.indexOf(val) !== -1}" + onchange="{toggleCharset}" + ) + | + | {voc.charsets[val]} + label.block(if="{fontobj.charsets.indexOf('custom') !== -1}") + textarea.wide( + value="{fontobj.customCharset}" + onchange="{wire('this.fontobj.customCharset')}" + ) + h3 {voc.resultingBitmapFontName} + copy-icon.toright(text="{fontobj.typefaceName}_{fontobj.weight}{fontobj.italic? '_Italic' : ''}") + code {fontobj.typefaceName}_{fontobj.weight}{fontobj.italic? '_Italic' : ''} + .clear .flexfix-footer button.wide(onclick="{fontSave}") svg.feather @@ -27,6 +61,23 @@ font-editor.panel.view this.mixin(window.riotVoc); this.mixin(window.riotWired); this.fontobj = this.opts.fontobj; + + this.charsetOptions = ['punctuation', 'basicLatin', 'latinExtended', 'cyrillic', 'greekCoptic', 'custom', 'allInFont']; + + this.toggleCharset = e => { + const {charsets} = this.fontobj; + const ind = charsets.indexOf(e.item.val); + if (ind === -1) { + if (e.item.val === 'allInFont' || + charsets.indexOf('allInFont') !== -1) { + charsets.length = 0; + } + charsets.push(e.item.val); + } else { + charsets.splice(ind, 1); + } + }; + this.oldTypefaceName = this.fontobj.typefaceName; this.fontSave = () => { this.parent.editingFont = false; @@ -44,4 +95,4 @@ font-editor.panel.view break; } } - }); \ No newline at end of file + }); diff --git a/src/riotTags/fonts-panel.tag b/src/riotTags/fonts-panel.tag index 0ac90bd03..740ecde5a 100644 --- a/src/riotTags/fonts-panel.tag +++ b/src/riotTags/fonts-panel.tag @@ -153,6 +153,11 @@ fonts-panel.flexfix.tall.fifty italic: false, origname: path.basename(dest), lastmod: Number(new Date()), + pixelFont: false, + pixelFontSize: 16, + pixelFontLineHeight: 18, + charsets: ['allInFont'], + customCharset: '', uid }; global.currentProject.fonts.push(obj); @@ -265,4 +270,4 @@ fonts-panel.flexfix.tall.fifty }); } }; - this.loadFonts(); \ No newline at end of file + this.loadFonts(); diff --git a/src/riotTags/main-menu.tag b/src/riotTags/main-menu.tag index 674d810f6..bd2300258 100644 --- a/src/riotTags/main-menu.tag +++ b/src/riotTags/main-menu.tag @@ -11,11 +11,13 @@ main-menu.flexcol li.it30(onclick="{saveProject}" title="{voc.save} (Control+S)" data-hotkey="Control+s") svg.feather use(xlink:href="data/icons.svg#save") - li.nbr.it30(onclick="{runProject}" title="{voc.launch} {voc.launchHotkeys}" data-hotkey="F5") - svg.feather - use(xlink:href="data/icons.svg#play") ul#mainnav.nav.tabs + li.nbl.it30(onclick="{runProject}" class="{active: tab === 'debug'}" title="{voc.launch} {voc.launchHotkeys}" data-hotkey="F5") + svg.feather + use(xlink:href="data/icons.svg#play") + span(if="{tab !== 'debug'}") {voc.launch} + span(if="{tab === 'debug'}") {voc.restart} li(onclick="{changeTab('project')}" class="{active: tab === 'project'}" data-hotkey="Control+1" title="Control+1") svg.feather use(xlink:href="data/icons.svg#sliders") @@ -49,6 +51,7 @@ main-menu.flexcol use(xlink:href="data/icons.svg#room") span {voc.rooms} div.flexitem.relative(if="{global.currentProject}") + debugger-screen-embedded(if="{tab === 'debug'}" params="{debugParams}" data-hotkey-scope="play" ref="debugger") project-settings(show="{tab === 'project'}" data-hotkey-scope="project") modules-panel(show="{tab === 'modules'}" data-hotkey-scope="modules") textures-panel(show="{tab === 'texture'}" data-hotkey-scope="texture") @@ -173,9 +176,19 @@ main-menu.flexcol runCtExport(global.currentProject, global.projdir) .then(() => { if (localStorage.disableBuiltInDebugger === 'yes') { + // Open in default browser nw.Shell.openExternal(`http://localhost:${server.address().port}/`); + } else if (this.tab === 'debug') { + // Restart the game as we already have the tab opened + this.refs.debugger.restartGame(); } else { - window.openDebugger(`http://localhost:${server.address().port}`); + // Open the debugger as usual + this.tab = 'debug'; + this.debugParams = { + title: global.currentProject.settings.authoring.title, + link: `http://localhost:${server.address().port}/` + }; + this.update(); } }) .catch(e => { @@ -477,6 +490,46 @@ main-menu.flexcol } }, { type: 'separator' + }, { + label: window.languageJSON.menu.openProject, + icon: 'folder', + click: () => { + alertify.confirm(window.languageJSON.common.reallyexit, () => { + window.showOpenDialog({ + defaultPath: require('./data/node_requires/resources/projects').getDefaultProjectDir(), + title: window.languageJSON.menu.openProject, + filter: '.ict' + }) + .then(projFile => { + if (!projFile) { + return; + } + window.signals.trigger('resetAll'); + window.loadProject(projFile); + }); + }); + } + }, { // The same as "Open project" item, but shows an examples' folder first + label: window.languageJSON.menu.openExample, + click: () => { + alertify.confirm(window.languageJSON.common.reallyexit, () => { + window.showOpenDialog({ + defaultPath: require('./data/node_requires/resources/projects').getExamplesDir(), + title: window.languageJSON.menu.openProject, + filter: '.ict' + }) + .then(projFile => { + if (!projFile) { + return; + } + window.signals.trigger('resetAll'); + window.loadProject(projFile); + }); + }); + } + }, { + label: window.languageJSON.intro.latest, + submenu: recentProjectsSubmenu }, { label: window.languageJSON.menu.startScreen, click: () => { @@ -486,9 +539,6 @@ main-menu.flexcol } }); } - }, { - label: window.languageJSON.intro.latest, - submenu: recentProjectsSubmenu }, { type: 'separator' }, { diff --git a/src/riotTags/shared/asset-viewer.tag b/src/riotTags/shared/asset-viewer.tag index 8c55ceedf..a4651fc0e 100644 --- a/src/riotTags/shared/asset-viewer.tag +++ b/src/riotTags/shared/asset-viewer.tag @@ -128,4 +128,4 @@ asset-viewer.flexfix(class="{opts.namespace} {opts.class}") localStorage[key] = localStorage[key] === 'list' ? 'grid' : 'list'; }; - this.updateList(); \ No newline at end of file + this.updateList(); diff --git a/src/riotTags/shared/copy-icon.tag b/src/riotTags/shared/copy-icon.tag new file mode 100644 index 000000000..f69886418 --- /dev/null +++ b/src/riotTags/shared/copy-icon.tag @@ -0,0 +1,19 @@ +// + Displays a little inline icon that copies a defined string on click. That's it. + + @attribute text (string) + The text to copy + +copy-icon(onclick="{copy}") + svg.feather.a(class="{success: copied}") + use(xlink:href="data/icons.svg#{copied ? 'check' : 'copy'}") + script. + const defaultText = 'There should have been some useful text, but something wrong happened. Please report about it.'; + this.copy = () => { + nw.Clipboard.get().set(this.opts.text || defaultText, 'text'); + this.copied = true; + setTimeout(() => { + this.copied = false; + this.update(); + }, 1000); + }; \ No newline at end of file diff --git a/src/styl/common.styl b/src/styl/common.styl index b9e186b8e..440acf0c4 100644 --- a/src/styl/common.styl +++ b/src/styl/common.styl @@ -30,6 +30,13 @@ body bottom 0 position absolute +.error + color error +.success + color success +.warning + color warning + .borderall border 1px solid borderBright .borderleft diff --git a/src/styl/inputs.styl b/src/styl/inputs.styl index fb5801291..da3753ada 100644 --- a/src/styl/inputs.styl +++ b/src/styl/inputs.styl @@ -116,14 +116,13 @@ input[type="reset"], top 50% transform translate(0, -50%) -/* Большой переключатель */ -siz = 4em +/* Large checkbox/toggle as seen in the modules tab */ .bigpower cursor pointer display block float left - width siz - height siz + width 4em + height @width border-radius 50% 10% 50% 50% background -webkit-linear-gradient(top, background 0, borderPale 100%) box-shadow 0 0 0.2em backgroundDeeper inset diff --git a/src/styl/tags/debugger/debugger-screen.styl b/src/styl/tags/debugger/debugger-screen.styl index d462f69ad..2110d8c03 100644 --- a/src/styl/tags/debugger/debugger-screen.styl +++ b/src/styl/tags/debugger/debugger-screen.styl @@ -13,5 +13,8 @@ height auto &.tight .debugger-toolbar-aButton > span display none -debugger-screen #thePreview - overflow hidden \ No newline at end of file +debugger-screen, debugger-screen-embedded + #thePreview + overflow hidden +debugger-screen-embedded + @extends .tall diff --git a/src/styl/tags/debugger/debugger-toolbar.styl b/src/styl/tags/debugger/debugger-toolbar.styl index 5dfa9a6ba..3f14f723f 100644 --- a/src/styl/tags/debugger/debugger-toolbar.styl +++ b/src/styl/tags/debugger/debugger-toolbar.styl @@ -18,11 +18,11 @@ debugger-toolbar justify-content space-around line-height 38px .&-aDragger, .&-aButton - height 100% align-content stretch padding 0 0.5rem .&-aDragger color borderBright + height 100% cursor move -webkit-app-region drag app-region drag diff --git a/src/styl/themeDay.styl b/src/styl/themeDay.styl index 70f366332..f019ebd2e 100644 --- a/src/styl/themeDay.styl +++ b/src/styl/themeDay.styl @@ -36,13 +36,6 @@ orange = warning theme = 'Day' themeDark = false -.error - color error -.success - color success -.warning - color warning - borderPale = #e1e2e5 borderBright = #c8cdd1 diff --git a/src/styl/themeHorizon.styl b/src/styl/themeHorizon.styl index 628164a90..7bb0b26dd 100644 --- a/src/styl/themeHorizon.styl +++ b/src/styl/themeHorizon.styl @@ -43,14 +43,6 @@ cyan = #59E1E3 theme = 'Horizon' themeDark = true -.error - color error -.success - color success -.warning - color warning - - @require 'hvost.styl' @require '3rdParty/*.styl' diff --git a/src/styl/themeLucasDracula.styl b/src/styl/themeLucasDracula.styl index d64b2ed90..7189a7138 100644 --- a/src/styl/themeLucasDracula.styl +++ b/src/styl/themeLucasDracula.styl @@ -39,15 +39,6 @@ orange = warning theme = 'LucasDracula' themeDark = true -.error - color error -.success - color success -.warning - color warning - - - @require 'hvost.styl' @require '3rdParty/*.styl' diff --git a/src/styl/themeNight.styl b/src/styl/themeNight.styl index 1fd8260c8..2b2404b13 100644 --- a/src/styl/themeNight.styl +++ b/src/styl/themeNight.styl @@ -42,13 +42,6 @@ orange = warning theme = 'Night' themeDark = true -.error - color error -.success - color success -.warning - color warning - @require 'hvost.styl' @require '3rdParty/*.styl' diff --git a/src/styl/themeSpringStream.styl b/src/styl/themeSpringStream.styl index 62eb1bb9a..ec6b603dd 100644 --- a/src/styl/themeSpringStream.styl +++ b/src/styl/themeSpringStream.styl @@ -63,13 +63,6 @@ orange = warning theme = 'Spring Stream' themeDark = false -.error - color error -.success - color success -.warning - color warning - borderPale = #d6dedd borderBright = #d6dedd diff --git a/src/styl/typography.styl b/src/styl/typography.styl index 655d90b33..15c47c99f 100644 --- a/src/styl/typography.styl +++ b/src/styl/typography.styl @@ -9,9 +9,18 @@ a, .a color act cursor pointer text-decoration none - trans + {trans} &:hover color accent1 + &.error + color error + &.success + color success + &.warning + color warning + .error, .success, .warning + &:hover + opacity 0.8 dd margin 0 dt