From cfa9be93334b680e367b38a88bb5dab87f4a22af Mon Sep 17 00:00:00 2001 From: Nicholas Sherlock Date: Sat, 19 Jun 2021 21:32:24 +1200 Subject: [PATCH] Fullscreen/smallscreen fixes and improvements --- example/index-mobile.html | 20 ----- example/index-mobile.js | 14 ---- js/ChickenPaint.js | 5 +- js/gui/CPLayersPalette.js | 2 +- js/gui/CPMainGUI.js | 2 + js/gui/CPMainMenu.js | 32 ++++---- js/gui/CPPalette.js | 114 ++++++++++++++++++++++++---- js/gui/CPPaletteManager.js | 128 +++++++++++++++++++++----------- js/gui/CPStrokePalette.js | 2 +- js/gui/CPSwatchesPalette.js | 2 +- js/gui/CPToolPalette.js | 2 +- js/languages/en.json | 4 +- js/languages/ja.json | 1 + package.json | 2 +- resources/css/chickenpaint.scss | 33 +++++++- 15 files changed, 245 insertions(+), 118 deletions(-) delete mode 100644 example/index-mobile.html delete mode 100644 example/index-mobile.js diff --git a/example/index-mobile.html b/example/index-mobile.html deleted file mode 100644 index 8abfa5c..0000000 --- a/example/index-mobile.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - -ChickenPaint testbed - - - - - - - - -

ChickenPaint testbed

-
-

- - \ No newline at end of file diff --git a/example/index-mobile.js b/example/index-mobile.js deleted file mode 100644 index f2d80d8..0000000 --- a/example/index-mobile.js +++ /dev/null @@ -1,14 +0,0 @@ -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/js/ChickenPaint.js b/js/ChickenPaint.js index 51812ca..a5b75d2 100644 --- a/js/ChickenPaint.js +++ b/js/ChickenPaint.js @@ -373,7 +373,10 @@ export default function ChickenPaint(options) { that.setFullScreen(!isFullScreen); }, isSupported: function() { - return !(options.fullScreenMode === "disable" || options.fullScreenMode === "force"); + return !( + options.fullScreenMode === "disable" || options.fullScreenMode === "force" + || options.allowFullScreen === false /* For backwards compat */ + ); }, modifies: {gui: true} }, diff --git a/js/gui/CPLayersPalette.js b/js/gui/CPLayersPalette.js index 44c2cbe..f095ba4 100644 --- a/js/gui/CPLayersPalette.js +++ b/js/gui/CPLayersPalette.js @@ -100,7 +100,7 @@ function computeLayerPredicates(layer) { } export default function CPLayersPalette(controller) { - CPPalette.call(this, controller, "layers", "Layers", true, true); + CPPalette.call(this, controller, "layers", "Layers", {resizeHorz: true, resizeVert: true}); const NOTIFICATION_HIDE_DELAY_MS_PER_CHAR = 70, diff --git a/js/gui/CPMainGUI.js b/js/gui/CPMainGUI.js index 8548095..221dc2d 100644 --- a/js/gui/CPMainGUI.js +++ b/js/gui/CPMainGUI.js @@ -121,6 +121,8 @@ export default function CPMainGUI(controller, uiElem) { window.addEventListener("resize", this.resize.bind(this)); + controller.on("fullScreen", fullscreen => this.setFullScreenMode(fullscreen)); + setTimeout(this.resize.bind(this), 0); } diff --git a/js/gui/CPMainMenu.js b/js/gui/CPMainMenu.js index 7cb3b3d..9ff73f5 100644 --- a/js/gui/CPMainMenu.js +++ b/js/gui/CPMainMenu.js @@ -335,9 +335,9 @@ const name: "-" }, { - name: "Show tool options", - action: "CPPalBrush", - mnemonic: "B", + name: "Show tools", + action: "CPPalTool", + mnemonic: "T", checkbox: true, checked: true }, @@ -349,9 +349,9 @@ const checked: true }, { - name: "Show layers", - action: "CPPalLayers", - mnemonic: "Y", + name: "Show stroke", + action: "CPPalStroke", + mnemonic: "S", checkbox: true, checked: true }, @@ -362,13 +362,6 @@ const checkbox: true, checked: true }, - { - name: "Show stroke", - action: "CPPalStroke", - mnemonic: "S", - checkbox: true, - checked: true - }, { name: "Show swatches", action: "CPPalSwatches", @@ -384,9 +377,16 @@ const checked: true }, { - name: "Show tools", - action: "CPPalTool", - mnemonic: "T", + name: "Show tool options", + action: "CPPalBrush", + mnemonic: "B", + checkbox: true, + checked: true + }, + { + name: "Show layers", + action: "CPPalLayers", + mnemonic: "L", checkbox: true, checked: true } diff --git a/js/gui/CPPalette.js b/js/gui/CPPalette.js index 16baa95..a0182a3 100644 --- a/js/gui/CPPalette.js +++ b/js/gui/CPPalette.js @@ -34,15 +34,36 @@ function distanceGreaterThan(a, b, threshold) { return dist > threshold * threshold; } -export default function CPPalette(cpController, className, title, resizeVert, resizeHorz) { - this.title = _(title); +/** + * + * @param {ChickenPaint} cpController + * @param {String} className + * @param {String} title + * @param {Object} [options] + * @param {boolean} options.resizeVert + * @param {boolean} options.resizeHorz + * @param {boolean} options.collapseDownwards + * + * @constructor + */ +export default function CPPalette(cpController, className, title, options) { + // Use a shorter version of the title if needed and one is available + if (cpController.getSmallScreenMode() && _(title + " (shorter)") !== title + " (shorter)") { + this.title = _(title + " (shorter)"); + } else { + this.title = _(title); + } + + options = options || {}; + this.name = className; - this.resizeVert = resizeVert || false; - this.resizeHorz = resizeHorz || false; + this.resizeVert = options.resizeVert || false; + this.resizeHorz = options.resizeHorz || false; let containerElement = document.createElement("div"), headElement = document.createElement("div"), + collapseIcon = document.createElement("i"), closeButton = document.createElement("button"), bodyElement = document.createElement("div"), @@ -96,12 +117,61 @@ export default function CPPalette(cpController, className, title, resizeVert, re this.setWidth(width); this.setHeight(height); }; + + this.setCollapseDownwards = function(collapseDownwards) { + options.collapseDownwards = collapseDownwards; + }; /** - * @param {boolean} [collapse] + * @param {boolean} [collapse] True to collapse, false to uncollapse, omit to toggle state */ this.toggleCollapse = function(collapse) { - $(containerElement).toggleClass("collapsed", collapse); + let + $containerElement = $(containerElement); + + if (collapse === undefined) { + collapse = !$containerElement.hasClass("collapsed"); + } else { + if ($containerElement.hasClass("collapsed") == collapse) { + return; + } + } + + let + windowHeight = $containerElement.parents(".chickenpaint").find(".chickenpaint-canvas").height(), + oldHeight = this.getHeight(), + oldBottom = this.getY() + oldHeight; + + $containerElement.toggleClass("collapsed", collapse); + + $(collapseIcon) + .toggleClass("fa-angle-down", !collapse) + .toggleClass("fa-angle-up", collapse); + + if (collapse) { + // Move the header down to the old base position + if (options.collapseDownwards) { + this.setLocation(this.getX(), Math.min(oldBottom, windowHeight) - this.getHeight()); + } + } else { + let + thisHeight = this.getHeight(); + + if (options.collapseDownwards) { + this.setLocation(this.getX(), Math.max(oldBottom - thisHeight, 0)); + } else { + // Keep palettes inside the window when uncollapsing + if (this.getY() + thisHeight > windowHeight) { + this.setLocation(this.getX(), Math.max(windowHeight - thisHeight, 0)); + } + } + } + }; + + this.userIsDoneWithUs = function() { + if (cpController.getSmallScreenMode()) { + this.toggleCollapse(true); + } }; function paletteHeaderPointerMove(e) { @@ -125,6 +195,9 @@ export default function CPPalette(cpController, className, title, resizeVert, re function paletteHeaderPointerDown(e) { if (e.button == 0) {/* Left */ + e.stopPropagation(); + e.preventDefault(); // Avoid generating further legacy mouse events + if (e.target.nodeName == "BUTTON") { // Close button was clicked that.emitEvent("paletteVisChange", [that, false]); @@ -132,8 +205,8 @@ export default function CPPalette(cpController, className, title, resizeVert, re headElement.setPointerCapture(e.pointerId); dragStartPos = { - x: parseInt(containerElement.style.left, 10), - y: parseInt(containerElement.style.top, 10), + x: parseInt(containerElement.style.left, 10) || 0, + y: parseInt(containerElement.style.top, 10) || 0, }; dragOffset = {x: e.pageX - $(containerElement).position().left, y: e.pageY - $(containerElement).position().top}; @@ -149,15 +222,25 @@ export default function CPPalette(cpController, className, title, resizeVert, re function paletteHeaderPointerUp(e) { 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(); + e.stopPropagation(); + e.preventDefault(); + + /* Don't move the dialog immediately, because otherwise a click event will be + * dispatched on the element which ends up under the cursor afterwards. + */ + setTimeout(() => { + that.setLocation(dragStartPos.x, dragStartPos.y); + that.toggleCollapse(); + }, 100); } - + dragAction = false; + + try { + headElement.releasePointerCapture(e.pointerId); + } catch (e) {} } } @@ -216,6 +299,8 @@ export default function CPPalette(cpController, className, title, resizeVert, re containerElement.appendChild(horzHandle); } + + collapseIcon.className = "collapse-icon fas fa-angle-down"; closeButton.type = "button"; closeButton.className = "close"; @@ -233,7 +318,8 @@ export default function CPPalette(cpController, className, title, resizeVert, re titleContainer.className = 'modal-header'; titleElem.className = 'modal-title'; - titleElem.appendChild(document.createTextNode(_(this.title))); + titleElem.appendChild(document.createTextNode(this.title)); + titleElem.appendChild(collapseIcon); titleContainer.appendChild(titleElem); titleContainer.appendChild(closeButton); diff --git a/js/gui/CPPaletteManager.js b/js/gui/CPPaletteManager.js index 5384880..19ce26e 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) { - let + const palettes = { tool: new CPToolPalette(cpController), misc: new CPMiscPalette(cpController), @@ -45,15 +45,36 @@ export default function CPPaletteManager(cpController) { swatches: new CPSwatchesPalette(cpController) }, - paletteFrames = [], - hiddenFrames = [], + defaultCollapse = { + tool: false, + color: false, + misc: false + }, + collapseDownwards = { + color: true, + textures: true, + layers: true + }, + parentElem = document.createElement("div"), - + that = this; + let + paletteFrames = [], + hiddenFrames = []; + this.palettes = palettes; + function getPaletteDisplayArea() { + // Use the canvas as a positioning guide to avoid overlapping scrollbars + let + canvas = $(parentElem).parents(".chickenpaint").find(".chickenpaint-canvas"); + + return {width: canvas.width(), height: canvas.height()}; + } + function showPalette(palette, show) { let palElement = palette.getElement(); @@ -100,19 +121,18 @@ export default function CPPaletteManager(cpController) { */ this.constrainPalettes = function() { let - windowWidth = $(parentElem).parents(".chickenpaint-main-section").width(), - windowHeight = $(parentElem).parents(".chickenpaint-main-section").height(); + windowDim = getPaletteDisplayArea(); 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) { - palette.setLocation(windowWidth - palette.getWidth(), palette.getY()); + if (palette.getX() + palette.getWidth() / 2 > windowDim.width) { + palette.setLocation(windowDim.width - palette.getWidth(), palette.getY()); } - if (palette.getY() + palette.getHeight() / 2 > windowHeight) { - palette.setLocation(palette.getX(), windowHeight - palette.getHeight()); + if (palette.getY() + palette.getHeight() / 2 > windowDim.height) { + palette.setLocation(palette.getX(), windowDim.height - palette.getHeight()); } } @@ -121,7 +141,7 @@ export default function CPPaletteManager(cpController) { //Special handling for the swatches palette being under the brush palette: let - widthToSpare = windowWidth - palettes.tool.getWidth() - palettes.misc.getWidth() - palettes.stroke.getWidth() - palettes.color.getWidth() - palettes.brush.getWidth() - 15 > 0; + widthToSpare = windowDim.width - 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() && Math.abs(palettes.swatches.getY() - palettes.brush.getY()) < 20) { @@ -129,8 +149,8 @@ export default function CPPaletteManager(cpController) { } //Special handling for layers palette being too damn tall: - if (palettes.layers.getY() + palettes.layers.getHeight() > windowHeight) { - palettes.layers.setHeight(Math.max(windowHeight - palettes.layers.getY(), 200)); + if (palettes.layers.getY() + palettes.layers.getHeight() > windowDim.height) { + palettes.layers.setHeight(Math.max(windowDim.height - palettes.layers.getY(), 200)); } }; @@ -139,47 +159,65 @@ export default function CPPaletteManager(cpController) { */ this.arrangePalettes = function() { let - windowWidth = $(parentElem).parents(".chickenpaint-main-section").width(), - windowHeight = $(parentElem).parents(".chickenpaint-main-section").height(), - - haveWidthToSpare = windowWidth - palettes.tool.getWidth() - palettes.misc.getWidth() - palettes.stroke.getWidth() - palettes.color.getWidth() - palettes.brush.getWidth() - 15 > 0; + windowDim = getPaletteDisplayArea(), - palettes.brush.setLocation(windowWidth - palettes.brush.getWidth() - 15, 0); + haveWidthToSpare; - let - bottomOfBrush = palettes.brush.getY() + palettes.brush.getHeight(), - layersY = windowHeight - bottomOfBrush > 300 ? bottomOfBrush + 2 : bottomOfBrush; + if (cpController.getSmallScreenMode()) { + palettes.tool.setLocation(0, 0); + palettes.misc.setLocation(palettes.tool.getX() + palettes.tool.getWidth() + 1, 0); + palettes.brush.setLocation(windowDim.width - palettes.brush.getWidth() - 15, palettes.misc.getY() + palettes.misc.getHeight() + 1); - palettes.layers.setSize(palettes.brush.getWidth() + (haveWidthToSpare ? 30 : 0), windowHeight - layersY); - palettes.layers.setLocation(palettes.brush.getX() + palettes.brush.getWidth() - palettes.layers.getWidth(), layersY); + let + layersY = 330; + + palettes.textures.setWidth(windowDim.width - palettes.textures.getX()); + + palettes.layers.setLocation(palettes.brush.getX() + palettes.brush.getWidth() - palettes.layers.getWidth(), palettes.textures.getY() - palettes.layers.getHeight()); + palettes.layers.setHeight(palettes.textures.getY() - layersY - 1); - palettes.tool.setLocation(0, 0); - - palettes.misc.setLocation(palettes.tool.getX() + palettes.tool.getWidth() + (haveWidthToSpare ? 5 : 1), 0); - - if (haveWidthToSpare) { - palettes.stroke.setLocation(palettes.misc.getX() + palettes.misc.getWidth() + (haveWidthToSpare ? 5 : 1), 0); - } else { palettes.stroke.setLocation(palettes.misc.getX(), palettes.misc.getY() + palettes.misc.getHeight() + 1); + palettes.swatches.setLocation(palettes.stroke.getX(), palettes.stroke.getY() + palettes.stroke.getHeight() + 1); + } else { + haveWidthToSpare = windowDim.width - palettes.tool.getWidth() - palettes.misc.getWidth() - palettes.stroke.getWidth() - palettes.color.getWidth() - palettes.brush.getWidth() - 15 > 0; + + palettes.brush.setLocation(windowDim.width - palettes.brush.getWidth() - 15, 0); + + let + bottomOfBrush = palettes.brush.getY() + palettes.brush.getHeight(), + layersY = windowDim.height - bottomOfBrush > 300 ? bottomOfBrush + 2 : bottomOfBrush; + + palettes.layers.setSize(palettes.brush.getWidth() + (haveWidthToSpare ? 30 : 0), windowDim.height - layersY); + palettes.layers.setLocation(palettes.brush.getX() + palettes.brush.getWidth() - palettes.layers.getWidth(), layersY); + + palettes.tool.setLocation(0, 0); + + palettes.misc.setLocation(palettes.tool.getX() + palettes.tool.getWidth() + (haveWidthToSpare ? 5 : 1), 0); + + if (haveWidthToSpare) { + palettes.stroke.setLocation(palettes.misc.getX() + palettes.misc.getWidth() + (haveWidthToSpare ? 5 : 1), 0); + } else { + palettes.stroke.setLocation(palettes.misc.getX(), palettes.misc.getY() + palettes.misc.getHeight() + 1); + } + + palettes.swatches.setLocation(Math.max(palettes.brush.getX() - palettes.swatches.getWidth() - (haveWidthToSpare ? 5 : 1), palettes.tool.getX() + palettes.tool.getWidth()), 0); + + palettes.textures.setWidth(Math.min(palettes.layers.getX() - palettes.textures.getX(), 490)); } - - palettes.swatches.setLocation(palettes.brush.getX() - palettes.swatches.getWidth() - (haveWidthToSpare ? 5 : 1), 0); - palettes.textures.setWidth(Math.min(palettes.layers.getX() - palettes.textures.getX(), 490)); - palettes.textures.setLocation(palettes.color.getX() + palettes.color.getWidth() + 4, windowHeight - palettes.textures.getHeight()); + palettes.textures.setLocation(palettes.color.getX() + palettes.color.getWidth() + 4, windowDim.height - palettes.textures.getHeight()); - palettes.color.setLocation(0, Math.max(palettes.tool.getY() + palettes.tool.getHeight(), windowHeight - palettes.color.getHeight())); + palettes.color.setLocation(0, Math.max(palettes.tool.getY() + palettes.tool.getHeight(), windowDim.height - palettes.color.getHeight())); }; cpController.on("smallScreen", function(smallScreenMode) { - for (let paletteName in palettes) { - let - palette = palettes[paletteName]; - - if (smallScreenMode) { - palette.show + if (smallScreenMode) { + for (let paletteName in palettes) { + let + palette = palettes[paletteName]; + + palette.toggleCollapse(smallScreenMode && (!(paletteName in defaultCollapse) || defaultCollapse[paletteName])); } - palette.toggleCollapse(smallScreenMode); } }); @@ -198,6 +236,10 @@ export default function CPPaletteManager(cpController) { showPalette(this, false); }); + if (paletteName in collapseDownwards) { + palette.setCollapseDownwards(true); + } + palElement.setAttribute("data-paletteName", paletteName); paletteFrames.push(palElement); } diff --git a/js/gui/CPStrokePalette.js b/js/gui/CPStrokePalette.js index c023264..38e7d93 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.toggleCollapse(); + that.userIsDoneWithUs(); }); body.appendChild(listElem); diff --git a/js/gui/CPSwatchesPalette.js b/js/gui/CPSwatchesPalette.js index 26b0d9f..5fc92cd 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.toggleCollapse(); + that.userIsDoneWithUs(); } }); diff --git a/js/gui/CPToolPalette.js b/js/gui/CPToolPalette.js index 743fdf1..2eec99b 100644 --- a/js/gui/CPToolPalette.js +++ b/js/gui/CPToolPalette.js @@ -173,7 +173,7 @@ export default function CPToolPalette(cpController) { button = buttons[parseInt(this.getAttribute("data-buttonIndex"), 10)]; cpController.actionPerformed({action: button.command}); - that.toggleCollapse(); + that.userIsDoneWithUs(); } } diff --git a/js/languages/en.json b/js/languages/en.json index 56f3486..be4c166 100644 --- a/js/languages/en.json +++ b/js/languages/en.json @@ -1,5 +1,7 @@ { "Dodge (tool)": "Dodge", "Burn (tool)": "Burn", - "Color (picker)": "Color" + "Color (picker)": "Color", + + "Color swatches (shorter)": "Swatches" } \ No newline at end of file diff --git a/js/languages/ja.json b/js/languages/ja.json index 3a9d8d9..898eaed 100644 --- a/js/languages/ja.json +++ b/js/languages/ja.json @@ -89,6 +89,7 @@ "Smoothing": "手ぶれ補正", "Color swatches": "カラーセット", + "Color swatches (shorter)": "カラーセット", "Remove": "削除", "Replace with current color": "選択中の色に置き換え", "Save swatches to your computer...": "カラーセットをPCに保存", diff --git a/package.json b/package.json index 3ef7699..70948f5 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "type": "git", "url": "https://github.com/thenickdude/chickenpaint.git" }, - "version": "0.2.2", + "version": "0.3.0", "devDependencies": { "@babel/cli": "^7.13.16", "@babel/core": "^7.14.0", diff --git a/resources/css/chickenpaint.scss b/resources/css/chickenpaint.scss index 0f7017b..c98d502 100644 --- a/resources/css/chickenpaint.scss +++ b/resources/css/chickenpaint.scss @@ -226,6 +226,7 @@ .chickenpaint-palette.collapsed { min-height: 27px; max-height: 27px; + } .chickenpaint-palette.collapsed > .chickenpaint-palette-body { display: none; @@ -254,15 +255,18 @@ .chickenpaint .modal-header .close { padding: 0.25rem; margin: -0.25rem -0.25rem -0.25rem auto; + font-size: 150%; } .chickenpaint-palette-head { font-size: 14px; } -.chickenpaint-palette-head .close { - font-size: 150%; +.chickenpaint-palette-head .collapse-icon { + margin-left:0.5em; + display: none; } .chickenpaint-palette-head h5 { font-size: 100%; + white-space: nowrap; } .chickenpaint-resize-handle-vert { position: absolute; @@ -740,6 +744,9 @@ } } +.chickenpaint-palette-textures { + min-width: 190px; +} .chickenpaint-palette-textures .chickenpaint-palette-body { display:flex; flex-direction:row; @@ -996,8 +1003,14 @@ body.chickenpaint-full-screen { } /* Small screen mode (UI behaviour changes for mobiles) */ -.chickenpaint-small-screen .chickenpaint-palette-head .close { - display: none; +.chickenpaint-small-screen { + .chickenpaint-palette-head .close { + display: none; + } + + .chickenpaint-palette-head .collapse-icon { + display: inline-block; + } } /* More compact styles for small screens */ @@ -1053,6 +1066,18 @@ body.chickenpaint-full-screen { .chickenpaint .widget-nav { display: block; } + + .chickenpaint .chickenpaint-layer-buttons .chickenpaint-small-toolbar-button { + margin-right:4px; + } + + .chickenpaint-palette-brush { + min-width:160px; + } + + .chickenpaint-texture-swatches { + max-height:200px; + } } /* Language overrides */