diff --git a/example/index-mobile.html b/example/index-mobile.html new file mode 100644 index 0000000..8abfa5c --- /dev/null +++ b/example/index-mobile.html @@ -0,0 +1,20 @@ + + + + + + +ChickenPaint testbed + + + + + + + + +

ChickenPaint testbed

+
+

+ + \ No newline at end of file diff --git a/example/index-mobile.js b/example/index-mobile.js new file mode 100644 index 0000000..f2d80d8 --- /dev/null +++ b/example/index-mobile.js @@ -0,0 +1,14 @@ +document.addEventListener("DOMContentLoaded", function() { + new ChickenPaint({ + uiElem: document.getElementById("chickenpaint-parent"), + //loadImageUrl: "./uploaded.png", + //loadChibiFileUrl: "./uploaded.chi", + saveUrl: "save.php", + postUrl: "posting.php", + exitUrl: "forum.php", + allowDownload: true, + resourcesRoot: "../resources/", + disableBootstrapAPI: true, + fullScreenMode: "force" + }); +}); diff --git a/example/index.js b/example/index.js index a80be40..cce26da 100644 --- a/example/index.js +++ b/example/index.js @@ -8,6 +8,7 @@ document.addEventListener("DOMContentLoaded", function() { exitUrl: "forum.php", allowDownload: true, resourcesRoot: "../resources/", - disableBootstrapAPI: true + disableBootstrapAPI: true, + fullScreenMode: "auto" }); }); diff --git a/js/ChickenPaint.js b/js/ChickenPaint.js index 5480e4d..7327937 100644 --- a/js/ChickenPaint.js +++ b/js/ChickenPaint.js @@ -89,6 +89,10 @@ function checkBrowserSupport() { return true; } +function isSmallScreen() { + return $(window).width() < 400 || $(window).height() < 768; +} + function createDrawingTools() { let tools = new Array(ChickenPaint.T_MAX); @@ -268,40 +272,45 @@ function createDrawingTools() { /** * @typedef {Object} ChickenPaintOptions * - * @property {Element} uiElem - DOM element to insert ChickenPaint into (required) + * @property {Element} uiElem - DOM element to insert ChickenPaint into * - * @property {Function} onLoaded - Callback to call when artwork loading completes + * @property {Function} [onLoaded] - Callback to call when artwork loading completes * - * @property {int} canvasWidth - Width in pixels to use when creating blank canvases (defaults to 800) - * @property {int} canvasHeight - Height in pixels to use when creating blank canvases (defaults to 600) - * @property {int} rotation - Integer from [0..3], number of 90 degree right rotations that should be applied to + * @property {int} [canvasWidth] - Width in pixels to use when creating blank canvases (defaults to 800) + * @property {int} [canvasHeight] - Height in pixels to use when creating blank canvases (defaults to 600) + * @property {int} [rotation] - Integer from [0..3], number of 90 degree right rotations that should be applied to * the canvas after loading * - * @property {string} saveUrl - URL to POST the drawing to to save it - * @property {string} postUrl - URL to navigate to after saving is successful and the user chooses to see/publish + * @property {string} [saveUrl] - URL to POST the drawing to to save it + * @property {string} [postUrl] - URL to navigate to after saving is successful and the user chooses to see/publish * their finished product - * @property {string} exitUrl - URL to navigate to after saving is successful and the user chooses to exit (optional) - * @property {string} testUrl - URL that ChickenPaint can simulate a drawing upload to to test the user's + * @property {string} [exitUrl] - URL to navigate to after saving is successful and the user chooses to exit (optional) + * @property {string} [testUrl] - URL that ChickenPaint can simulate a drawing upload to to test the user's * permissions/connection (optional) * - * @property {string} loadImageUrl - URL of PNG/JPEG image to load for editing (optional) - * @property {string} loadChibiFileUrl - URL of .chi file to load for editing (optional). Used in preference to loadImage. - * @property {string} loadSwatchesUrl - URL of an .aco palette to load (optional) - * @property {CPArtwork} artwork - Artwork to load into ChickenPaint (if you've already created one) + * @property {string} [loadImageUrl] - URL of PNG/JPEG image to load for editing (optional) + * @property {string} [loadChibiFileUrl] - URL of .chi file to load for editing (optional). Used in preference to loadImage. + * @property {string} [loadSwatchesUrl] - URL of an .aco palette to load (optional) + * @property {CPArtwork} [artwork] - Artwork to load into ChickenPaint (if you've already created one) * - * @property {boolean} allowMultipleSends - Allow the drawing to be sent to the server multiple times (saving does not + * @property {boolean} [allowMultipleSends] - Allow the drawing to be sent to the server multiple times (saving does not * immediately end drawing session). - * @property {boolean} allowDownload - Allow the drawing to be saved to the user's computer - * @property {boolean} allowFullScreen - Allow the drawing tool to enter "full screen" mode, where the rest of the page - * contents will be hidden + * @property {boolean} [allowDownload] - Allow the drawing to be saved to the user's computer * - * @property {boolean} disableBootstrapAPI - Disable Bootstrap's data API on the root of the document. This speeds up + * @property {"allow"|"auto"|"force"|"disable"} [fullScreenMode] - Control the behaviour of the full screen option: + * allow - Don't automatically enter full screen mode, but allow it to be + * chosen manually (default) + * auto - Automatically enter full screen mode on startup on small screens + * force - Enter full screen mode at startup and do not provide option to leave + * disable - Don't allow full screen mode at all + * + * @property {boolean} [disableBootstrapAPI] - Disable Bootstrap's data API on the root of the document. This speeds up * things considerably. * * @property {string} resourcesRoot - URL to the directory that contains the gfx/css etc directories (relative to the * page that ChickenPaint is loaded on) * - * @property {string} language - Provide an explicit ISO language code here (e.g. "ja_JP") to override the guessed browser language + * @property {string} [language] - Provide an explicit ISO language code here (e.g. "ja_JP") to override the guessed browser language * Unsupported languages will fall back to English. * Currently only "en" and "ja" are available. */ @@ -349,7 +358,8 @@ export default function ChickenPaint(options) { preTransformMode = curMode, curGradient = [0xFF000000, 0xFFFFFFFF], - fullScreenMode = false, + smallScreenMode = false, + isFullScreen = false, tools = createDrawingTools(), @@ -360,17 +370,10 @@ export default function ChickenPaint(options) { CPFullScreen: { action: function () { - fullScreenMode = !fullScreenMode; - - $("body").toggleClass("chickenpaint-full-screen", fullScreenMode); - $(uiElem).toggleClass("chickenpaint-full-screen", fullScreenMode); - - setTimeout(function () { - mainGUI.setFullScreenMode(fullScreenMode); - }, 200); + that.setFullScreen(!isFullScreen); }, isSupported: function() { - return options.allowFullScreen !== false; + return !(options.fullScreenMode === "disable" || options.fullScreenMode === "force"); }, modifies: {gui: true} }, @@ -1231,7 +1234,31 @@ export default function ChickenPaint(options) { // callCPEventListeners(); TODO }; + + this.setSmallScreenMode = function(small) { + if (smallScreenMode !== small) { + smallScreenMode = small; + + $(uiElem).toggleClass("chickenpaint-small-screen", smallScreenMode); + that.emitEvent("smallScreen", [smallScreenMode]); + } + }; + + this.getSmallScreenMode = function() { + return smallScreenMode; + }; + + this.setFullScreen = function(newVal) { + if (isFullScreen !== newVal) { + isFullScreen = newVal; + + $("body").toggleClass("chickenpaint-full-screen", isFullScreen); + $(uiElem).toggleClass("chickenpaint-full-screen", isFullScreen); + that.emitEvent("fullScreen", [isFullScreen]); + } + }; + function installUnsavedWarning() { if (isEventSupported("onbeforeunload")) { window.addEventListener("beforeunload", function(e) { @@ -1256,11 +1283,14 @@ export default function ChickenPaint(options) { if (!uiElem) { return; } - + that.artwork.on("editModeChanged", onEditModeChanged); mainGUI = new CPMainGUI(that, uiElem); - + + that.emitEvent("fullScreen", [isFullScreen]); + that.emitEvent("smallScreen", [smallScreenMode]); + setTool(ChickenPaint.T_PEN); mainGUI.arrangePalettes(); @@ -1296,6 +1326,17 @@ export default function ChickenPaint(options) { if (options.disableBootstrapAPI) { $(document).off('.data-api'); } + + this.setSmallScreenMode(isSmallScreen()); + + switch (options.fullScreenMode) { + case "force": + this.setFullScreen(true); + break; + case "auto": + this.setFullScreen(smallScreenMode); + break; + } if (options.loadImageUrl || options.loadChibiFileUrl) { let diff --git a/js/gui/CPMainGUI.js b/js/gui/CPMainGUI.js index b5a9fd5..8548095 100644 --- a/js/gui/CPMainGUI.js +++ b/js/gui/CPMainGUI.js @@ -78,10 +78,12 @@ export default function CPMainGUI(controller, uiElem) { }; this.setFullScreenMode = function(value) { - fullScreenMode = value; + if (fullScreenMode !== value) { + fullScreenMode = value; - that.resize(); - that.arrangePalettes(); + that.resize(); + that.arrangePalettes(); + } }; this.resize = function() { diff --git a/js/gui/CPPalette.js b/js/gui/CPPalette.js index dd1e7f9..16baa95 100644 --- a/js/gui/CPPalette.js +++ b/js/gui/CPPalette.js @@ -24,7 +24,15 @@ import $ from "jquery"; import EventEmitter from "wolfy87-eventemitter"; import {_} from "../languages/lang"; -const isInLowResolutionMode = window.innerHeight < 820 || window.innerWidth < 400; +const + DRAG_START_THRESHOLD = 5; + +function distanceGreaterThan(a, b, threshold) { + let + dist = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); + + return dist > threshold * threshold; +} export default function CPPalette(cpController, className, title, resizeVert, resizeHorz) { this.title = _(title); @@ -41,6 +49,7 @@ export default function CPPalette(cpController, className, title, resizeVert, re vertHandle = null, horzHandle = null, + dragStartPos, dragAction, dragOffset, @@ -88,15 +97,29 @@ export default function CPPalette(cpController, className, title, resizeVert, re this.setHeight(height); }; - this.toggleBodyElementVisibility = function() { - if (isInLowResolutionMode) { - $(containerElement).toggleClass("collapsed"); - } + /** + * @param {boolean} [collapse] + */ + this.toggleCollapse = function(collapse) { + $(containerElement).toggleClass("collapsed", collapse); }; function paletteHeaderPointerMove(e) { - if (e.buttons != 0 && dragAction == "move") { - that.setLocation(e.pageX - dragOffset.x, e.pageY - dragOffset.y); + if (e.buttons != 0) { + let + newX = e.pageX - dragOffset.x, + newY = e.pageY - dragOffset.y + + if (dragAction == "dragStart") { + if (distanceGreaterThan({x: newX, y: newY}, dragStartPos, DRAG_START_THRESHOLD)) { + // Recognise this as a drag rather than a clink + dragAction = "dragging"; + } + } + + if (dragAction == "dragging") { + that.setLocation(newX, newY); + } } } @@ -108,17 +131,33 @@ export default function CPPalette(cpController, className, title, resizeVert, re } else { headElement.setPointerCapture(e.pointerId); + dragStartPos = { + x: parseInt(containerElement.style.left, 10), + y: parseInt(containerElement.style.top, 10), + }; dragOffset = {x: e.pageX - $(containerElement).position().left, y: e.pageY - $(containerElement).position().top}; - dragAction = "move"; + + if (cpController.getSmallScreenMode()) { + // Wait for the cursor to move a certain amount before we classify this as a drag + dragAction = "dragStart"; + } else { + dragAction = "dragging"; + } } } } function paletteHeaderPointerUp(e) { - if (dragAction == "move") { + if (dragAction === "dragging" || dragAction === "dragStart") { headElement.releasePointerCapture(e.pointerId); + + if (dragAction === "dragStart") { + // We clicked the header. Cancel the drag and toggle the palette instead + that.setLocation(dragStartPos.x, dragStartPos.y); + that.toggleCollapse(); + } + dragAction = false; - that.toggleBodyElementVisibility(); } } @@ -197,7 +236,7 @@ export default function CPPalette(cpController, className, title, resizeVert, re titleElem.appendChild(document.createTextNode(_(this.title))); titleContainer.appendChild(titleElem); - if (!isInLowResolutionMode) titleContainer.appendChild(closeButton); + titleContainer.appendChild(closeButton); headElement.appendChild(titleContainer); diff --git a/js/gui/CPPaletteManager.js b/js/gui/CPPaletteManager.js index 4ba20c6..5384880 100644 --- a/js/gui/CPPaletteManager.js +++ b/js/gui/CPPaletteManager.js @@ -33,7 +33,7 @@ import CPTexturePalette from "./CPTexturePalette.js"; import CPSwatchesPalette from "./CPSwatchesPalette.js"; export default function CPPaletteManager(cpController) { - var + let palettes = { tool: new CPToolPalette(cpController), misc: new CPMiscPalette(cpController), @@ -55,7 +55,7 @@ export default function CPPaletteManager(cpController) { this.palettes = palettes; function showPalette(palette, show) { - var + let palElement = palette.getElement(); if (show) { @@ -70,7 +70,7 @@ export default function CPPaletteManager(cpController) { } this.showPaletteByName = function(paletteName, show) { - var + let palette = palettes[paletteName]; if (palette) { @@ -85,8 +85,8 @@ export default function CPPaletteManager(cpController) { hiddenFrames.push(this); }); } else { - for (var i = 0; i < hiddenFrames.length; i++) { - var + for (let i = 0; i < hiddenFrames.length; i++) { + let frame = hiddenFrames[i]; that.showPaletteByName(frame.getAttribute("data-paletteName"), true); @@ -99,12 +99,12 @@ export default function CPPaletteManager(cpController) { * Pop palettes that are currently outside the visible area back into view. */ this.constrainPalettes = function() { - var + let windowWidth = $(parentElem).parents(".chickenpaint-main-section").width(), windowHeight = $(parentElem).parents(".chickenpaint-main-section").height(); - for (var i in palettes) { - var palette = palettes[i]; + for (let i in palettes) { + let palette = palettes[i]; /* Move palettes that are more than half out of the frame back into it */ if (palette.getX() + palette.getWidth() / 2 > windowWidth) { @@ -120,7 +120,7 @@ export default function CPPaletteManager(cpController) { //palettes.swatches.moveToFront(); //Special handling for the swatches palette being under the brush palette: - var + let widthToSpare = windowWidth - palettes.tool.getWidth() - palettes.misc.getWidth() - palettes.stroke.getWidth() - palettes.color.getWidth() - palettes.brush.getWidth() - 15 > 0; if (palettes.swatches.getX() + palettes.swatches.getWidth() == palettes.brush.getX() + palettes.brush.getWidth() && @@ -138,7 +138,7 @@ export default function CPPaletteManager(cpController) { * Rearrange the palettes from scratch into a useful arrangement. */ this.arrangePalettes = function() { - var + let windowWidth = $(parentElem).parents(".chickenpaint-main-section").width(), windowHeight = $(parentElem).parents(".chickenpaint-main-section").height(), @@ -146,7 +146,7 @@ export default function CPPaletteManager(cpController) { palettes.brush.setLocation(windowWidth - palettes.brush.getWidth() - 15, 0); - var + let bottomOfBrush = palettes.brush.getY() + palettes.brush.getHeight(), layersY = windowHeight - bottomOfBrush > 300 ? bottomOfBrush + 2 : bottomOfBrush; @@ -169,13 +169,20 @@ export default function CPPaletteManager(cpController) { palettes.textures.setLocation(palettes.color.getX() + palettes.color.getWidth() + 4, windowHeight - palettes.textures.getHeight()); palettes.color.setLocation(0, Math.max(palettes.tool.getY() + palettes.tool.getHeight(), windowHeight - palettes.color.getHeight())); - - for (var i in palettes) { - var palette = palettes[i]; - palette.toggleBodyElementVisibility(); - } }; + cpController.on("smallScreen", function(smallScreenMode) { + for (let paletteName in palettes) { + let + palette = palettes[paletteName]; + + if (smallScreenMode) { + palette.show + } + palette.toggleCollapse(smallScreenMode); + } + }); + this.getElement = function() { return parentElem; }; diff --git a/js/gui/CPStrokePalette.js b/js/gui/CPStrokePalette.js index 93a1700..c023264 100644 --- a/js/gui/CPStrokePalette.js +++ b/js/gui/CPStrokePalette.js @@ -88,7 +88,7 @@ export default function CPStrokePalette(cpController) { $(this).addClass("selected"); cpController.actionPerformed({action: button.command}); - that.toggleBodyElementVisibility(); + that.toggleCollapse(); }); body.appendChild(listElem); diff --git a/js/gui/CPSwatchesPalette.js b/js/gui/CPSwatchesPalette.js index 3942748..26b0d9f 100644 --- a/js/gui/CPSwatchesPalette.js +++ b/js/gui/CPSwatchesPalette.js @@ -223,7 +223,7 @@ export default function CPSwatchesPalette(controller) { controller.setCurColor(new CPColor(parseInt(swatch.getAttribute("data-color"), 10))); e.stopPropagation(); e.preventDefault(); - that.toggleBodyElementVisibility(); + that.toggleCollapse(); } }); diff --git a/js/gui/CPToolPalette.js b/js/gui/CPToolPalette.js index ea5afe3..088cfed 100644 --- a/js/gui/CPToolPalette.js +++ b/js/gui/CPToolPalette.js @@ -167,7 +167,7 @@ export default function CPToolPalette(cpController) { button = buttons[parseInt(this.getAttribute("data-buttonIndex"), 10)]; cpController.actionPerformed({action: button.command}); - that.toggleBodyElementVisibility(); + that.toggleCollapse(); } } diff --git a/resources/css/chickenpaint.scss b/resources/css/chickenpaint.scss index 1c3aacf..f1a38a2 100644 --- a/resources/css/chickenpaint.scss +++ b/resources/css/chickenpaint.scss @@ -286,20 +286,6 @@ user-select: none; } -.chickenpaint.chickenpaint-full-screen { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - overflow: hidden; - background-color:white; - z-index:1000; -} -body.chickenpaint-full-screen { - overflow:hidden; -} - .chickenpaint-tools { width:80px; text-align:center; @@ -990,6 +976,27 @@ body.chickenpaint-full-screen { .chickenpaint .widget-nav .widget-toggler.selected { background-color: #ffffc4; } + +/* Full screen mode */ +.chickenpaint.chickenpaint-full-screen { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + background-color:white; + z-index:1000; +} +body.chickenpaint-full-screen { + overflow:hidden; +} + +/* Small screen mode (UI behaviour changes for mobiles) */ +.chickenpaint-small-screen .chickenpaint-palette-head .close { + display: none; +} + /* More compact styles for small screens */ @media (max-height: 768px), (max-width: 400px) { .chickenpaint .navbar { @@ -1021,6 +1028,7 @@ body.chickenpaint-full-screen { .chickenpaint-palette-head .close { font-size: 18px; + display: none; } .chickenpaint select.form-control {