diff --git a/Platform/UI/Canvas.js b/Platform/UI/Canvas.js index 210194f73..7a2d09b74 100644 --- a/Platform/UI/Canvas.js +++ b/Platform/UI/Canvas.js @@ -515,6 +515,11 @@ function newCanvas() { return } } + if (nodeOnFocus.payload.uiObject.listSelector !== undefined) { + if (nodeOnFocus.payload.uiObject.listSelector.visible === true) { + return + } + } if (nodeOnFocus.payload.uiObject.conditionEditor !== undefined) { if (nodeOnFocus.payload.uiObject.conditionEditor.visible === true) { return diff --git a/Projects/Community-Plugins/Schemas/App-Schema/plugin-user-profiles.json b/Projects/Community-Plugins/Schemas/App-Schema/plugin-user-profiles.json index 8442d092a..4762e60e2 100644 --- a/Projects/Community-Plugins/Schemas/App-Schema/plugin-user-profiles.json +++ b/Projects/Community-Plugins/Schemas/App-Schema/plugin-user-profiles.json @@ -12,6 +12,17 @@ "actionProject": "Governance", "relatedUiObjectProject": "Governance" }, + { + "action": "Add Specified User Profile", + "label": "Add Specified User Profile", + "iconPathOn": "user-profile", + "iconPathOff": "user-profile", + "iconProject": "Governance", + "actionProject": "Governance", + "relatedUiObject": "User Profile", + "dontShowAtFullscreen": true, + "actionFunction": "payload.executeAction" + }, { "action": "Delete UI Object", "actionProject": "Visual-Scripting", @@ -30,6 +41,9 @@ "attachingRules": { "compatibleTypes": "->Plugin Project->" }, + "editors": { + "list": true + }, "childrenNodesProperties": [ { "name": "pluginFiles", diff --git a/Projects/Foundations/UI/Spaces/Floating-Space/ListSelector.js b/Projects/Foundations/UI/Spaces/Floating-Space/ListSelector.js new file mode 100644 index 000000000..2339da76f --- /dev/null +++ b/Projects/Foundations/UI/Spaces/Floating-Space/ListSelector.js @@ -0,0 +1,321 @@ + +function newListSelector() { + const MODULE_NAME = 'List Selector' + + let thisObject = { + isVisibleFunction: undefined, + visible: false, + imagePathOK: undefined, + imagePathNOT_OK: undefined, + rawRadius: undefined, + targetRadius: undefined, + currentRadius: undefined, + container: undefined, + payload: undefined, + deactivate: deactivate, + activate: activate, + physics: physics, + drawBackground: drawBackground, + drawForeground: drawForeground, + onMouseClick: onMouseClick, + raiseEventParentChanged: raiseEventParentChanged, + onMouseWheel: onMouseWheel, + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + thisObject.container.isWheelable = true + thisObject.container.detectMouseOver = true + thisObject.container.frame.radius = 0 + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + + let optionsList = {} + let current + let icon + let propertyName + let lastSelected = 0 + let SIZE_FACTOR = 1.0 + let onMouseWheelEventSubscriptionId + let selfMouseClickEventSubscriptionId + let onMouseClickEventSubscriptionId + + const SIZE = 36 + const FONT_SIZE = 24 + const VISIBLE_LABELS = 7 + let selected = Math.ceil(VISIBLE_LABELS / 2) - 1 + + let wheelDeltaDirection + let wheelDeltaCounter = 0 + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseWheelEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfMouseClickEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseClickEventSubscriptionId) + thisObject.container.finalize() + thisObject.container = undefined + thisObject.payload = undefined + thisObject.isVisibleFunction = undefined + } + + function initialize() { + icon = UI.projects.foundations.spaces.designSpace.getIconByProjectAndName( 'Governance', 'user-profile') + onMouseWheelEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseWheel', onMouseWheel) + selfMouseClickEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + } + + function deactivate() { + if (thisObject.payload === undefined) { return } + if (thisObject.visible === true) { + thisObject.visible = false + } + thisObject.payload.uiObject.container.eventHandler.stopListening(onMouseClickEventSubscriptionId) + EDITOR_ON_FOCUS = false + } + + function raiseEventParentChanged() { + let event = { + selected: selected, + parent: current, + propertyName: propertyName + } + thisObject.container.eventHandler.raiseEvent('onParentChanged', event) + } + + function activate(action, listValues, eventSubscriptionId) { + thisObject.visible = true + thisObject.payload = action.node.payload + thisObject.rawRadius = 8 + thisObject.targetRadius = thisObject.container.frame.radius + thisObject.currentRadius = 0 + thisObject.payload.uiObject.resetErrorMessage() + thisObject.payload.uiObject.uiObjectTitle.exitEditMode() + + onMouseClickEventSubscriptionId = eventSubscriptionId + + optionsList = listValues + + EDITOR_ON_FOCUS = true + } + + function onMouseWheel() { + + if (IS_MAC && Math.abs(event.wheelDelta) < MAC_MOUSE_WHEEL_INFINITE_SCROLL_THRESHOLD) { + let sensitivity + if (event.delta < 0) { + if (event.shiftKey === true) { + sensitivity = MAC_SCROLL_SENSITIVITY + } else { + sensitivity = MAC_ZOOM_SENSITIVITY + } + if (wheelDeltaDirection === -1) { + wheelDeltaCounter++ + if (wheelDeltaCounter < sensitivity) { + return + } else { + wheelDeltaCounter = 0 + } + } else { + wheelDeltaCounter = 0 + wheelDeltaDirection = -1 + return + } + } else { + if (event.shiftKey === true) { + sensitivity = MAC_SCROLL_SENSITIVITY + } else { + sensitivity = MAC_ZOOM_SENSITIVITY + } + if (wheelDeltaDirection === 1) { + wheelDeltaCounter++ + if (wheelDeltaCounter < sensitivity) { + return + } else { + wheelDeltaCounter = 0 + } + } else { + wheelDeltaCounter = 0 + wheelDeltaDirection = 1 + return + } + } + event.delta = - event.delta + } + + selected = selected - event.delta + if (selected < Math.ceil(VISIBLE_LABELS / 2)) { selected = Math.ceil(VISIBLE_LABELS / 2) - 1 } + if (selected > optionsList.length - Math.ceil(VISIBLE_LABELS / 2)) { selected = optionsList.length - Math.ceil(VISIBLE_LABELS / 2) } + + if (selected !== lastSelected) { + lastSelected = selected + raiseEventParentChanged() + } + } + + function onMouseClick(pPoint) { + + let tableStartPosition = { + x: thisObject.container.frame.radius, + y: thisObject.container.frame.radius + } + + tableStartPosition = thisObject.container.frame.frameThisPoint(tableStartPosition) + + let offset = thisObject.container.frame.radius / ( 8 / 5 ) + let listSquare = offset * 2 + let rowHeight = listSquare / VISIBLE_LABELS + + if (pPoint.x >= tableStartPosition.x - offset && pPoint.x <= tableStartPosition.x + offset) { + for (let i = 0; i < VISIBLE_LABELS; i++) { + + let upperBoundary = (tableStartPosition.y - offset) + (i * rowHeight) + let lowerBoundary = (tableStartPosition.y - offset) + ((i + 1) * rowHeight) + + if (pPoint.y >= upperBoundary && pPoint.y <= lowerBoundary) { + + let clickedValue = (i - Math.floor(VISIBLE_LABELS / 2) + selected) + + let event = { + point: pPoint, + parent: thisObject, + selectedNode: optionsList[clickedValue] + } + + thisObject.payload.uiObject.container.eventHandler.raiseEvent('listSelectorClicked', event) + + deactivate() + } + } + } + } + + function getContainer(point) { + + if (thisObject.visible === true) { + + if (point.x === VERY_LARGE_NUMBER) { + /* The the mouse leaves the canvas, and event of mouse over with a ridiculous coordinate is triggered so that + anyone can react. In our case, the code editor has a text area that is not part of the canvas, so the event is + triggered. We compensate recognizing this coordinate and returning our container. */ + return thisObject.container + } + + + if (thisObject.container.frame.isThisPointHere(point, true, false) === true) { + return thisObject.container + } else { + return undefined + } + } + } + + function physics() { + if (Math.abs(thisObject.currentRadius - thisObject.targetRadius) >= 0.5) { + if (thisObject.currentRadius < thisObject.targetRadius) { + thisObject.currentRadius = thisObject.currentRadius + 0.5 + } else { + thisObject.currentRadius = thisObject.currentRadius - 0.5 + } + } + + thisObject.container.frame.position.x = -thisObject.container.frame.radius + thisObject.container.frame.position.y = -thisObject.container.frame.radius + + thisObject.container.frame.width = thisObject.container.frame.radius * 2 * SIZE_FACTOR + thisObject.container.frame.height = thisObject.container.frame.radius * 2 * SIZE_FACTOR + + + } + + function drawBackground() { + if (thisObject.visible === true) { + + let position = { + x: 0, + y: 0 + } + + position = thisObject.container.frame.frameThisPoint(position) + + let radius = thisObject.container.frame.radius * SIZE_FACTOR + + if (radius > 0.5) { + browserCanvasContext.beginPath() + browserCanvasContext.arc(position.x + thisObject.container.frame.radius, position.y + thisObject.container.frame.radius, radius + 3, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + 1 + ')' + browserCanvasContext.fill() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(position.x + thisObject.container.frame.radius, position.y + thisObject.container.frame.radius, radius , 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.MIDNIGHT_GREEN + ', ' + 1 + ')' + browserCanvasContext.fill() + } + } + } + function drawForeground() { + + if (thisObject.visible === true) { + + let fontSize + let fontColor + let opacity + + for (let i = 0; i < VISIBLE_LABELS; i++) { + + let index = i - Math.floor(VISIBLE_LABELS / 2) + selected + let label = '' + if (index >= 0 && index < optionsList.length) { + label = optionsList[index] + } + fontColor = UI_COLOR.BLACK + fontSize = FONT_SIZE + opacity = 1 + + let offset = thisObject.container.frame.radius / ( 8 / 5 ) + let listSquare = offset * 2 + let rowOffset = listSquare * ( i / VISIBLE_LABELS ) + let rowHeight = listSquare / VISIBLE_LABELS + + let rowPosition = { + x: thisObject.container.frame.radius, + y: (thisObject.container.frame.radius - offset) + rowOffset + (rowHeight / 2) + } + + rowPosition = thisObject.container.frame.frameThisPoint(rowPosition) + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(rowPosition.x - offset, rowPosition.y - rowHeight / 2); + browserCanvasContext.lineTo(rowPosition.x + offset, rowPosition.y - rowHeight / 2); + browserCanvasContext.lineTo(rowPosition.x + offset, rowPosition.y + rowHeight / 2); + browserCanvasContext.lineTo(rowPosition.x - offset, rowPosition.y + rowHeight / 2); + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.MIDNIGHT_GREEN + ', 1)'; + browserCanvasContext.lineWidth = 3 + browserCanvasContext.stroke() + + if (index % 2) { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.OCEAN_GREEN + ', 0.2)'; + } else { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.OCEAN_GREEN + ', 0.4)'; + } + + browserCanvasContext.fill(); + + let middleValue = Math.floor(VISIBLE_LABELS / 2) + + UI.projects.foundations.utilities.drawPrint.drawIcon(icon, 1 / 2, 1 / 2, - offset + SIZE, (i - middleValue) * rowHeight, SIZE, thisObject.container) + UI.projects.foundations.utilities.drawPrint.drawLabel(label, 1 / 2, 1 / 2, 0, (SIZE - fontSize) / 2 + (i - middleValue) * rowHeight, fontSize, thisObject.container, fontColor, undefined, undefined, opacity) + } + } + } +} + diff --git a/Projects/Foundations/UI/Spaces/Floating-Space/UiObject.js b/Projects/Foundations/UI/Spaces/Floating-Space/UiObject.js index c30bc434c..80ab72198 100644 --- a/Projects/Foundations/UI/Spaces/Floating-Space/UiObject.js +++ b/Projects/Foundations/UI/Spaces/Floating-Space/UiObject.js @@ -18,6 +18,7 @@ function newUiObject() { configEditor: undefined, conditionEditor: undefined, formulaEditor: undefined, + listSelector: undefined, uiObjectTitle: undefined, icon: undefined, uiObjectMessage: undefined, @@ -210,6 +211,11 @@ function newUiObject() { thisObject.formulaEditor = undefined } + if (thisObject.listSelector !== undefined) { + thisObject.listSelector.finalize() + thisObject.listSelector = undefined + } + icon = undefined chainAttachToNode = undefined referenceAttachToNode = undefined @@ -309,6 +315,11 @@ function newUiObject() { if (container !== undefined) { return container } } + if (thisObject.listSelector !== undefined) { + container = thisObject.listSelector.getContainer(point) + if (container !== undefined) { return container } + } + let getitle = true if (thisObject.codeEditor !== undefined) { @@ -331,6 +342,11 @@ function newUiObject() { getitle = false } } + if (thisObject.listSelector !== undefined) { + if (thisObject.listSelector.visible === true) { + getitle = false + } + } if (getitle === true) { container = thisObject.uiObjectTitle.getContainer(point) @@ -381,6 +397,10 @@ function newUiObject() { thisObject.formulaEditor.physics() } + if (thisObject.listSelector !== undefined) { + thisObject.listSelector.physics() + } + if (thisObject.payload.chainParent === undefined || thisObject.payload.chainParent.payload === undefined) { thisObject.payload.targetPosition.x = thisObject.payload.position.x, thisObject.payload.targetPosition.y = thisObject.payload.position.y @@ -1353,6 +1373,9 @@ function newUiObject() { if (thisObject.formulaEditor !== undefined) { thisObject.formulaEditor.deactivate() } + if (thisObject.listSelector !== undefined) { + thisObject.listSelector.deactivate() + } if (thisObject.payload !== undefined && thisObject.isOnFocus === false && @@ -1381,6 +1404,9 @@ function newUiObject() { if (thisObject.formulaEditor !== undefined) { thisObject.formulaEditor.deactivate() } + if (thisObject.listSelector !== undefined) { + thisObject.listSelector.deactivate() + } isDragging = true if (event.button === 2) { rightDragging = true @@ -1468,6 +1494,10 @@ function newUiObject() { thisObject.formulaEditor.drawBackground() thisObject.formulaEditor.drawForeground() } + if (thisObject.listSelector !== undefined) { + thisObject.listSelector.drawBackground() + thisObject.listSelector.drawForeground() + } let drawMenu = true let drawTitle = true @@ -1496,6 +1526,12 @@ function newUiObject() { drawTitle = false } } + if (thisObject.listSelector !== undefined) { + if (thisObject.listSelector.visible === true) { + drawMenu = false + drawTitle = false + } + } if (drawMenu === true) { drawBodyAndPicture() @@ -1673,6 +1709,9 @@ function newUiObject() { if (thisObject.formulaEditor !== undefined) { if (thisObject.formulaEditor.visible === true) { return true } } + if (thisObject.listSelector !== undefined) { + if (thisObject.listSelector.visible === true) { return true } + } } function drawNodeType() { diff --git a/Projects/Foundations/UI/Spaces/Floating-Space/UiObjectConstructor.js b/Projects/Foundations/UI/Spaces/Floating-Space/UiObjectConstructor.js index 95ea99825..855aa10b7 100644 --- a/Projects/Foundations/UI/Spaces/Floating-Space/UiObjectConstructor.js +++ b/Projects/Foundations/UI/Spaces/Floating-Space/UiObjectConstructor.js @@ -473,6 +473,12 @@ function newUiObjectConstructor() { uiObject.formulaEditor.container.connectToParent(uiObject.container, false, false, true, true, false, false, false, false) uiObject.formulaEditor.initialize() } + if (schemaDocument.editors.list === true) { + uiObject.listSelector = newListSelector() + uiObject.listSelector.isVisibleFunction = uiObject.isVisibleFunction + uiObject.listSelector.container.connectToParent(uiObject.container, false, false, true, true, false, false, false, false) + uiObject.listSelector.initialize() + } if (schemaDocument.editors.condition === true) { uiObject.conditionEditor = newConditionEditor() uiObject.conditionEditor.isVisibleFunction = uiObject.isVisibleFunction diff --git a/Projects/Governance/UI/Function-Libraries/ActionSwitch.js b/Projects/Governance/UI/Function-Libraries/ActionSwitch.js index 5732d4e22..50eabdc4d 100644 --- a/Projects/Governance/UI/Function-Libraries/ActionSwitch.js +++ b/Projects/Governance/UI/Function-Libraries/ActionSwitch.js @@ -68,6 +68,11 @@ function newGovernanceActionSwitch() { UI.projects.governance.functionLibraries.pluginsFunctions.addMissingPluginUserProfiles(action.node, action.rootNodes) } break + case 'Add Specified User Profile': + { + UI.projects.governance.functionLibraries.pluginsFunctions.addSpecifiedUserProfile(action.node, action.rootNodes) + } + break case 'Add Missing Plugin Pools': { UI.projects.governance.functionLibraries.pluginsFunctions.addMissingPluginPools(action.node, action.rootNodes) diff --git a/Projects/Governance/UI/Function-Libraries/PluginsFunctions.js b/Projects/Governance/UI/Function-Libraries/PluginsFunctions.js index 6209338a6..2eaf1b188 100644 --- a/Projects/Governance/UI/Function-Libraries/PluginsFunctions.js +++ b/Projects/Governance/UI/Function-Libraries/PluginsFunctions.js @@ -1,6 +1,7 @@ function newGovernanceFunctionLibraryPluginsFunctions() { let thisObject = { addMissingPluginUserProfiles: addMissingPluginUserProfiles, + addSpecifiedUserProfile: addSpecifiedUserProfile, addMissingPluginPools: addMissingPluginPools, addMissingPluginAssets: addMissingPluginAssets, addMissingPluginFeatures: addMissingPluginFeatures, @@ -27,6 +28,35 @@ function newGovernanceFunctionLibraryPluginsFunctions() { } } + function addSpecifiedUserProfile(node, rootNodes) { + let action = { node: node} + + let projectName = UI.projects.communityPlugins.utilities.plugins.getProjectName(node) + if (projectName === "" || projectName === undefined) { + if (node.payload.parentNode !== undefined) { + node.payload.parentNode.payload.uiObject.setErrorMessage( + "Config codeName must have the name of the project.", + UI.projects.governance.globals.designer.SET_ERROR_COUNTER_FACTOR + ) + return + } + } + + UI.projects.communityPlugins.utilities.plugins.getPluginFileNames(projectName, 'User-Profiles', onNamesArrived) + + function onNamesArrived(fileNames) { + let eventSubscriptionId = node.payload.uiObject.container.eventHandler.listenToEvent('listSelectorClicked', onListSelect) + node.payload.uiObject.listSelector.activate(action, fileNames, eventSubscriptionId) + + function onListSelect(event) { + let selectedArray = [] + selectedArray.push(event.selectedNode) + UI.projects.communityPlugins.utilities.plugins.addMissingPluginFiles(node, selectedArray, 'User-Profiles', 'User Profile', 'Governance') + node.payload.uiObject.container.eventHandler.stopListening('listSelectorClicked') + } + } + } + function addMissingPluginPools(node, rootNodes) { let projectName = UI.projects.communityPlugins.utilities.plugins.getProjectName(node) if (projectName === "" || projectName === undefined) {