diff --git a/.gitignore b/.gitignore
index c8a06d5..86c4cc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
.vs/*
node_modules/*
*.stg
+package-lock.json
+package.json
diff --git a/README.md b/README.md
index a3c38dd..b8d91d7 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Pseudo-vector image creator.
## [Launch Application](https://malulleybovo.github.io/SymbolArtEditorOnline/)
- #### Version 2.0.1
+ #### Version 2.1.0
### Quickly get down to business and make art with fluidity and agility.
Symbol Art Editor was designed based on three pillars: simplicity, ease of use, and agility.
diff --git a/index.html b/index.html
index f780293..bb0c7fb 100644
--- a/index.html
+++ b/index.html
@@ -103,8 +103,8 @@
-
-
+
+
diff --git a/res/templates/container.html b/res/templates/container.html
deleted file mode 100644
index 41d60fb..0000000
--- a/res/templates/container.html
+++ /dev/null
@@ -1 +0,0 @@
-
-
v2.0.1
© 2021 Copyright malulleybovo
+
v2.1.0
© 2021 Copyright malulleybovo
diff --git a/src/UIApplication.js b/src/UIApplication.js
index 53779b0..d24d703 100644
--- a/src/UIApplication.js
+++ b/src/UIApplication.js
@@ -67,6 +67,9 @@ class UIApplication {
} else if (originalColorInContainer) {
this._renderer.presenter.setColorForSelectedContainer({ originalColorInContainer: originalColorInContainer, hexValue: hexValue, opacity: opacity, updatesHistory: lastInteraction });
}
+ if (lastInteraction) {
+ this._layerPicker.updateWith({ container: this._symbolArt.root });
+ }
},
requestedEyeDropperColors: _ => {
this._symbolArt.helperImage.imageColors().then(colors => {
@@ -88,15 +91,55 @@ class UIApplication {
},
onAssetChange: (selectedAsset) => {
this._renderer.presenter.setAssetForSelectedSymbol({ asset: selectedAsset });
+ this._layerPicker.updateWith({ container: this._symbolArt.root });
}
});
- _containerPicker = new UIContainerPicker({
- onContainerSelected: (containerUuid) => {
- let container3D = Layer3D.layersInUse[containerUuid];
- if (container3D instanceof Container3D) {
- this._renderer.setSelection({ layer3D: container3D });
+ _layerPicker = new UILayerPicker({
+ onLayerSelected: (layerUuid) => {
+ let layer3D = Layer3D.layersInUse[layerUuid];
+ if (layer3D instanceof Layer3D) {
+ this._renderer.setSelection({ layer3D: layer3D });
this._renderer.panToSelection();
+ SymbolControls3D.shared.attach({ toSymbol3D: this._renderer.selectionGroup });
+ ContainerControls3D.shared.attach({ toContainer3D: this._renderer.selectionGroup });
+ HelperImageControls3D.shared.attach({ toHelperImage: null });
}
+ },
+ onRenameTapped: layerUuid => {
+ let layerToRename = null;
+ this._symbolArt.root.depthFirstIterator(layer => {
+ if (layer.uuid === layerUuid) {
+ layerToRename = layer;
+ return true;
+ }
+ });
+ if (!(layerToRename instanceof Layer)) return;
+ new UIModalTextField({
+ title: 'Rename layer:', initialText: layerToRename.name,
+ onInput: text => { },
+ onResult: text => {
+ if (layerToRename.name == text) return;
+ layerToRename.name = text;
+ this._renderer.updateWith({ symbolArt: this._symbolArt });
+ HistoryState.shared.pushHistory({ data: this._symbolArt.clone() });
+ }
+ });
+ },
+ onDeleteTapped: layerUuid => {
+ let layerToDelete = null;
+ this._symbolArt.root.depthFirstIterator(layer => {
+ if (layer.uuid === layerUuid) {
+ layerToDelete = layer;
+ return true;
+ }
+ });
+ if (!(layerToDelete instanceof Layer) && layerToDelete.parent) return;
+ layerToDelete.parent.remove({ sublayer: layerToDelete });
+ this._renderer.updateWith({ symbolArt: this._symbolArt });
+ HistoryState.shared.pushHistory({ data: this._symbolArt.clone() });
+ SymbolControls3D.shared.attach({ toSymbol3D: this._renderer.selectionGroup });
+ ContainerControls3D.shared.attach({ toContainer3D: this._renderer.selectionGroup });
+ HelperImageControls3D.shared.attach({ toHelperImage: null });
}
});
_helperImageSettings = new UIHelperImageSettings({
@@ -139,7 +182,10 @@ class UIApplication {
_renderer = (() => {
let renderer = new Renderer();
renderer.onSymbolArtChanged = () => {
- this._containerPicker.updateWith({ containers: this._symbolArt.root.containers });
+ this._layerPicker.updateWith({ container: this._symbolArt.root });
+ setTimeout(_ => {
+ this._layerPicker.select({ layerWithUuid: this._renderer.selectionUuid });
+ }, 10);
};
renderer.onSelectionChanged = (selectionUuid) => {
let selectionLayer = this._symbolArt.findLayer({ withUuidString: selectionUuid });
@@ -149,6 +195,7 @@ class UIApplication {
this._actionBar.setNorthEastButton({ enabled: selectedLayer, forViewMode: ViewMode.symbolEditorMode });
this._actionBar.setEastButton({ enabled: selectedLayer, forViewMode: ViewMode.symbolEditorMode });
this._assetPicker.assetSelectionEnabled = selectedSymbol;
+ this._layerPicker.select({ layerWithUuid: selectionUuid });
if (!selectedLayer) return;
if (selectedSymbol) {
this._assetPicker.update({
@@ -246,7 +293,7 @@ class UIApplication {
if (!this.loaded) return;
let $body = $('body');
this._copyrightView.append({ to: $body });
- this._containerPicker.append({ to: $body });
+ this._layerPicker.append({ to: $body });
this._menu.append({ to: $body });
this._actionBar.append({ to: $body });
this._colorPicker.append({ to: $body });
diff --git a/src/components/InputDevice.js b/src/components/InputDevice.js
index f9a53ec..eadd6f9 100644
--- a/src/components/InputDevice.js
+++ b/src/components/InputDevice.js
@@ -39,12 +39,22 @@ class InputDevice {
_tapDuration = 500;
_tapTimeout = null;
- _longTapDuration = 750;
+ _longTapDuration = 600;
_longTapTimeout = null;
_longStart = false;
get longStart() { return this._longStart }
+ _longTouchesWithoutDelay = false;
+ get longTouchesWithoutDelay() {
+ return this._longTouchesWithoutDelay;
+ }
+ set longTouchesWithoutDelay(value) {
+ if (typeof value === 'boolean') {
+ this._longTouchesWithoutDelay = value;
+ }
+ }
+
_previousPinchLength = -1;
_minimumNumberOfPointsToTriggerMotion = 10;
@@ -89,7 +99,7 @@ class InputDevice {
this._longTapTimeout = null;
this._longStart = true;
this.longTouchBegan(longEvent);
- }, this._longTapDuration);
+ }, this._longTouchesWithoutDelay ? 25 : this._longTapDuration);
}
this.touchBegan(event, this._activeTouchEvents.length);
}
@@ -147,6 +157,19 @@ class InputDevice {
_interactionEnded(event) {
if (!UIApplication.shared.loaded) return;
+ let longStart = this._longStart;
+ if (this._longTapTimeout !== null) {
+ if (this._longTouchesWithoutDelay) {
+ setTimeout(_ => {
+ this._interactionEnded(event);
+ });
+ return;
+ } else {
+ clearTimeout(this._longTapTimeout);
+ this._longTapTimeout = null;
+ this._longStart = false;
+ }
+ }
if (event && event.type === 'blur') {
this._activeTouchEvents = [];
}
@@ -165,16 +188,11 @@ class InputDevice {
}
this._activeEventState = 0;
this._originalTouchEvent = null;
- if (this._longStart) {
+ if (longStart) {
this.longTouchEnded(event);
} else {
this.touchEnded(event);
}
- if (this._longTapTimeout !== null) {
- clearTimeout(this._longTapTimeout);
- this._longTapTimeout = null;
- this._longStart = false;
- }
}
_scrolled(event) {
diff --git a/src/components/ui/UIAssetPicker.js b/src/components/ui/UIAssetPicker.js
index 88ce554..71cbcae 100644
--- a/src/components/ui/UIAssetPicker.js
+++ b/src/components/ui/UIAssetPicker.js
@@ -206,7 +206,6 @@ class UIAssetPicker extends UIView {
this._assetPreview.attr('src', '');
return;
}
- this._assetSelected({ uiAsset: this._catalog[asset.filePath].uiAsset });
}
updateState() {
diff --git a/src/components/ui/UIColorPicker.js b/src/components/ui/UIColorPicker.js
index 4db47eb..b4531c0 100644
--- a/src/components/ui/UIColorPicker.js
+++ b/src/components/ui/UIColorPicker.js
@@ -407,6 +407,7 @@ class UIColorPicker extends UIView {
}
});
this._colorPaletteList.empty();
+ this._colorPalette.forEach(a => a.remove());
this._colorPalette = [];
Object.values(occurrences).forEach(a => {
let subview = new UIColorPreview({
diff --git a/src/components/ui/UIContainer.js b/src/components/ui/UIContainer.js
deleted file mode 100644
index 83d8d3f..0000000
--- a/src/components/ui/UIContainer.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * Symbol Art Editor
- *
- * @author malulleybovo (since 2021)
- * @license GNU General Public License v3.0
- *
- * @licstart The following is the entire license notice for the
- * JavaScript code in this page.
- *
- * Copyright (C) 2021 Arthur Malulley B. de O.
- *
- *
- * The JavaScript code in this page is free software: you can
- * redistribute it and/or modify it under the terms of the GNU
- * General Public License (GNU GPL) as published by the Free Software
- * Foundation, either version 3 of the License, or (at your option)
- * any later version. The code is distributed WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
- *
- * As additional permission under GNU GPL version 3 section 7, you
- * may distribute non-source (e.g., minimized or compacted) forms of
- * that code without the copy of the GNU GPL normally required by
- * section 4, provided you include this license notice and a URL
- * through which recipients can access the Corresponding Source.
- *
- * @licend The above is the entire license notice
- * for the JavaScript code in this page.
- *
- */
-
-class UIContainer extends UIView {
-
- get viewPath() { return 'res/templates/container.html' }
-
- _onTap = null;
- get onTap() { return this._onTap }
- set onTap(value) {
- if (typeof value !== 'function' && value !== null) return;
- this._onTap = value;
- }
-
- get name() {
- if (!this.loaded) return '';
- return this.view.text();
- }
- set name(value) {
- if (typeof value === 'string') {
- this.didLoad(_ => {
- this.view.text(value);
- });
- }
- }
-
- _identifier = null;
- get identifier() {
- return this._identifier;
- }
- set identifier(value) {
- if (typeof value === 'string') {
- this._identifier = value;
- }
- }
-
- get loaded() {
- return this.view instanceof jQuery
- && this.view[0] instanceof HTMLElement;
- }
-
- constructor({ filePath = null } = {}) {
- super();
- let path = filePath;
- this.didLoad(_ => {
- if (mobileClient) this.view.css('margin-right', '8px');
- this.view.attr('src', path);
- this.gestureRecognizer = new UITapGestureRecognizer({
- targetHtmlElement: this.view[0], onTap: () => {
- if (this._onTap) {
- this._onTap(this);
- }
- }
- });
- });
- }
-
-}
diff --git a/src/components/ui/UIContainerPicker.js b/src/components/ui/UIContainerPicker.js
deleted file mode 100644
index 8bf7dc6..0000000
--- a/src/components/ui/UIContainerPicker.js
+++ /dev/null
@@ -1,203 +0,0 @@
-/**
- * Symbol Art Editor
- *
- * @author malulleybovo (since 2021)
- * @license GNU General Public License v3.0
- *
- * @licstart The following is the entire license notice for the
- * JavaScript code in this page.
- *
- * Copyright (C) 2021 Arthur Malulley B. de O.
- *
- *
- * The JavaScript code in this page is free software: you can
- * redistribute it and/or modify it under the terms of the GNU
- * General Public License (GNU GPL) as published by the Free Software
- * Foundation, either version 3 of the License, or (at your option)
- * any later version. The code is distributed WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
- *
- * As additional permission under GNU GPL version 3 section 7, you
- * may distribute non-source (e.g., minimized or compacted) forms of
- * that code without the copy of the GNU GPL normally required by
- * section 4, provided you include this license notice and a URL
- * through which recipients can access the Corresponding Source.
- *
- * @licend The above is the entire license notice
- * for the JavaScript code in this page.
- *
- */
-
-class UIContainerPicker extends UIView {
-
- get viewPath() { return 'res/templates/containerpicker.html' }
-
- _listView = (() => {
- this.didLoad(_ => {
- this._listView = this.view.find('#listview');
- this.updateState();
- });
- })();
-
- _symbolCount = (() => {
- this.didLoad(_ => {
- this._symbolCount = this.view.find('#symbolcount');
- this.updateState();
- });
- })();
-
- _symbolTotal = (() => {
- this.didLoad(_ => {
- this._symbolTotal = this.view.find('#symboltotal');
- this.updateState();
- });
- })();
-
- _containerCount = (() => {
- this.didLoad(_ => {
- this._containerCount = this.view.find('#containercount');
- this.updateState();
- });
- })();
-
- _containerTotal = (() => {
- this.didLoad(_ => {
- this._containerTotal = this.view.find('#containertotal');
- this.updateState();
- });
- })();
-
- _onContainerSelected = null;
- set onContainerSelected(value) {
- if (typeof value !== 'function' && value !== null) return;
- this._onContainerSelected = value;
- }
-
- get loaded() {
- return this.view instanceof jQuery
- && this.view[0] instanceof HTMLElement
- && this._listView instanceof jQuery
- && this._symbolCount instanceof jQuery
- && this._symbolTotal instanceof jQuery
- && this._containerCount instanceof jQuery
- && this._containerTotal instanceof jQuery;
- }
-
- set isHidden(value) {
- if (typeof value !== 'boolean') return;
- this.didLoad(_ => {
- this.view.css('right', value ? '-200px' : '0px');
- });
- }
-
- constructor({ onContainerSelected = null } = { }) {
- super();
- this.onContainerSelected = onContainerSelected;
- this.didLoad(_ => {
- this.view.gestureRecognizer = new UIGestureRecognizer({
- targetHtmlElement: this.view[0],
- preventsDefault: false,
- onPointerDown: (event) => {
- // to prevent propagation
- }, onPointerMove: (event) => {
- // to prevent propagation
- }, onPointerUp: (event) => {
- // to prevent propagation
- }, onScroll: (event) => {
- // to prevent propagation
- }, onKeyPress: (event) => {
- return false;
- }
- });
- });
- ApplicationState.shared.add({
- onChangeViewModeListener: () => {
- this.updateState();
- }
- });
- this.updateState();
- }
-
- _uiContainers = [];
-
- updateWith({ containers }) {
- if (!(Array.isArray(containers))) return;
- if (!this.loaded) {
- this.didLoad(_ => {
- this.updateWith({ containers: containers });
- });
- return;
- }
- let uiContainers = [];
- for (var index in containers) {
- let container = containers[index];
- if (!(container instanceof Container)) continue;
- let matches = this._uiContainers.filter(a => a.identifier === container.uuid);
- let uiContainer = null;
- if (matches[0]) {
- uiContainer = matches[0];
- } else {
- uiContainer = new UIContainer();
- }
- uiContainer.name = container.name.length === 0 ? 'container' : container.name;
- uiContainer.identifier = container.uuid;
- uiContainer.onTap = (uiContainer) => {
- if (this._onContainerSelected) {
- this._onContainerSelected(uiContainer.identifier);
- }
- };
- uiContainers.push(uiContainer);
- }
- let requiresUpdate = false;
- for (var index in uiContainers) {
- if (this._uiContainers[index] !== uiContainers[index]) {
- requiresUpdate = true;
- break;
- }
- }
- if (!requiresUpdate) {
- for (var index in this._uiContainers) {
- if (this._uiContainers[index] !== uiContainers[index]) {
- requiresUpdate = true;
- break;
- }
- }
- }
- if (requiresUpdate) {
- this._listView.empty();
- this._uiContainers = uiContainers;
- for (var index in this._uiContainers) {
- this._uiContainers[index].append({ to: this._listView });
- }
- }
- this.updateLimits();
- }
-
- updateLimits() {
- if (!this.loaded) {
- this.didLoad(_ => {
- this.updateLimits();
- });
- return;
- }
- this._symbolCount.text(`${UIApplication.shared.symbolArt.root.numberOfSymbols}`);
- this._symbolTotal.text(`${SymbolArt.maximumNumberOfSymbols}`);
- this._containerCount.text(`${UIApplication.shared.symbolArt.root.containers.length}`);
- this._containerTotal.text(`${SymbolArt.maximumNumberOfContainers}`);
- }
-
- updateState() {
- switch (ApplicationState.shared.viewMode) {
- case ViewMode.layerEditorMode:
- this.isHidden = false;
- break;
- case ViewMode.symbolEditorMode:
- case ViewMode.helperImageMode:
- default:
- this.isHidden = true;
- return;
- }
- }
-
-}
diff --git a/src/components/ui/UILayer.js b/src/components/ui/UILayer.js
new file mode 100644
index 0000000..f7a7926
--- /dev/null
+++ b/src/components/ui/UILayer.js
@@ -0,0 +1,245 @@
+/**
+ * Symbol Art Editor
+ *
+ * @author malulleybovo (since 2021)
+ * @license GNU General Public License v3.0
+ *
+ * @licstart The following is the entire license notice for the
+ * JavaScript code in this page.
+ *
+ * Copyright (C) 2021 Arthur Malulley B. de O.
+ *
+ *
+ * The JavaScript code in this page is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GNU GPL) as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option)
+ * any later version. The code is distributed WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
+ *
+ * As additional permission under GNU GPL version 3 section 7, you
+ * may distribute non-source (e.g., minimized or compacted) forms of
+ * that code without the copy of the GNU GPL normally required by
+ * section 4, provided you include this license notice and a URL
+ * through which recipients can access the Corresponding Source.
+ *
+ * @licend The above is the entire license notice
+ * for the JavaScript code in this page.
+ *
+ */
+
+class UILayer extends UIView {
+
+ get viewPath() { return 'res/templates/layer.html' }
+
+ _onTap = null;
+ get onTap() { return this._onTap }
+ set onTap(value) {
+ if (typeof value !== 'function' && value !== null) return;
+ this._onTap = value;
+ }
+
+ get name() {
+ if (!this.loaded) return '';
+ return this._textView.text();
+ }
+ set name(value) {
+ if (typeof value === 'string') {
+ this.didLoad(_ => {
+ this._textView.text(value);
+ });
+ }
+ }
+
+ _identifier = null;
+ get identifier() {
+ return this._identifier;
+ }
+ set identifier(value) {
+ if (typeof value === 'string') {
+ this._identifier = value;
+ }
+ }
+
+ set previewAsset(value) {
+ if (value instanceof Asset) {
+ this.didLoad(_ => {
+ this._symbolPreview.find('image').attr('href', value.filePath);
+ });
+ }
+ }
+
+ set previewAssetColor(value) {
+ if (value instanceof Color) {
+ this.didLoad(_ => {
+ let hsv = value.hsv;
+ this._symbolPreview.find('feColorMatrix').attr(
+ 'values',
+ (value.r / Color.upperBound) + ' 0 0 0 0 0 '
+ + (value.g / Color.upperBound) + ' 0 0 0 0 0 '
+ + (value.b / Color.upperBound) + ' 0 0 0 0 0 1 0');
+ });
+ }
+ }
+
+ _level = 0;
+ get level() {
+ return this._level;
+ }
+ set level(value) {
+ if (Number.isSafeInteger(value)) {
+ this._level = value;
+ this.updateState();
+ }
+ }
+
+ _collapsible = true;
+ get collapsible() {
+ return this._collapsible;
+ }
+ set collapsible(value) {
+ if (typeof value === 'boolean') {
+ this._collapsible = value;
+ this.updateState();
+ }
+ }
+
+ _collapsed = false;
+ get collapsed() {
+ return this._collapsed;
+ }
+
+ _onCollapseChanged = null;
+ set onCollapseChanged(value) {
+ if (typeof value === 'function') {
+ this._onCollapseChanged = value;
+ }
+ }
+
+ _selected = false;
+ get selected() {
+ return this._selected;
+ }
+ set selected(value) {
+ if (typeof value === 'boolean') {
+ this._selected = value;
+ this.updateState();
+ }
+ }
+
+ _onRenameTapped = null;
+ set onRenameTapped(value) {
+ if (typeof value !== 'function' && value !== null) return;
+ this._onRenameTapped = value;
+ }
+
+ _onDeleteTapped = null;
+ set onDeleteTapped(value) {
+ if (typeof value !== 'function' && value !== null) return;
+ this._onDeleteTapped = value;
+ }
+
+ get loaded() {
+ return this.view instanceof jQuery
+ && this.view[0] instanceof HTMLElement
+ && this._textView instanceof jQuery
+ && this._collapseButton instanceof jQuery
+ && this._symbolPreview instanceof jQuery
+ && this._renameButton instanceof jQuery
+ && this._deleteButton instanceof jQuery;
+ }
+
+ _collapseButton = (() => {
+ this.didLoad(_ => {
+ this._collapseButton = this.view.find('#collapsebutton');
+ this._collapseButton.gestureRecognizer = new UITapGestureRecognizer({
+ targetHtmlElement: this._collapseButton[0], onTap: () => {
+ this._collapsed = !this._collapsed;
+ this._collapseButton.css('background', this._collapsed ? '#101010' : 'white');
+ let icon = this._collapseButton.find('i');
+ if (this._collapsed) {
+ icon.removeClass('fa-angle-down');
+ icon.addClass('fa-angle-up');
+ } else {
+ icon.removeClass('fa-angle-up');
+ icon.addClass('fa-angle-down');
+ }
+ if (this._onCollapseChanged) {
+ this._onCollapseChanged(this);
+ }
+ }
+ });
+ });
+ })();
+
+ _symbolPreview = (() => {
+ this.didLoad(_ => {
+ this._symbolPreview = this.view.find('#symbolpreview');
+ });
+ })();
+
+ _textView = (() => {
+ this.didLoad(_ => {
+ this._textView = this.view.find('#textview');
+ });
+ })();
+
+ _renameButton = (() => {
+ this.didLoad(_ => {
+ this._renameButton = this.view.find('#renamebutton');
+ this._renameButton.gestureRecognizer = new UITapGestureRecognizer({
+ targetHtmlElement: this._renameButton[0], onTap: () => {
+ if (this._onRenameTapped) {
+ this._onRenameTapped(this);
+ }
+ }
+ });
+ });
+ })();
+
+ _deleteButton = (() => {
+ this.didLoad(_ => {
+ this._deleteButton = this.view.find('#deletebutton');
+ this._deleteButton.gestureRecognizer = new UITapGestureRecognizer({
+ targetHtmlElement: this._deleteButton[0], onTap: () => {
+ if (this._onDeleteTapped) {
+ this._onDeleteTapped(this);
+ }
+ }
+ });
+ });
+ })();
+
+ constructor({ filePath = null } = {}) {
+ super();
+ let path = filePath;
+ this.didLoad(_ => {
+ if (mobileClient) this.view.css('margin-right', '8px');
+ this.view.attr('src', path);
+ this.gestureRecognizer = new UITapGestureRecognizer({
+ targetHtmlElement: this.view[0], onTap: () => {
+ if (this._onTap) {
+ this._onTap(this);
+ }
+ }
+ });
+ this.view.find('#placeholderid').attr('id', this.uuidString + '-assetpreview');
+ this.view.find('image').attr('filter', 'url(#' + this.uuidString + '-assetpreview)');
+ });
+ }
+
+ updateState() {
+ if (!this.loaded) {
+ this.didLoad(_ => {
+ this.updateState();
+ });
+ return;
+ }
+ this._textView.css('color', this._selected ? '#ff9e2c' : '');
+ this._collapseButton.css('visibility', this._collapsible ? '' : 'hidden');
+ this._symbolPreview.css('visibility', this._collapsible ? 'hidden' : '');
+ this.view.css('margin-left', (this._level * 12) + 'px');
+ }
+
+}
diff --git a/src/components/ui/UILayerPicker.js b/src/components/ui/UILayerPicker.js
new file mode 100644
index 0000000..1beedf8
--- /dev/null
+++ b/src/components/ui/UILayerPicker.js
@@ -0,0 +1,365 @@
+/**
+ * Symbol Art Editor
+ *
+ * @author malulleybovo (since 2021)
+ * @license GNU General Public License v3.0
+ *
+ * @licstart The following is the entire license notice for the
+ * JavaScript code in this page.
+ *
+ * Copyright (C) 2021 Arthur Malulley B. de O.
+ *
+ *
+ * The JavaScript code in this page is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GNU GPL) as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option)
+ * any later version. The code is distributed WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
+ *
+ * As additional permission under GNU GPL version 3 section 7, you
+ * may distribute non-source (e.g., minimized or compacted) forms of
+ * that code without the copy of the GNU GPL normally required by
+ * section 4, provided you include this license notice and a URL
+ * through which recipients can access the Corresponding Source.
+ *
+ * @licend The above is the entire license notice
+ * for the JavaScript code in this page.
+ *
+ */
+
+class UILayerPicker extends UIView {
+
+ get viewPath() { return 'res/templates/layerpicker.html' }
+
+ _listView = (() => {
+ this.didLoad(_ => {
+ this._listView = this.view.find('#listview');
+ this.updateState();
+ });
+ })();
+
+ _symbolCount = (() => {
+ this.didLoad(_ => {
+ this._symbolCount = this.view.find('#symbolcount');
+ this.updateState();
+ });
+ })();
+
+ _symbolTotal = (() => {
+ this.didLoad(_ => {
+ this._symbolTotal = this.view.find('#symboltotal');
+ this.updateState();
+ });
+ })();
+
+ _containerCount = (() => {
+ this.didLoad(_ => {
+ this._containerCount = this.view.find('#containercount');
+ this.updateState();
+ });
+ })();
+
+ _containerTotal = (() => {
+ this.didLoad(_ => {
+ this._containerTotal = this.view.find('#containertotal');
+ this.updateState();
+ });
+ })();
+
+ _layerSearchContainer = (() => {
+ this.didLoad(_ => {
+ this._layerSearchContainer = this.view.find('#layersearchcontainer');
+ });
+ })();
+
+ _layerSearchTextField = (() => {
+ this.didLoad(_ => {
+ this._layerSearchTextField = this.view.find('#layersearchtextfield');
+ if (mobileClient) {
+ this._layerSearchTextField.helper = new UITextFieldHelper({
+ view: this._layerSearchTextField,
+ title: 'Search layer name:',
+ onInput: (value) => {
+ this._updateSearchBar();
+ return value;
+ }
+ });
+ }
+ this.updateState();
+ this._layerSearchTextField.on('input', () => {
+ this._updateSearchBar();
+ });
+ });
+ })();
+
+ _searchButton = (() => {
+ this.didLoad(_ => {
+ this._searchButton = this.view.find('#searchbutton');
+ this._searchButton.gestureRecognizer = new UITapGestureRecognizer({
+ targetHtmlElement: this._searchButton[0], onTap: () => {
+ if (this._layerSearchTextField.val().trim().length > 0) {
+ this._layerSearchTextField.val('');
+ this._updateSearchBar();
+ }
+ }
+ });
+ });
+ })();
+
+ _onLayerSelected = null;
+ set onLayerSelected(value) {
+ if (typeof value !== 'function' && value !== null) return;
+ this._onLayerSelected = value;
+ }
+
+ _onRenameTapped = null;
+ set onRenameTapped(value) {
+ if (typeof value !== 'function' && value !== null) return;
+ this._onRenameTapped = value;
+ }
+
+ _onDeleteTapped = null;
+ set onDeleteTapped(value) {
+ if (typeof value !== 'function' && value !== null) return;
+ this._onDeleteTapped = value;
+ }
+
+ _lastSelectedUuid = null;
+
+ get loaded() {
+ return this.view instanceof jQuery
+ && this.view[0] instanceof HTMLElement
+ && this._listView instanceof jQuery
+ && this._symbolCount instanceof jQuery
+ && this._symbolTotal instanceof jQuery
+ && this._containerCount instanceof jQuery
+ && this._containerTotal instanceof jQuery
+ && this._layerSearchContainer instanceof jQuery
+ && this._layerSearchTextField instanceof jQuery;
+ }
+
+ set isHidden(value) {
+ if (typeof value !== 'boolean') return;
+ this.didLoad(_ => {
+ this.view.css('right', value ? ('-' + this.view.css('width')) : '0px');
+ });
+ }
+
+ constructor({ onLayerSelected = null, onRenameTapped = null, onDeleteTapped = null } = { }) {
+ super();
+ this.onLayerSelected = onLayerSelected;
+ this.onRenameTapped = onRenameTapped;
+ this.onDeleteTapped = onDeleteTapped;
+ this.didLoad(_ => {
+ this.view.gestureRecognizer = new UIGestureRecognizer({
+ targetHtmlElement: this.view[0],
+ preventsDefault: false,
+ onPointerDown: (event) => {
+ // to prevent propagation
+ }, onPointerMove: (event) => {
+ // to prevent propagation
+ }, onPointerUp: (event) => {
+ // to prevent propagation
+ }, onScroll: (event) => {
+ // to prevent propagation
+ }, onKeyPress: (event) => {
+ return false;
+ }
+ });
+ });
+ ApplicationState.shared.add({
+ onChangeViewModeListener: () => {
+ this.updateState();
+ }
+ });
+ this.updateState();
+ }
+
+ _uiLayers = [];
+
+ updateWith({ container }) {
+ if (!(container instanceof Container)) return;
+ if (!this.loaded) {
+ this.didLoad(_ => {
+ this.updateWith({ container: container });
+ });
+ return;
+ }
+ let layers = [];
+ container.reverseDepthFirstIterator(layer => {
+ layers.push(layer);
+ });
+ let uiLayers = [];
+ for (var index in layers) {
+ let layer = layers[index];
+ let matches = this._uiLayers.filter(a => a.identifier === layer.uuid);
+ let uiLayer = null;
+ if (matches[0]) {
+ uiLayer = matches[0];
+ } else {
+ uiLayer = new UILayer();
+ }
+ uiLayer.name = layer.name.length === 0 ? (layer instanceof Container ? 'container' : 'symbol') : layer.name;
+ uiLayer.identifier = layer.uuid;
+ uiLayer.onTap = (uiLayer) => {
+ if (this._onLayerSelected) {
+ this._onLayerSelected(uiLayer.identifier);
+ }
+ };
+ uiLayer.level = layer.distanceFromRoot;
+ uiLayer.collapsible = layer instanceof Container;
+ if (layer instanceof Symbol) {
+ uiLayer.previewAsset = layer.asset;
+ uiLayer.previewAssetColor = layer.color;
+ }
+ uiLayer.onCollapseChanged = instance => {
+ this._updateCollapsibles();
+ };
+ uiLayer.onRenameTapped = instance => {
+ if (this._onRenameTapped) {
+ this._onRenameTapped(instance.identifier);
+ }
+ };
+ uiLayer.onDeleteTapped = instance => {
+ if (this._onDeleteTapped) {
+ this._onDeleteTapped(instance.identifier);
+ }
+ };
+ uiLayers.push(uiLayer);
+ }
+ let requiresUpdate = false;
+ for (var index in uiLayers) {
+ if (this._uiLayers[index] !== uiLayers[index]) {
+ requiresUpdate = true;
+ break;
+ }
+ }
+ if (!requiresUpdate) {
+ for (var index in this._uiLayers) {
+ if (this._uiLayers[index] !== uiLayers[index]) {
+ requiresUpdate = true;
+ break;
+ }
+ }
+ }
+ this._layerSearchTextField.val('');
+ if (requiresUpdate) {
+ this._uiLayers = uiLayers;
+ this._listView.empty();
+ for (var index in this._uiLayers) {
+ this._uiLayers[index].append({ to: this._listView });
+ }
+ this._layerSearchContainer.css('display', '');
+ if (uiLayers.length < 2) {
+ this._layerSearchContainer.css('display', 'none');
+ }
+ setTimeout(_ => {
+ this._updateCollapsibles();
+ }, 10);
+ } else {
+ this._updateCollapsibles();
+ }
+ this.updateLimits();
+ }
+
+ _updateCollapsibles() {
+ let collapsedAtLevel = -1;
+ for (var index in this._uiLayers) {
+ this._uiLayers[index].view.css('display', '');
+ let searchKeys = this._layerSearchTextField.val().trim();
+ if (searchKeys.length > 0) {
+ if (!this._uiLayers[index].name.toUpperCase().includes(searchKeys.toUpperCase())) {
+ this._uiLayers[index].view.css('display', 'none');
+ }
+ } else {
+ if (collapsedAtLevel >= 0) {
+ if (this._uiLayers[index].level > collapsedAtLevel) {
+ this._uiLayers[index].view.css('display', 'none');
+ } else {
+ collapsedAtLevel = -1;
+ }
+ }
+ if (this._uiLayers[index].collapsed) {
+ collapsedAtLevel = this._uiLayers[index].level;
+ }
+ }
+ }
+ }
+
+ _updateSearchBar() {
+ if (!this.loaded) {
+ this.didLoad(_ => {
+ this._updateSearchBar();
+ });
+ return;
+ }
+ let icon = this._searchButton.find('i');
+ if (this._layerSearchTextField.val().trim().length > 0) {
+ icon.removeClass('fa-search');
+ icon.addClass('fa-times');
+ } else {
+ icon.removeClass('fa-times');
+ icon.addClass('fa-search');
+ }
+ this._updateCollapsibles();
+ this.select({ layerWithUuid: this._lastSelectedUuid });
+ }
+
+ updateLimits() {
+ if (!this.loaded) {
+ this.didLoad(_ => {
+ this.updateLimits();
+ });
+ return;
+ }
+ this._symbolCount.text(`${UIApplication.shared.symbolArt.root.numberOfSymbols}`);
+ this._symbolTotal.text(`${SymbolArt.maximumNumberOfSymbols}`);
+ this._containerCount.text(`${UIApplication.shared.symbolArt.root.containers.length}`);
+ this._containerTotal.text(`${SymbolArt.maximumNumberOfContainers}`);
+ }
+
+ updateState() {
+ switch (ApplicationState.shared.viewMode) {
+ case ViewMode.symbolEditorMode:
+ case ViewMode.layerEditorMode:
+ this.isHidden = false;
+ break;
+ case ViewMode.helperImageMode:
+ default:
+ this.isHidden = true;
+ return;
+ }
+ }
+
+ select({ layerWithUuid }) {
+ if (!this.loaded) {
+ this.didLoad(_ => {
+ this.select({ layerWithUuid: layerWithUuid });
+ });
+ return;
+ }
+ this._uiLayers.forEach(uiLayer => {
+ uiLayer.selected = uiLayer.identifier === layerWithUuid;
+ });
+ this._lastSelectedUuid = layerWithUuid;
+ this.scroll({ toLayerWithUuid: layerWithUuid });
+ }
+
+ scroll({ toLayerWithUuid }) {
+ if (!this.loaded) {
+ this.didLoad(_ => {
+ this.scroll({ toLayerWithUuid: toLayerWithUuid });
+ });
+ return;
+ }
+ let uiLayer = this._uiLayers.filter(a => a.identifier === toLayerWithUuid)[0];
+ if (uiLayer) {
+ uiLayer.didLoad(_ => {
+ uiLayer.view[0].scrollIntoView({ block: "nearest", inline: "nearest" });
+ });
+ }
+ }
+
+}
diff --git a/src/components/ui/UIMenu.js b/src/components/ui/UIMenu.js
index 6adf9e0..994e7d4 100644
--- a/src/components/ui/UIMenu.js
+++ b/src/components/ui/UIMenu.js
@@ -33,6 +33,8 @@ class UIMenu extends UIView {
get viewPath() { return 'res/templates/menu.html' }
+ _tapHoldEnabled = true;
+
_collapsibleMenu = new UICollapsibleMenu();
_ellipsisButton = (() => {
@@ -45,6 +47,21 @@ class UIMenu extends UIView {
});
});
})();
+
+ _tapHoldToggleButton = (() => {
+ this.didLoad(_ => {
+ this._tapHoldToggleButton = this.view.find('#tapholdtogglebutton');
+ this._tapHoldToggleButton.gestureRecognizer = new UITapGestureRecognizer({
+ targetHtmlElement: this._tapHoldToggleButton[0], onTap: () => {
+ if (!this._tapHoldEnabled) {
+ ApplicationState.shared.interaction = InteractionType.enablingTapHoldFeature;
+ } else {
+ ApplicationState.shared.interaction = InteractionType.disablingTapHoldFeature;
+ }
+ }
+ });
+ });
+ })();
_undoButton = (() => {
this.didLoad(_ => {
@@ -89,6 +106,16 @@ class UIMenu extends UIView {
ApplicationState.shared.add({
onChangeViewModeListener: () => {
this.updateState();
+ },
+ onChangeInteractionListener: () => {
+ if (ApplicationState.shared.interaction === InteractionType.enablingTapHoldFeature) {
+ this._tapHoldEnabled = true;
+ this.updateState();
+ }
+ if (ApplicationState.shared.interaction === InteractionType.disablingTapHoldFeature) {
+ this._tapHoldEnabled = false;
+ this.updateState();
+ }
}
});
HistoryState.shared.add({
@@ -109,6 +136,7 @@ class UIMenu extends UIView {
this._redoButton.css('opacity', HistoryState.shared.isAtMostRecentState ? '0.5' : '1');
this._symbolArtTypeLabel.text(UIApplication.shared.symbolArt.type === SymbolArtType.symbolArt ? 'SYMBOL ART' : 'TEAM FLAG');
this._symbolArtTypeLabel.css('opacity', window.innerWidth < 460 && ApplicationState.shared.viewMode === ViewMode.layerEditorMode ? '0' : '');
+ this._tapHoldToggleButton.css('color', this._tapHoldEnabled ? 'white' : '#ff9e2c');
});
}
diff --git a/src/components/ui/UIModalTextField.js b/src/components/ui/UIModalTextField.js
index 19f97e9..bb9212f 100644
--- a/src/components/ui/UIModalTextField.js
+++ b/src/components/ui/UIModalTextField.js
@@ -90,6 +90,11 @@ class UIModalTextField extends UIView {
// to prevent propagation
}
});
+ this._textField.on('keyup', event => {
+ if (event.key === 'Enter' || event.keyCode === 13) {
+ this._close();
+ }
+ });
});
})();
diff --git a/src/components/ui/UIView.js b/src/components/ui/UIView.js
index f4c4dfb..015d8b9 100644
--- a/src/components/ui/UIView.js
+++ b/src/components/ui/UIView.js
@@ -114,5 +114,11 @@ class UIView extends UUID {
});
});
}
+
+ remove() {
+ this.didLoad(_ => {
+ this.view.remove();
+ });
+ }
}
diff --git a/src/model/Container.js b/src/model/Container.js
index bf80243..e49f2a2 100644
--- a/src/model/Container.js
+++ b/src/model/Container.js
@@ -34,16 +34,31 @@ class Container extends Layer {
static maximumDepth = 5;
get origin() {
- let containerOrigin = new Origin();
+ let x = 0;
+ let y = 0;
let origins = this.symbols.map(a => a.frame.origin);
for (var index in origins) {
let origin = origins[index];
- containerOrigin.x += origin.x;
- containerOrigin.y += origin.y;
+ x += origin.x;
+ y += origin.y;
+ }
+ x /= origins.length;
+ y /= origins.length;
+ return new Origin({ x: x, y: y });
+ }
+ set origin(value) {
+ if (!(value instanceof Origin)) return;
+ let currentOrigin = this.origin;
+ let dx = SymbolArt.scaling * Math.round((value.x - currentOrigin.x) / SymbolArt.scaling);
+ let dy = SymbolArt.scaling * Math.round((value.y - currentOrigin.y) / SymbolArt.scaling);
+ let frames = this.symbols.map(a => a.frame);
+ for (var index in frames) {
+ let origin = frames[index].origin;
+ frames[index].origin = new Origin({
+ x: origin.x + SymbolArt.scaling * Math.round(dx / SymbolArt.scaling),
+ y: origin.y + SymbolArt.scaling * Math.round(dy / SymbolArt.scaling)
+ });
}
- containerOrigin.x /= origins.length;
- containerOrigin.y /= origins.length;
- return containerOrigin;
}
get boundingBox() {
@@ -223,6 +238,21 @@ class Container extends Layer {
}
}
}
+
+ reverseDepthFirstIterator(callback) {
+ for (var index = this._sublayers.length - 1; index >= 0; index--) {
+ let sublayer = this._sublayers[index];
+ if (sublayer instanceof Symbol) {
+ let shouldBreak = callback(sublayer);
+ if (shouldBreak) return true;
+ } else if (sublayer instanceof Container) {
+ let shouldBreak = callback(sublayer);
+ if (shouldBreak) return true;
+ shouldBreak = sublayer.reverseDepthFirstIterator(callback);
+ if (shouldBreak) return true;
+ }
+ }
+ }
contains({ sublayer = undefined } = {}) {
if (typeof sublayer === 'undefined')
diff --git a/src/model/InteractionType.js b/src/model/InteractionType.js
index c7f22bf..371c269 100644
--- a/src/model/InteractionType.js
+++ b/src/model/InteractionType.js
@@ -49,6 +49,9 @@ class InteractionType {
static reshapingHelperImageDidBegin = 15;
static reshapingContainerWillBegin = 16;
static reshapingContainerDidBegin = 17;
+ static willCloneCurrentSelection = 18;
+ static enablingTapHoldFeature = 19;
+ static disablingTapHoldFeature = 20;
static valid({ rawValue }) {
switch (rawValue) {
@@ -70,6 +73,9 @@ class InteractionType {
case InteractionType.reshapingHelperImageDidBegin:
case InteractionType.reshapingContainerWillBegin:
case InteractionType.reshapingContainerDidBegin:
+ case InteractionType.willCloneCurrentSelection:
+ case InteractionType.enablingTapHoldFeature:
+ case InteractionType.disablingTapHoldFeature:
return true;
default:
return false;
diff --git a/src/model/Symbol.js b/src/model/Symbol.js
index 9f65ce8..94df2df 100644
--- a/src/model/Symbol.js
+++ b/src/model/Symbol.js
@@ -110,6 +110,7 @@ class Symbol extends Layer {
clone({ retainUuid = true } = { }) {
let clone = new Symbol({
+ name: this.name,
origin: new Origin({ x: this.frame.origin.x, y: this.frame.origin.y }),
sizeOfDiagonalAC: this.frame.sizeOfDiagonalAC,
sizeOfDiagonalBD: this.frame.sizeOfDiagonalBD,
diff --git a/src/view/renderer/RendererPresenter.js b/src/view/renderer/RendererPresenter.js
index 05df3a5..287f05a 100644
--- a/src/view/renderer/RendererPresenter.js
+++ b/src/view/renderer/RendererPresenter.js
@@ -123,12 +123,51 @@ class RendererPresenter extends InputDevice {
ApplicationState.shared.trigger = TriggerType.none;
this._renderer.helperImage.greenScreenEnabled = !this._renderer.helperImage.greenScreenEnabled;
}
+ },
+ onChangeInteractionListener: _ => {
+ if (ApplicationState.shared.interaction === InteractionType.enablingTapHoldFeature) {
+ ApplicationState.shared.interaction = InteractionType.none;
+ this.longTouchesWithoutDelay = false;
+ }
+ if (ApplicationState.shared.interaction === InteractionType.disablingTapHoldFeature) {
+ ApplicationState.shared.interaction = InteractionType.none;
+ this.longTouchesWithoutDelay = true;
+ }
}
});
}
-
+
tapped(event) {
- if (ApplicationState.shared.interaction !== InteractionType.none) return;
+ if (ApplicationState.shared.viewMode === ViewMode.symbolEditorMode
+ && ApplicationState.shared.interaction === InteractionType.willCloneCurrentSelection) {
+ let layerToClone = UIApplication.shared.symbolArt.findLayer({ withUuidString: this._renderer.selectionUuid });
+ let worldPosition = this.worldVector(event);
+ let newLayer = null;
+ if (layerToClone instanceof Layer && layerToClone.parent instanceof Container) {
+ newLayer = layerToClone.clone({ retainUuid: false });
+ let container = layerToClone.parent;
+ let index = container.indexOf({ sublayer: layerToClone }) + 1;
+ container.add({ sublayer: newLayer, atIndex: index });
+ } else {
+ newLayer = new Symbol();
+ let container = UIApplication.shared.symbolArt.root;
+ container.add({ sublayer: newLayer });
+ }
+ if (newLayer instanceof Symbol) {
+ let x = newLayer.frame.origin.x + SymbolArt.scaling * Math.round((worldPosition.x - newLayer.frame.origin.x) / SymbolArt.scaling);
+ let y = newLayer.frame.origin.y + SymbolArt.scaling * Math.round((worldPosition.y - newLayer.frame.origin.y) / SymbolArt.scaling);
+ newLayer.frame.origin = new Origin({ x: x, y: y });
+ } else if (newLayer instanceof Container) {
+ newLayer.origin = new Origin({ x: worldPosition.x, y: worldPosition.y });
+ }
+ ApplicationState.shared.interaction = InteractionType.none;
+ this._renderer.updateWith({ symbolArt: UIApplication.shared.symbolArt });
+ this._renderer.setSelection({ layer3D: Object.values(Layer3D.layersInUse).filter(a => a.layerUuid === newLayer.uuid)[0] });
+ SymbolControls3D.shared.attach({ toSymbol3D: this._renderer.selectionGroup });
+ ContainerControls3D.shared.attach({ toContainer3D: this._renderer.selectionGroup });
+ HelperImageControls3D.shared.attach({ toHelperImage: null });
+ HistoryState.shared.pushHistory({ data: UIApplication.shared.symbolArt.clone() });
+ } else if (ApplicationState.shared.interaction !== InteractionType.none) return;
if (ApplicationState.shared.viewMode === ViewMode.layerEditorMode) {
this._renderer.touched({ screenPosition: this.normalizedVector(event), hitsLayers: true, hitsControls: false });
if (this._renderer.selectionUuid) {
@@ -141,7 +180,7 @@ class RendererPresenter extends InputDevice {
this._updateSymbolColorGuess();
}
}
-
+
touchBegan(event, count) {
if (count > 1) return;
this._initialEvent = event;
@@ -912,10 +951,24 @@ class RendererPresenter extends InputDevice {
} else if ((event.ctrlKey || event.metaKey) && (event.code === 'Minus' || event.keyCode === 189)) {
event.preventDefault();
this._renderer.zoom *= 0.95;
+ } else {
+ if (!this.longTouchesWithoutDelay && (event.key === "Shift" || event.keyCode === 16)) {
+ ApplicationState.shared.interaction = InteractionType.disablingTapHoldFeature;
+ } else if (ApplicationState.shared.viewMode === ViewMode.symbolEditorMode && (event.key === "Control" || event.keyCode === 17) && ApplicationState.shared.interaction !== InteractionType.willCloneCurrentSelection) {
+ ApplicationState.shared.interaction = InteractionType.willCloneCurrentSelection;
+ } else if (ApplicationState.shared.interaction === InteractionType.willCloneCurrentSelection && !(event.key === "Control" || event.keyCode === 17)) {
+ ApplicationState.shared.interaction = InteractionType.none;
+ }
}
}
pressedKey(event) {
+ if (ApplicationState.shared.interaction === InteractionType.willCloneCurrentSelection) {
+ ApplicationState.shared.interaction = InteractionType.none;
+ }
+ if (this.longTouchesWithoutDelay && (event.key === "Shift" || event.keyCode === 16)) {
+ ApplicationState.shared.interaction = InteractionType.enablingTapHoldFeature;
+ }
if (!event || ApplicationState.shared.interaction !== InteractionType.none || this._renderer.hasOngoingAnimations) return;
if (event.code === 'Space' || event.keyCode === 32) {
ApplicationState.shared.trigger = TriggerType.none;
@@ -934,6 +987,10 @@ class RendererPresenter extends InputDevice {
} else if (((event.ctrlKey || event.metaKey) && (event.code === 'KeyY' || event.keyCode === 89))
|| ((event.ctrlKey || event.metaKey) && event.shiftKey && (event.code === 'KeyZ' || event.keyCode === 90))) {
HistoryState.shared.redo();
+ } else if (event.code === 'ArrowUp' || event.keyCode === 38) {
+ this._selectNextLayer();
+ } else if (event.code === 'ArrowDown' || event.keyCode === 40) {
+ this._selectPreviousLayer();
}
}
@@ -1119,6 +1176,7 @@ class RendererPresenter extends InputDevice {
if (layer.name == text) return;
layer.name = text;
this._renderer.updateWith({ symbolArt: UIApplication.shared.symbolArt });
+ HistoryState.shared.pushHistory({ data: UIApplication.shared.symbolArt.clone() });
}
});
}
@@ -1239,5 +1297,42 @@ class RendererPresenter extends InputDevice {
}
}
}
+
+ _selectNextLayer() {
+ let foundCurrentlySelected = false;
+ let layers = [];
+ UIApplication.shared.symbolArt.root.reverseDepthFirstIterator(layer => {
+ layers.push(layer);
+ });
+ for (var index = layers.length - 1; index >= 0; index--) {
+ let layer = layers[index];
+ if (foundCurrentlySelected) {
+ this._renderer.setSelection({ layer3D: Object.values(Layer3D.layersInUse).filter(a => a.layerUuid === layer.uuid)[0] });
+ SymbolControls3D.shared.attach({ toSymbol3D: this._renderer.selectionGroup });
+ ContainerControls3D.shared.attach({ toContainer3D: this._renderer.selectionGroup });
+ HelperImageControls3D.shared.attach({ toHelperImage: null });
+ break;
+ }
+ if (layer.uuid === this._renderer.selectionUuid) {
+ foundCurrentlySelected = true;
+ }
+ }
+ }
+
+ _selectPreviousLayer() {
+ let foundCurrentlySelected = false;
+ UIApplication.shared.symbolArt.root.reverseDepthFirstIterator(layer => {
+ if (foundCurrentlySelected) {
+ this._renderer.setSelection({ layer3D: Object.values(Layer3D.layersInUse).filter(a => a.layerUuid === layer.uuid)[0] });
+ SymbolControls3D.shared.attach({ toSymbol3D: this._renderer.selectionGroup });
+ ContainerControls3D.shared.attach({ toContainer3D: this._renderer.selectionGroup });
+ HelperImageControls3D.shared.attach({ toHelperImage: null });
+ return true;
+ }
+ if (layer.uuid === this._renderer.selectionUuid) {
+ foundCurrentlySelected = true;
+ }
+ });
+ }
}