diff --git a/misc/icons/source/common-warning-red.svg b/misc/icons/source/common-warning-red.svg new file mode 100644 index 00000000..ba09fc4d --- /dev/null +++ b/misc/icons/source/common-warning-red.svg @@ -0,0 +1,64 @@ + + + +image/svg+xml + + \ No newline at end of file diff --git a/src/css/animations.css b/src/css/animations.css index 83463b4a..f24f9e72 100644 --- a/src/css/animations.css +++ b/src/css/animations.css @@ -1,3 +1,9 @@ @keyframes fade { 50% { opacity: 0.5; } } + +@keyframes glow { + 0% { opacity: 0.66; } + 50% { opacity: 1; } + 100% { opacity: 0.66; } +} diff --git a/src/css/dialogs-performance-info.css b/src/css/dialogs-performance-info.css new file mode 100644 index 00000000..132e8954 --- /dev/null +++ b/src/css/dialogs-performance-info.css @@ -0,0 +1,66 @@ +.performance-link { + display: none; + position: fixed; + bottom: 10px; + right: 10px; + z-index: 11000; + cursor: pointer; + opacity: 0; + transition : opacity 0.3s; +} + +.performance-link.visible { + display: block; + opacity: 0.66; + animation: glow 2s infinite; +} + +.performance-link.visible:hover { + opacity: 1; + animation: none; +} + +#dialog-container.performance-info { + width: 500px; + height: 525px; + top : 50%; + left : 50%; + position : absolute; + margin-left: -250px; +} + +.dialog-performance-info-body { + font-size: 13px; + letter-spacing: 1px; + padding: 10px 20px; +} + +.dialog-performance-info-body ul { + border: 1px solid #666; + padding: 5px; + border-radius: 3px; +} + +.dialog-performance-info-body li { + list-style-type: initial; + margin: 0 20px; +} + +.dialog-performance-info-body sup { + color: gold; + cursor: pointer; +} + +.show #dialog-container.performance-info { + margin-top: -300px; +} + +.dialog-performance-info-body .warning-icon { + float: left; + margin-top: 10px; +} + +.dialog-performance-info-body .warning-icon-info { + overflow: hidden; + margin-left: 30px; +} diff --git a/src/img/icons/common/common-warning-red.png b/src/img/icons/common/common-warning-red.png new file mode 100644 index 00000000..c5f4ea80 Binary files /dev/null and b/src/img/icons/common/common-warning-red.png differ diff --git a/src/img/icons/common/common-warning-red@2x.png b/src/img/icons/common/common-warning-red@2x.png new file mode 100644 index 00000000..bb24901b Binary files /dev/null and b/src/img/icons/common/common-warning-red@2x.png differ diff --git a/src/index.html b/src/index.html index d0f5f478..6d52399d 100644 --- a/src/index.html +++ b/src/index.html @@ -71,13 +71,17 @@ @@include('templates/misc-templates.html', {}) @@include('templates/popup-preview.html', {}) -   +   + @@include('templates/dialogs/create-palette.html', {}) @@include('templates/dialogs/import-image.html', {}) @@include('templates/dialogs/browse-local.html', {}) @@include('templates/dialogs/cheatsheet.html', {}) + @@include('templates/dialogs/performance-info.html', {}) @@include('templates/settings/application.html', {}) diff --git a/src/js/Events.js b/src/js/Events.js index 75c37067..65f921f1 100644 --- a/src/js/Events.js +++ b/src/js/Events.js @@ -80,6 +80,8 @@ var Events = { CURRENT_COLORS_UPDATED : 'CURRENT_COLORS_UPDATED', + PERFORMANCE_REPORT_CHANGED : 'PERFORMANCE_REPORT_CHANGED', + // Tests MOUSE_EVENT : 'MOUSE_EVENT', KEYBOARD_EVENT : 'KEYBOARD_EVENT', diff --git a/src/js/app.js b/src/js/app.js index 83ce44a4..2959beb6 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -153,6 +153,13 @@ document.querySelector('#drawing-canvas-container')); this.fileDropperService.init(); + this.userWarningController = new pskl.controller.UserWarningController(this.piskelController); + this.userWarningController.init(); + + this.performanceReportService = new pskl.service.performance.PerformanceReportService( + this.piskelController, this.currentColorsService); + this.performanceReportService.init(); + this.drawingLoop = new pskl.rendering.DrawingLoop(); this.drawingLoop.addCallback(this.render, this); this.drawingLoop.start(); diff --git a/src/js/controller/UserWarningController.js b/src/js/controller/UserWarningController.js new file mode 100644 index 00000000..c1b62147 --- /dev/null +++ b/src/js/controller/UserWarningController.js @@ -0,0 +1,55 @@ +(function () { + var ns = $.namespace('pskl.controller'); + + ns.UserWarningController = function (piskelController, currentColorsService) { + this.piskelController = piskelController; + this.currentColorsService = currentColorsService; + }; + + // This method is not attached to the prototype because we want to trigger it + // from markup generated for a notification message. + ns.UserWarningController.showPerformanceInfoDialog = function () { + $.publish(Events.DIALOG_DISPLAY, { + dialogId: 'performance-info' + }); + }; + + ns.UserWarningController.prototype.init = function () { + $.subscribe(Events.PERFORMANCE_REPORT_CHANGED, this.onPerformanceReportChanged_.bind(this)); + + this.performanceLinkEl = document.querySelector('.performance-link'); + pskl.utils.Event.addEventListener( + this.performanceLinkEl, + 'click', + ns.UserWarningController.showPerformanceInfoDialog, + this + ); + }; + + ns.UserWarningController.prototype.destroy = function () { + pskl.utils.Event.removeAllEventListeners(this); + this.performanceLinkEl = null; + }; + + ns.UserWarningController.prototype.onPerformanceReportChanged_ = function (event, report) { + var shouldDisplayWarning = report.hasProblem(); + + // Check if a performance warning is already displayed. + var isWarningDisplayed = this.performanceLinkEl.classList.contains('visible'); + + // Show/hide the performance warning link depending on the received report. + this.performanceLinkEl.classList.toggle('visible', shouldDisplayWarning); + + // Show a notification message if the new report indicates a performance issue + // and we were not displaying a warning before. + if (shouldDisplayWarning && !isWarningDisplayed) { + $.publish(Events.SHOW_NOTIFICATION, [{ + 'content': 'Performance problem detected, ' + + '' + + 'learn more?', + 'hideDelay' : 5000 + }]); + } + }; +})(); diff --git a/src/js/controller/dialogs/DialogsController.js b/src/js/controller/dialogs/DialogsController.js index 7afb6561..cf6a9e63 100644 --- a/src/js/controller/dialogs/DialogsController.js +++ b/src/js/controller/dialogs/DialogsController.js @@ -17,6 +17,10 @@ 'import-image' : { template : 'templates/dialogs/import-image.html', controller : ns.ImportImageController + }, + 'performance-info' : { + template : 'templates/dialogs/performance-info.html', + controller : ns.PerformanceInfoController } }; diff --git a/src/js/controller/dialogs/PerformanceInfoController.js b/src/js/controller/dialogs/PerformanceInfoController.js new file mode 100644 index 00000000..87fd57b5 --- /dev/null +++ b/src/js/controller/dialogs/PerformanceInfoController.js @@ -0,0 +1,11 @@ +(function () { + var ns = $.namespace('pskl.controller.dialogs'); + + ns.PerformanceInfoController = function () {}; + + pskl.utils.inherit(ns.PerformanceInfoController, ns.AbstractDialogController); + + ns.PerformanceInfoController.prototype.init = function () { + this.superclass.init.call(this); + }; +})(); diff --git a/src/js/service/performance/PerformanceReport.js b/src/js/service/performance/PerformanceReport.js new file mode 100644 index 00000000..2cdebf20 --- /dev/null +++ b/src/js/service/performance/PerformanceReport.js @@ -0,0 +1,45 @@ +(function () { + var ns = $.namespace('pskl.service.performance'); + + /** + * We consider that piskel should behave correctly for a sprite with the following specs: + * - 256*256 + * - 30 frames + * - 5 layers + * - 30 colors + * Based on these assumptions, as well as a few arbitrary hard limits we try to check + * if the provided sprite might present a performance issue. + * + * @param {Piskel} piskel the sprite to analyze + * @param {Number} colorsCount number of colors for the current sprite + * (not part of the piskel model so has to be provided separately). + */ + ns.PerformanceReport = function (piskel, colorsCount) { + var pixels = piskel.getWidth() * piskel.getHeight(); + this.resolution = pixels > (500 * 500); + + var layersCount = piskel.getLayers().length; + this.layers = layersCount > 25; + + var framesCount = piskel.getLayerAt(0).size(); + this.frames = framesCount > 100; + + this.colors = colorsCount > 100; + + var overallScore = (pixels / 2500) + (layersCount * 4) + framesCount + colorsCount; + this.overall = overallScore > 100; + }; + + ns.PerformanceReport.prototype.equals = function (report) { + return (report instanceof ns.PerformanceReport && + this.resolution == report.resolution && + this.layers == report.layers && + this.frames == report.frames && + this.colors == report.colors && + this.overall == report.overall); + }; + + ns.PerformanceReport.prototype.hasProblem = function () { + return this.resolution || this.layers || this.frames || this.colors || this.overall; + }; +})(); diff --git a/src/js/service/performance/PerformanceReportService.js b/src/js/service/performance/PerformanceReportService.js new file mode 100644 index 00000000..43a8ab8b --- /dev/null +++ b/src/js/service/performance/PerformanceReportService.js @@ -0,0 +1,25 @@ +(function () { + var ns = $.namespace('pskl.service.performance'); + + ns.PerformanceReportService = function (piskelController, currentColorsService) { + this.piskelController = piskelController; + this.currentColorsService = currentColorsService; + + this.currentReport = null; + }; + + ns.PerformanceReportService.prototype.init = function () { + $.subscribe(Events.HISTORY_STATE_SAVED, this.createReport_.bind(this)); + }; + + ns.PerformanceReportService.prototype.createReport_ = function () { + var report = new ns.PerformanceReport( + this.piskelController.getPiskel(), + this.currentColorsService.getCurrentColors().length); + + if (!report.equals(this.currentReport)) { + $.publish(Events.PERFORMANCE_REPORT_CHANGED, [report]); + this.currentReport = report; + } + }; +})(); diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index e5d6f605..cb2e8d3d 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -115,6 +115,7 @@ "js/controller/NotificationController.js", "js/controller/TransformationsController.js", "js/controller/CanvasBackgroundController.js", + "js/controller/UserWarningController.js", // Settings sub-controllers "js/controller/settings/AbstractSettingController.js", @@ -139,6 +140,7 @@ "js/controller/dialogs/ImportImageController.js", "js/controller/dialogs/BrowseLocalController.js", "js/controller/dialogs/CheatsheetController.js", + "js/controller/dialogs/PerformanceInfoController.js", // Dialogs controller "js/controller/dialogs/DialogsController.js", @@ -181,6 +183,8 @@ "js/service/FileDropperService.js", "js/service/SelectedColorsService.js", "js/service/MouseStateService.js", + "js/service/performance/PerformanceReport.js", + "js/service/performance/PerformanceReportService.js", "js/service/storage/LocalStorageService.js", "js/service/storage/GalleryStorageService.js", "js/service/storage/DesktopStorageService.js", diff --git a/src/piskel-style-list.js b/src/piskel-style-list.js index 9199543e..a208b9c1 100644 --- a/src/piskel-style-list.js +++ b/src/piskel-style-list.js @@ -22,6 +22,7 @@ "css/dialogs-cheatsheet.css", "css/dialogs-create-palette.css", "css/dialogs-import-image.css", + "css/dialogs-performance-info.css", "css/notifications.css", "css/toolbox.css", "css/toolbox-layers-list.css", diff --git a/src/templates/dialogs/performance-info.html b/src/templates/dialogs/performance-info.html new file mode 100644 index 00000000..98d72da1 --- /dev/null +++ b/src/templates/dialogs/performance-info.html @@ -0,0 +1,32 @@ + \ No newline at end of file