diff --git a/applications/BodyPaint4/bodyPaint4.js b/applications/BodyPaint4/bodyPaint4.js new file mode 100644 index 0000000..c513608 --- /dev/null +++ b/applications/BodyPaint4/bodyPaint4.js @@ -0,0 +1,1351 @@ +// +// Bodypaint4.js +// +// Created by David Rowe on 15 Feb 2017 (as fingerpaint.js) +// Copyright 2017 High Fidelity, Inc. +// +// !NOTE, fingerpaint.js is likely the base of this script, it is unknown which High Fidelity employee(s) turned it into the "bodypaint app", possibly "Simon" +// +// Changelog: +// +// 4.00 August 2022 by @SilverfishVR: +// Polylines created renamed to "bodypaint". +// App icons modified and moved to script directory. +// Sort of fixed continous line mode, line width is still wrong on first point of a new segnment if continous line is enabled +// Increased max line width to 0.05 (was 0.036). +// Some dead code removed. +// 2 brushes added, "Paws" (https://freesvg.org/1553022565) and "keepOut" (https://freesvg.org/muga-barb-wire) both are licenced under https://creativecommons.org/licenses/publicdomain/ +// Renamed to Bodypaint4 since it was previously distributed as Bodypaint 3 +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http:// www.apache.org/licenses/LICENSE-2.0.html +// +(function () { + var tablet, + button, + BUTTON_NAME = "BODY PAINT", + // undo vars + _deleteAllStack = [], + UNDO_STACK_SIZE = 5, + _undoStack = [], + _isFingerPainting = false, + _isTabletFocused = false, + _shouldRestoreTablet = false, + MAX_LINE_WIDTH = 0.05, + _leftHand = null, + _rightHand = null, + _leftBrush = null, + _rightBrush = null, + _isBrushColored = false, + _isLeftHandDominant = false, + _isMouseDrawing = false, + _savedSettings = null, + _isTabletDisplayed = false, + _paintMode = PAINT_WORLD, + _nearestAvatarInfo = null, + _avatarID = null, + _nearestJointIndex = null, + // options + PAINT_WORLD = "paint_world", + PAINT_SELF = "paint_self", + PAINT_OTHERS = "paint_others", + CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint", + HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index", + HIFI_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable", + HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable", + SCRIPT_PATH = Script.resolvePath(''), + CONTENT_PATH = SCRIPT_PATH.substr(0, SCRIPT_PATH.lastIndexOf('/')), + ANIMATION_SCRIPT_PATH = Script.resolvePath("content/animatedBrushes/animatedBrushScript.js"), + APP_URL = CONTENT_PATH + "/html/main.html", + ICON_URL = CONTENT_PATH + "/content/appicons/body-paint-i.svg", + ACTIVE_ICON_URL = CONTENT_PATH + "/content/appicons/body-paint-a.svg"; + + Script.include("/~/system/libraries/controllers.js"); + Script.include("content/js/ColorUtils.js"); + Script.include("content/animatedBrushes/animatedBrushesList.js"); + + var _inkSourceOverlay = null; + // Set path for finger paint hand animations + var RIGHT_ANIM_URL = Script.resourcesPath() + 'avatar/animations/touch_point_closed_right.fbx'; + var LEFT_ANIM_URL = Script.resourcesPath() + 'avatar/animations/touch_point_closed_left.fbx'; + var RIGHT_ANIM_URL_OPEN = Script.resourcesPath() + 'avatar/animations/touch_point_open_right.fbx'; + var LEFT_ANIM_URL_OPEN = Script.resourcesPath() + 'avatar/animations/touch_point_open_left.fbx'; + + function getNearestAvatarInfo(startPosition) { + var paintingFingerPosition = startPosition; + var searchRadius = 2; + + var avatarUUIDList = AvatarList.getAvatarsInRange(paintingFingerPosition, searchRadius).filter(function (uuid) { + return uuid !== MyAvatar.sessionUUID; // self + }); + + if (avatarUUIDList.length > 0) { + + var closestAvatarInfo = null; + var minDistance; + + for (var i = 0; i < avatarUUIDList.length; i++) { + var avatarInfo = AvatarList.getAvatar(avatarUUIDList[i]); + var position = avatarInfo.position; + + // check for closest avatar + var distance = Vec3.distance(position, startPosition); + if (!minDistance || minDistance > distance) { + minDistance = distance; + closestAvatarInfo = avatarInfo; + } + } + + return closestAvatarInfo; + } else { + // no avatars found + return; + } + + } + + function findClosestJoint(avatarInfo, startPosition) { + // + // Iterate through the avatar's joints and return the joint closest to position, + // excluding some joints so that we don't draw onto eyeballs, etc. + + var jointsOfInterest = avatarInfo.jointNames; + + if (avatarInfo.sessionUUID === MyAvatar.sessionUUID) { + print("DRAWING TO SELF"); + jointsOfInterest = jointsOfInterest.filter(function (jointName) { + var hand = (_isLeftHandDominant ? "LeftHand" : "RightHand"); + return jointName.indexOf(hand) === -1; + }); + } + + var closestJointIndex = -1; + var minDistance; + + var jointNamePatterns = [ + "hips", "leg", "spine", "arm", "hand", "head", "shoulder", "finger", "wrist", "toe", "hair", "neck"]; + + for (var i = 0; i < jointsOfInterest.length; i++) { + var jointName = jointsOfInterest[i]; + + var index = avatarInfo.getJointIndex(jointName); + if (index === -1) { + continue; + } + + var foundMatch = false; + for (var p = 0; p < jointNamePatterns.length; ++p) { + if (jointName.match(new RegExp(".*" + jointNamePatterns[p] + ".*", "i"))) { + foundMatch = true; + break; + } + } + if (!foundMatch) { + continue; + } + + // check for closest joint + var position = avatarInfo.getJointPosition(index); + var distance = Vec3.distance(position, startPosition); + if (!minDistance || minDistance > distance) { + minDistance = distance; + closestJointIndex = index; + } + } + + // check if collides with another polyline + + // getEntityProperties() get the parentID and parentJointID + // + + // which is closer to use + return closestJointIndex; + } + + function paintBrush(name) { + // Paints in 3D + var brushName = name, + _strokeColor = { + red: _savedSettings.currentColor.red, + green: _savedSettings.currentColor.green, + blue: _savedSettings.currentColor.blue + }, + _dynamicColor = null, + ERASE_SEARCH_RADIUS = 0.1, // m + STROKE_DIMENSIONS = { x: 10, y: 10, z: 10 }, + _isDrawingLine = false, + _isTriggerWidthEnabled = _savedSettings.currentTriggerWidthEnabled, + _entityID, + _basePosition, + _strokePoints, + _strokeNormals, + _strokeColors, + _strokeWidths, + _timeOfLastPoint, + _isContinuousLine = _savedSettings.currentIsContinuous, + _lastPosition = null, + _shouldKeepDrawing = false, + _texture = CONTENT_PATH + "/" + _savedSettings.currentTexture.brushName, + _dynamicEffects = _savedSettings.currentDynamicBrushes, + // 'https:// upload.wikimedia.org/wikipedia/commons/thumb/9/93/Caris_Tessellation.svg/1024px-Caris_Tessellation.svg.png', // Daantje + _strokeWidthMultiplier = _savedSettings.currentStrokeWidth * 2 + 0.1, + _isUvModeStretch = _savedSettings.currentTexture.brushType == "stretch", + MIN_STROKE_LENGTH = 0.002, // m + MIN_STROKE_INTERVAL = 66, // ms + MAX_POINTS_PER_LINE = 52; // Quick fix for polyline points disappearing issue. + function calculateStrokeNormal() { + if (!_isMouseDrawing) { + var controllerPose = _isLeftHandDominant + ? getControllerWorldLocation(Controller.Standard.LeftHand, true) + : getControllerWorldLocation(Controller.Standard.RightHand, true); + var fingerTipRotation = controllerPose.rotation; + return Quat.getUp(fingerTipRotation); + + } else { + return Vec3.multiplyQbyV(Camera.getOrientation(), Vec3.UNIT_NEG_Z); + } + + } + + function setStrokeColor(red, green, blue) { + _strokeColor.red = red; + _strokeColor.green = green; + _strokeColor.blue = blue; + } + + function getStrokeColor() { + return _strokeColor; + } + + function calculateValueInRange(value, min, max, increment) { + var delta = max - min; + value += increment; + if (value > max) { + return min; + } else { + return value; + } + } + + function attacthColorToProperties(properties) { + // colored brushes should always be white and no effects should be applied + if (_isBrushColored) { + properties.color = { red: 255, green: 255, blue: 255 }; + return; + } + + var isAnyDynamicEffectEnabled = false; + if ("dynamicHue" in _dynamicEffects && _dynamicEffects.dynamicHue) { + isAnyDynamicEffectEnabled = true; + var hueIncrement = 359.0 / MAX_POINTS_PER_LINE; + _dynamicColor.hue = calculateValueInRange(_dynamicColor.hue, 0, 359, hueIncrement); + } + + if ("dynamicSaturation" in _dynamicEffects && _dynamicEffects.dynamicSaturation) { + isAnyDynamicEffectEnabled = true; + _dynamicColor.saturation = _dynamicColor.saturation == 0.5 ? 1.0 : 0.5; + } + + if ("dynamicValue" in _dynamicEffects && _dynamicEffects.dynamicValue) { + isAnyDynamicEffectEnabled = true; + _dynamicColor.value = _dynamicColor.value == 0.6 ? 1.0 : 0.6; + } + + + if (!isAnyDynamicEffectEnabled) { + properties.color = _strokeColor; + return; + } + + var newRgbColor = hsv2rgb(_dynamicColor); + _strokeColors.push({ + x: newRgbColor.red / 255.0, + y: newRgbColor.green / 255.0, + z: newRgbColor.blue / 255.0 + } + ); + properties.strokeColors = _strokeColors; + } + + function setDynamicEffects(dynamicEffects) { + _dynamicEffects = dynamicEffects; + } + + function isDrawingLine() { + return _isDrawingLine; + } + + function setStrokeWidthMultiplier(strokeWidthMultiplier) { + _strokeWidthMultiplier = strokeWidthMultiplier; + } + + function setIsContinuousLine(isContinuousLine) { + _isContinuousLine = isContinuousLine; + } + + function setIsTriggerWidthEnabled(isTriggerWidthEnabled) { + _isTriggerWidthEnabled = isTriggerWidthEnabled; + } + + function setUVMode(isUvModeStretch) { + _isUvModeStretch = isUvModeStretch; + } + + function getStrokeWidth() { + return _strokeWidthMultiplier; + } + + function getEntityID() { + return _entityID; + } + + function setTexture(texture) { + _texture = texture; + } + + function undo() { + var undo = _undoStack.pop(); + if (_undoStack.length == 0) { + var undoDisableEvent = { type: "undoDisable", value: true }; + tablet.emitScriptEvent(JSON.stringify(undoDisableEvent)); + } + if (undo.type == "deleted") { + + handleDeleteEvent(undo); + + } else if (undo.type == "deleteAll") { + + var recreateList = undo.data; + + recreateList.forEach(function (item) { + handleDeleteEvent(item); + }); + + } else { + Entities.deleteEntity(undo.data); + + // remove from _deleteAllStack + _deleteAllStack = _deleteAllStack.filter(function (deleteStackItem) { + return undo.data !== deleteStackItem.data.id; + }); + + } + + function handleDeleteEvent(undo) { + var prevEntityId = undo.data.id; + var newEntity = Entities.addEntity(undo.data); + // restoring a deleted entity will create a new entity with a new id therefore we need to update + // the created elements id in the undo stack. For the delete elements though, it is not necessary + // to update the id since you can't delete the same entity twice + for (var i = 0; i < _undoStack.length; i++) { + if (_undoStack[i].type == "created" && _undoStack[i].data == prevEntityId) { + _undoStack[i].data = newEntity; + // _deleteAllStack add the new entity into the stack + + var deleteStackItem = { + type: _undoStack[i].type, + data: Entities.getEntityProperties(newEntity) + }; + + _deleteAllStack.push(deleteStackItem); + } + } + } + } + + function calculateLineWidth(width) { + if (_isTriggerWidthEnabled) { + return width * _strokeWidthMultiplier; + } else { + return MAX_LINE_WIDTH * _strokeWidthMultiplier; // MAX_LINE_WIDTH + } + } + + // *** + // + function startLine(position, width) { + // Start drawing a polyline. + if (_isTabletFocused) + return; + + width = calculateLineWidth(width); + + if (_isDrawingLine) { + // print("ERROR: startLine() called when already drawing line"); + // Nevertheless, continue on and start a new line. + } + + // this part made continous drawing not work, not sure what the intention was? + // if (_shouldKeepDrawing) { + // _strokePoints = [Vec3.distance(_basePosition, _strokePoints[_strokePoints.length - 1])]; + // } else { + // _strokePoints = [Vec3.ZERO]; + // } + + _strokePoints = [Vec3.ZERO]; + + _basePosition = position; + _strokeNormals = [calculateStrokeNormal()]; + _strokeColors = []; + _strokeWidths = [width]; + _timeOfLastPoint = Date.now(); + + // *** + if (_paintMode === PAINT_OTHERS) { + _nearestAvatarInfo = getNearestAvatarInfo(position); + } else if (_paintMode === PAINT_SELF) { + _nearestAvatarInfo = AvatarList.getAvatar(MyAvatar.sessionUUID); + } + + if (_nearestAvatarInfo && _nearestAvatarInfo.sessionUUID) { + _avatarID = null; + _nearestJointIndex = null; + _avatarID = _nearestAvatarInfo.sessionUUID; + _nearestJointIndex = findClosestJoint(_nearestAvatarInfo, position); + } + + // *** + // add the parent + // do the joint calculation / check other polylines + var newEntityProperties = { + type: "PolyLine", + name: "bodyPainting", + shapeType: "box", + position: position, + linePoints: _strokePoints, + normals: _strokeNormals, + strokeWidths: _strokeWidths, + textures: _texture, // Daantje + isUVModeStretch: _isUvModeStretch, + dimensions: STROKE_DIMENSIONS, + }; + + _dynamicColor = rgb2hsv(_strokeColor); + attacthColorToProperties(newEntityProperties); + _entityID = Entities.addEntity(newEntityProperties); + _isDrawingLine = true; + addAnimationToBrush(_entityID); + _lastPosition = position; + } + + function drawLine(position, width) { + // Add a stroke to the polyline if stroke is a sufficient length. + var localPosition, + distanceToPrevious, + MAX_DISTANCE_TO_PREVIOUS = 1.0; + + width = calculateLineWidth(width); + + if (!_isDrawingLine) { + // print("ERROR: drawLine() called when not drawing line"); + return; + } + + localPosition = Vec3.subtract(position, _basePosition); + distanceToPrevious = Vec3.distance(localPosition, _strokePoints[_strokePoints.length - 1]); + + if (distanceToPrevious > MAX_DISTANCE_TO_PREVIOUS) { + // Ignore occasional spurious finger tip positions. + return; + } + + if (distanceToPrevious >= MIN_STROKE_LENGTH + && (Date.now() - _timeOfLastPoint) >= MIN_STROKE_INTERVAL + && _strokePoints.length < MAX_POINTS_PER_LINE) { + _strokePoints.push(localPosition); + _strokeNormals.push(calculateStrokeNormal()); + _strokeWidths.push(width); + _timeOfLastPoint = Date.now(); + + var editItemProperties = { + color: _strokeColor, + linePoints: _strokePoints, + normals: _strokeNormals, + strokeWidths: _strokeWidths + }; + attacthColorToProperties(editItemProperties); + Entities.editEntity(_entityID, editItemProperties); + + } else if (_isContinuousLine && _strokePoints.length >= MAX_POINTS_PER_LINE) { + finishLine(position, width); + _shouldKeepDrawing = true; + //print("_isContinuousLine is " + _isContinuousLine + " _shouldKeepDrawing is " + _shouldKeepDrawing); + startLine(_lastPosition, width); + } + _lastPosition = position; + } + + function finishLine(position, width) { + // Finish drawing polyline; delete if it has only 1 point. + var userData = Entities.getEntityProperties(_entityID).userData; + + if (userData && JSON.parse(userData).animations && !_isBrushColored) { + Entities.editEntity(_entityID, { + script: ANIMATION_SCRIPT_PATH + }); + } + + // // Set parent on line end + if (_nearestAvatarInfo && _nearestAvatarInfo.sessionUUID) { + Entities.editEntity(_entityID, { + parentID: _avatarID, + parentJointIndex: _nearestJointIndex + }); + } + + //no need I think + //width = calculateLineWidth(width); + + // clean up for next line + _avatarID = null; + _nearestJointIndex = null; + _nearestAvatarInfo = null; + + if (!_isDrawingLine) { + print("ERROR: finishLine() called when not drawing line"); + return; + } + + if (_strokePoints.length === 1) { + // Delete "empty" line. + Entities.deleteEntity(_entityID); + } + + _isDrawingLine = false; + _shouldKeepDrawing = false; + addElementToUndoStack({ type: "created", data: _entityID }); + } + + function cancelLine() { + // Cancel any line being drawn. + if (_isDrawingLine) { + Entities.deleteEntity(_entityID); + _isDrawingLine = false; + } + } + + function eraseClosestLine(position) { + // Erase closest line that is within search radius of finger tip. + var entities, + entitiesLength, + properties, + i, + pointsLength, + j, + distance, + found = false, + foundID, + foundDistance = ERASE_SEARCH_RADIUS; + + // Find entities with bounding box within search radius. + entities = Entities.findEntities(position, ERASE_SEARCH_RADIUS); + + // Fine polyline entity with closest point within search radius. + for (i = 0, entitiesLength = entities.length; i < entitiesLength; i += 1) { + properties = Entities.getEntityProperties(entities[i], ["type", "position", "linePoints"]); + if (properties.type === "PolyLine") { + _basePosition = properties.position; + for (j = 0, pointsLength = properties.linePoints.length; j < pointsLength; j += 1) { + distance = Vec3.distance(position, Vec3.sum(_basePosition, properties.linePoints[j])); + if (distance <= foundDistance) { + found = true; + foundID = entities[i]; + foundDistance = distance; + } + } + } + } + + // Delete found entity. + if (found) { + addElementToUndoStack({ type: "deleted", data: Entities.getEntityProperties(foundID) }); + Entities.deleteEntity(foundID); + } + } + + function tearDown() { + cancelLine(); + } + + return { + name: name, + startLine: startLine, + drawLine: drawLine, + finishLine: finishLine, + cancelLine: cancelLine, + eraseClosestLine: eraseClosestLine, + tearDown: tearDown, + setStrokeColor: setStrokeColor, + setStrokeWidthMultiplier: setStrokeWidthMultiplier, + setTexture: setTexture, + isDrawingLine: isDrawingLine, + undo: undo, + getStrokeColor: getStrokeColor, + getStrokeWidth: getStrokeWidth, + getEntityID: getEntityID, + setUVMode: setUVMode, + setIsTriggerWidthEnabled: setIsTriggerWidthEnabled, + setDynamicEffects: setDynamicEffects, + setIsContinuousLine: setIsContinuousLine + }; + } + + function handController(name) { + // Translates controller data into application events. + var handName = name, + + triggerPressedCallback, + triggerPressingCallback, + triggerReleasedCallback, + gripPressedCallback, + + rawTriggerValue = 0.0, + triggerValue = 0.0, + isTriggerPressed = false, + TRIGGER_SMOOTH_RATIO = 0.1, + TRIGGER_OFF = 0.05, + TRIGGER_ON = 0.1, + TRIGGER_START_WIDTH_RAMP = 0.11, + TRIGGER_FINISH_WIDTH_RAMP = 1.0, + TRIGGER_RAMP_WIDTH = TRIGGER_FINISH_WIDTH_RAMP - TRIGGER_START_WIDTH_RAMP, + MIN_LINE_WIDTH = 0.005, + RAMP_LINE_WIDTH = MAX_LINE_WIDTH - MIN_LINE_WIDTH, + + rawGripValue = 0.0, + gripValue = 0.0, + isGripPressed = false, + GRIP_SMOOTH_RATIO = 0.1, + GRIP_OFF = 0.05, + GRIP_ON = 0.1; + + function onTriggerPress(value) { + // Controller values are only updated when they change so store latest for use in update. + rawTriggerValue = value; + } + + function updateTriggerPress(value) { + + var LASER_ALPHA = 0.5; + var LASER_TRIGGER_COLOR_XYZW = { x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA }; + var SYSTEM_LASER_DIRECTION = { x: 0, y: 0, z: -1 }; + var LEFT_HUD_LASER = 1; + var RIGHT_HUD_LASER = 2; + var BOTH_HUD_LASERS = LEFT_HUD_LASER + RIGHT_HUD_LASER; + + var wasTriggerPressed, + fingerTipPosition, + lineWidth; + + triggerValue = triggerValue * TRIGGER_SMOOTH_RATIO + rawTriggerValue * (1.0 - TRIGGER_SMOOTH_RATIO); + + wasTriggerPressed = isTriggerPressed; + if (isTriggerPressed) { + isTriggerPressed = triggerValue > TRIGGER_OFF; + } else { + isTriggerPressed = triggerValue > TRIGGER_ON; + } + + if (wasTriggerPressed || isTriggerPressed) { + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); + + if (triggerValue < TRIGGER_START_WIDTH_RAMP) { + lineWidth = MIN_LINE_WIDTH; + } else { + lineWidth = MIN_LINE_WIDTH + + (triggerValue - TRIGGER_START_WIDTH_RAMP) / TRIGGER_RAMP_WIDTH * RAMP_LINE_WIDTH; + } + + if ((handName === "left" && _isLeftHandDominant) || (handName === "right" && !_isLeftHandDominant)) { + if (!wasTriggerPressed && isTriggerPressed) { + // TEST DAANTJE changes to a random color everytime you start a new line + triggerPressedCallback(fingerTipPosition, lineWidth); + } else if (wasTriggerPressed && isTriggerPressed) { + triggerPressingCallback(fingerTipPosition, lineWidth); + } else { + triggerReleasedCallback(fingerTipPosition, lineWidth); + } + } + } + } + + function onGripPress(value) { + // Controller values are only updated when they change so store latest for use in update. + rawGripValue = value; + } + + function updateGripPress() { + var fingerTipPosition; + gripValue = gripValue * GRIP_SMOOTH_RATIO + rawGripValue * (1.0 - GRIP_SMOOTH_RATIO); + + if (isGripPressed) { + isGripPressed = gripValue > GRIP_OFF; + } else { + isGripPressed = gripValue > GRIP_ON; + if (isGripPressed) { + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); + + if ((handName === "left" && _isLeftHandDominant) || (handName === "right" && !_isLeftHandDominant)) { + gripPressedCallback(fingerTipPosition); + } + } + } + } + + function checkTabletHasFocus() { + var controllerPose = _isLeftHandDominant + ? getControllerWorldLocation(Controller.Standard.LeftHand, true) + : getControllerWorldLocation(Controller.Standard.RightHand, true); + + var fingerTipRotation = controllerPose.rotation; + var fingerTipPosition = controllerPose.position; + var pickRay = { + origin: fingerTipPosition, + direction: Quat.getUp(fingerTipRotation) + } + if (_inkSourceOverlay) { + var overlays = Overlays.findRayIntersection(pickRay, false, [HMD.tabletID], [_inkSourceOverlay.overlayID]); + if (overlays.intersects && HMD.tabletID == overlays.overlayID) { + if (!_isTabletFocused) { + _isTabletFocused = true; + Overlays.editOverlay(_inkSourceOverlay, { visible: false }); + updateHandAnimations(); + pauseProcessing(); + } + } else { + if (_isTabletFocused) { + _isTabletFocused = false; + Overlays.editOverlay(_inkSourceOverlay, { visible: true }); + resumeProcessing(); + updateHandFunctions(); + } + }; + } + } + + function onUpdate() { + if (HMD.tabletID + && (((_leftBrush == null || _rightBrush == null) + || (!_leftBrush.isDrawingLine() && !_rightBrush.isDrawingLine())))) { + + checkTabletHasFocus(); + } + + updateTriggerPress(); + updateGripPress(); + } + + function setUp(onTriggerPressed, onTriggerPressing, onTriggerReleased, onGripPressed) { + triggerPressedCallback = onTriggerPressed; + triggerPressingCallback = onTriggerPressing; + triggerReleasedCallback = onTriggerReleased; + gripPressedCallback = onGripPressed; + } + + function tearDown() { + // Nothing to do. + } + + return { + onTriggerPress: onTriggerPress, + onGripPress: onGripPress, + onUpdate: onUpdate, + setUp: setUp, + tearDown: tearDown + }; + } + + function updateHandFunctions() { + // Update other scripts' hand functions. + var enabled = !_isFingerPainting || _isTabletDisplayed; + + Messages.sendMessage(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + holdEnabled: enabled, + nearGrabEnabled: enabled, + farGrabEnabled: enabled + }), true); + + + Messages.sendMessage(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + pointerEnabled: false + }), true); + + Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({ + pointIndex: !enabled + }), true); + } + + function updateHandAnimations() { + if (_leftBrush == null) { + return; + } + var ANIM_URL = (_isLeftHandDominant ? LEFT_ANIM_URL : RIGHT_ANIM_URL); + var ANIM_OPEN = (_isLeftHandDominant ? LEFT_ANIM_URL_OPEN : RIGHT_ANIM_URL_OPEN); + var handLiteral = (_isLeftHandDominant ? "left" : "right"); + + // Clear previous hand animation override + restoreAllHandAnimations(); + + // "rightHandGraspOpen","rightHandGraspClosed", + MyAvatar.overrideRoleAnimation(handLiteral + "HandGraspOpen", ANIM_OPEN, 30, false, 19, 20); + MyAvatar.overrideRoleAnimation(handLiteral + "HandGraspClosed", ANIM_URL, 30, false, 19, 20); + + // "rightIndexPointOpen","rightIndexPointClosed", + MyAvatar.overrideRoleAnimation(handLiteral + "IndexPointOpen", ANIM_OPEN, 30, false, 19, 20); + MyAvatar.overrideRoleAnimation(handLiteral + "IndexPointClosed", ANIM_URL, 30, false, 19, 20); + + // "rightThumbRaiseOpen","rightThumbRaiseClosed", + MyAvatar.overrideRoleAnimation(handLiteral + "ThumbRaiseOpen", ANIM_OPEN, 30, false, 19, 20); + MyAvatar.overrideRoleAnimation(handLiteral + "ThumbRaiseClosed", ANIM_URL, 30, false, 19, 20); + + // "rightIndexPointAndThumbRaiseOpen","rightIndexPointAndThumbRaiseClosed", + MyAvatar.overrideRoleAnimation(handLiteral + "IndexPointAndThumbRaiseOpen", ANIM_OPEN, 30, false, 19, 20); + MyAvatar.overrideRoleAnimation(handLiteral + "IndexPointAndThumbRaiseClosed", ANIM_URL, 30, false, 19, 20); + + // turn off lasers and other interactions + Messages.sendLocalMessage("Hifi-Hand-Disabler", "none"); + Messages.sendLocalMessage("Hifi-Hand-Disabler", handLiteral); + + // update ink Source + + var strokeColor = _leftBrush.getStrokeColor(); + var strokeWidth = _leftBrush.getStrokeWidth() * 0.06; + if (_inkSourceOverlay == null) { + _inkSourceOverlay = Overlays.addOverlay( + "sphere", + { + parentID: MyAvatar.sessionUUID, + parentJointIndex: MyAvatar.getJointIndex(handLiteral === "left" ? "LeftHandIndex4" : "RightHandIndex4"), + localPosition: { x: 0, y: 0, z: 0 }, + size: strokeWidth, + color: strokeColor, + solid: true + }); + } else { + Overlays.editOverlay( + _inkSourceOverlay, + { + parentJointIndex: MyAvatar.getJointIndex(handLiteral === "left" ? "LeftHandIndex4" : "RightHandIndex4"), + localPosition: { x: 0, y: 0, z: 0 }, + size: strokeWidth, + color: strokeColor + }); + } + + } + + function restoreAllHandAnimations() { + // "rightHandGraspOpen","rightHandGraspClosed", + MyAvatar.restoreRoleAnimation("rightHandGraspOpen"); + MyAvatar.restoreRoleAnimation("rightHandGraspClosed"); + + // "rightIndexPointOpen","rightIndexPointClosed", + MyAvatar.restoreRoleAnimation("rightIndexPointOpen"); + MyAvatar.restoreRoleAnimation("rightIndexPointClosed"); + + // "rightThumbRaiseOpen","rightThumbRaiseClosed", + MyAvatar.restoreRoleAnimation("rightThumbRaiseOpen"); + MyAvatar.restoreRoleAnimation("rightThumbRaiseClosed"); + + // "rightIndexPointAndThumbRaiseOpen","rightIndexPointAndThumbRaiseClosed", + MyAvatar.restoreRoleAnimation("rightIndexPointAndThumbRaiseOpen"); + MyAvatar.restoreRoleAnimation("rightIndexPointAndThumbRaiseClosed"); + + // "leftHandGraspOpen","leftHandGraspClosed", + MyAvatar.restoreRoleAnimation("leftHandGraspOpen"); + MyAvatar.restoreRoleAnimation("leftHandGraspClosed"); + + // "leftIndexPointOpen","leftIndexPointClosed", + MyAvatar.restoreRoleAnimation("leftIndexPointOpen"); + MyAvatar.restoreRoleAnimation("leftIndexPointClosed"); + + // "leftThumbRaiseOpen","leftThumbRaiseClosed", + MyAvatar.restoreRoleAnimation("leftThumbRaiseOpen"); + MyAvatar.restoreRoleAnimation("leftThumbRaiseClosed"); + + // "leftIndexPointAndThumbRaiseOpen","leftIndexPointAndThumbRaiseClosed", + MyAvatar.restoreRoleAnimation("leftIndexPointAndThumbRaiseOpen"); + MyAvatar.restoreRoleAnimation("leftIndexPointAndThumbRaiseClosed"); + } + + function pauseProcessing() { + print("INFO: Pause Processing"); + Messages.sendLocalMessage("Hifi-Hand-Disabler", "none"); + Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + // Restores and clears hand animations + restoreAllHandAnimations(); + } + + function resumeProcessing() { + print("INFO: Resume Processing"); + // Change to finger paint hand animation + updateHandAnimations(); + + // Messages channels for enabling/disabling other scripts' functions. + Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + } + + function enableProcessing() { + print("INFO: Star Processing"); + // Connect controller API to handController objects. + _leftHand = handController("left"); + _rightHand = handController("right"); + + if (_savedSettings == null) { + restoreLastValues(); + } + // Connect handController outputs to paintBrush objects. + _leftBrush = paintBrush("left"); + _leftHand.setUp(_leftBrush.startLine, _leftBrush.drawLine, _leftBrush.finishLine, _leftBrush.eraseClosestLine); + _rightBrush = paintBrush("right"); + _rightHand.setUp(_rightBrush.startLine, _rightBrush.drawLine, _rightBrush.finishLine, _rightBrush.eraseClosestLine); + + var controllerMapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); + controllerMapping.from(Controller.Standard.LT).to(_leftHand.onTriggerPress); + controllerMapping.from(Controller.Standard.LeftGrip).to(_leftHand.onGripPress); + controllerMapping.from(Controller.Standard.RT).to(_rightHand.onTriggerPress); + controllerMapping.from(Controller.Standard.RightGrip).to(_rightHand.onGripPress); + Controller.enableMapping(CONTROLLER_MAPPING_NAME); + + // Change to finger paint hand animation + updateHandAnimations(); + + // Messages channels for enabling/disabling other scripts' functions. + Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + + // Update hand controls. + Script.update.connect(_leftHand.onUpdate); + Script.update.connect(_rightHand.onUpdate); + } + + function disableProcessing() { + print("INFO: Disable Processing"); + if (_leftHand && _rightHand) { + Script.update.disconnect(_leftHand.onUpdate); + Script.update.disconnect(_rightHand.onUpdate); + + Controller.disableMapping(CONTROLLER_MAPPING_NAME); + + Messages.sendLocalMessage("Hifi-Hand-Disabler", "none"); + + _leftBrush.tearDown(); + _leftBrush = null; + _leftHand.tearDown(); + _leftHand = null; + + _rightBrush.tearDown(); + _rightBrush = null; + _rightHand.tearDown(); + _rightHand = null; + + Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + + // Restores and clears hand animations + restoreAllHandAnimations(); + + // clears Overlay sphere + Overlays.deleteOverlay(_inkSourceOverlay); + _inkSourceOverlay = null; + } + } + + // Load last fingerpaint settings + function restoreLastValues() { + _savedSettings = new Object(); + _savedSettings.currentColor = Settings.getValue("currentColor", { red: 250, green: 0, blue: 0, origin: "custom" }), + _savedSettings.currentStrokeWidth = Settings.getValue("currentStrokeWidth", 0.25); + _savedSettings.currentTexture = Settings.getValue("currentTexture", { brushID: 0 }); + _savedSettings.currentDrawingHand = Settings.getValue("currentDrawingHand", MyAvatar.getDominantHand() == "left"); + _savedSettings.currentAnimatedBrushes = Settings.getValue("currentAnimatedBrushes", []); + _savedSettings.customColors = Settings.getValue("customColors", []); + _savedSettings.currentTab = Settings.getValue("currentTab", 0); + _savedSettings.currentTriggerWidthEnabled = Settings.getValue("currentTriggerWidthEnabled", true); + _savedSettings.currentDynamicBrushes = Settings.getValue("currentDynamicBrushes", new Object()); + _savedSettings.currentIsContinuous = Settings.getValue("currentIsContinuous", false); + _savedSettings.currentIsBrushColored = Settings.getValue("currentIsBrushColored", false); + _savedSettings.currentHeadersCollapsedStatus = Settings.getValue("currentHeadersCollapsedStatus", new Object()); + _savedSettings.undoDisable = _undoStack.length == 0; + // set some global variables + _isLeftHandDominant = _savedSettings.currentDrawingHand; + _isBrushColored = _savedSettings.currentIsBrushColored; + } + + function onButtonClicked() { + restoreLastValues(); + var wasFingerPainting = _isFingerPainting; + _isFingerPainting = !_isFingerPainting; + + if (!_isFingerPainting) { + tablet.gotoHomeScreen(); + } + button.editProperties({ isActive: _isFingerPainting }); + + if (wasFingerPainting) { + _leftBrush.cancelLine(); + _rightBrush.cancelLine(); + } + + if (_isFingerPainting) { + tablet.gotoWebScreen(APP_URL + "?" + encodeURIComponent(JSON.stringify(_savedSettings))); + HMD.openTablet(); + enableProcessing(); + _savedSettings = null; + } + + updateHandFunctions(); + + if (!_isFingerPainting) { + disableProcessing(); + Controller.mousePressEvent.disconnect(mouseStartLine); + Controller.mouseMoveEvent.disconnect(mouseDrawLine); + Controller.mouseReleaseEvent.disconnect(mouseFinishLine); + } else { + Controller.mousePressEvent.connect(mouseStartLine); + Controller.mouseMoveEvent.connect(mouseDrawLine); + Controller.mouseReleaseEvent.connect(mouseFinishLine); + } + } + + function onTabletShownChanged() { + if (_shouldRestoreTablet && tablet.tabletShown) { + _shouldRestoreTablet = false; + _isTabletFocused = false; + _isFingerPainting = false; + HMD.openTablet(); + onButtonClicked(); + HMD.openTablet(); + } + } + + function onTabletScreenChanged(type, url) { + var TABLET_SCREEN_CLOSED = "Closed"; + var TABLET_SCREEN_HOME = "Home"; + var TABLET_SCREEN_WEB = "Web"; + + _isTabletDisplayed = type !== TABLET_SCREEN_CLOSED; + _isFingerPainting = type === TABLET_SCREEN_WEB && url.indexOf("html/main.html") > -1; + if (!_isFingerPainting) { + disableProcessing(); + updateHandFunctions(); + } + button.editProperties({ isActive: _isFingerPainting }); + } + + + function onWebEventReceived(event) { + + if (typeof event === "string") { + event = JSON.parse(event); + } + + if (_leftBrush == null) { + print("ERROR: Painting not enabled."); + return; + } + + switch (event.type) { + case "appReady": + _isTabletFocused = false; // make sure we can set the focus on the tablet again + break; + + case "reloadPage": + restoreLastValues(); + tablet.gotoWebScreen(APP_URL + "?" + encodeURIComponent(JSON.stringify(_savedSettings))); + break; + + case "changeTab": + Settings.setValue("currentTab", event.currentTab); + break; + + case "changePaintMode": + var option = event.option; + + if (option === PAINT_SELF || option === PAINT_OTHERS) { + _paintMode = option; + } else { + _paintMode = PAINT_WORLD; + } + _paintMode = option; + + break; + + case "deleteAll": + + if (_deleteAllStack.length > 0) { + + // delete each entity inside _deleteAllStack + var deleteList = _deleteAllStack.slice(); + + for (var i = 0; i < _deleteAllStack.length; i++) { + var toDelete = _deleteAllStack[i]; + + Entities.deleteEntity(toDelete.data.id); + } + + // add deleteAll item to undo stack + addElementToUndoStack({ type: "deleteAll", data: deleteList }); + _deleteAllStack = []; + + } + break; + + case "changeColor": + if (_leftBrush != null && _rightBrush != null) { + if (!_isBrushColored) { + var setStrokeColorEvent = { type: "changeStrokeColor", value: event }; + tablet.emitScriptEvent(JSON.stringify(setStrokeColorEvent)); + + Settings.setValue("currentColor", event); + _leftBrush.setStrokeColor(event.red, event.green, event.blue); + _rightBrush.setStrokeColor(event.red, event.green, event.blue); + + Overlays.editOverlay(_inkSourceOverlay, { + color: { red: event.red, green: event.green, blue: event.blue } + }); + } + } + break; + + case "switchTriggerPressureWidth": + if (_leftBrush != null && _rightBrush != null) { + Settings.setValue("currentTriggerWidthEnabled", event.enabled); + _leftBrush.setIsTriggerWidthEnabled(event.enabled); + _rightBrush.setIsTriggerWidthEnabled(event.enabled); + } + break; + + case "addCustomColor": + var customColors = Settings.getValue("customColors", []); + customColors.push({ red: event.red, green: event.green, blue: event.blue }); + if (customColors.length > event.maxColors) { + customColors.splice(0, 1); // remove first color + } + Settings.setValue("customColors", customColors); + break; + + case "switchCollapsed": + var collapsedStatus = Settings.getValue("currentHeadersCollapsedStatus", new Object()); + collapsedStatus[event.sectionId] = event.isCollapsed; + Settings.setValue("currentHeadersCollapsedStatus", collapsedStatus); + break; + + case "changeBrush": + if (_leftBrush != null && _rightBrush != null) { + Settings.setValue("currentTexture", event); + Settings.setValue("currentIsBrushColored", event.isColored); + + if (event.brushType === "repeat") { + _leftBrush.setUVMode(false); + _rightBrush.setUVMode(false); + + } else if (event.brushType === "stretch") { + _leftBrush.setUVMode(true); + _rightBrush.setUVMode(true); + } + + _isBrushColored = event.isColored; + event.brushName = Script.resolvePath(event.brushName); + _leftBrush.setTexture(event.brushName); + _rightBrush.setTexture(event.brushName); + } + break; + + case "changeLineWidth": + if (_leftBrush != null && _rightBrush != null) { + Settings.setValue("currentStrokeWidth", event.brushWidth); + var dim = event.brushWidth * 2 + 0.1; + _leftBrush.setStrokeWidthMultiplier(dim); + _rightBrush.setStrokeWidthMultiplier(dim); + Overlays.editOverlay(_inkSourceOverlay, { + size: dim * 0.06 + }); + } + break; + + case "undo": + // The undo is called only on the right brush because the undo stack is global, meaning that + // calling undo on both the left and right brush would cause the stack to pop twice. + // Using the leftBrush instead of the rightBrush would have the exact same effect. + if (_leftBrush != null && _rightBrush != null) { + _rightBrush.undo(); + } + break; + + case "changeBrushHand": + Settings.setValue("currentDrawingHand", event.DrawingHand == "left"); + _isLeftHandDominant = event.DrawingHand == "left"; + _isTabletFocused = false; + updateHandAnimations(); + break; + + case "switchAnimatedBrush": + var animatedBrushes = Settings.getValue("currentAnimatedBrushes", []); + var brushSettingsIndex = animatedBrushes.indexOf(event.animatedBrushID); + if (!event.enabled && brushSettingsIndex > -1) { // already exists so we are disabling it + animatedBrushes.splice(brushSettingsIndex, 1); + } else if (event.enabled && brushSettingsIndex == -1) { // doesn't exist yet so we are just adding it + animatedBrushes.push(event.animatedBrushID); + } + Settings.setValue("currentAnimatedBrushes", animatedBrushes); + print("Setting animated brushes: " + JSON.stringify(animatedBrushes)); + AnimatedBrushesInfo[event.animatedBrushName].isEnabled = event.enabled; + AnimatedBrushesInfo[event.animatedBrushName].settings = event.settings; + break; + + case "switchDynamicBrush": + var dynamicBrushes = Settings.getValue("currentDynamicBrushes", new Object()); + dynamicBrushes[event.dynamicBrushId] = event.enabled; + Settings.setValue("currentDynamicBrushes", dynamicBrushes); + _rightBrush.setDynamicEffects(dynamicBrushes); + _leftBrush.setDynamicEffects(dynamicBrushes); + break; + + case "switchIsContinuous": + Settings.setValue("currentIsContinuous", event.enabled); + _rightBrush.setIsContinuousLine(event.enabled); + _leftBrush.setIsContinuousLine(event.enabled); + break; + + default: + break; + } + } + + function addAnimationToBrush(entityID) { + Object.keys(AnimatedBrushesInfo).forEach(function (animationName) { + if (AnimatedBrushesInfo[animationName].isEnabled) { + var prevUserData = Entities.getEntityProperties(entityID).userData; + // preserve other possible user data + prevUserData = prevUserData == "" ? new Object() : JSON.parse(prevUserData); + if (prevUserData.animations == null) { + prevUserData.animations = {}; + } + + prevUserData.animations[animationName] = animatedBrushFactory(animationName, + AnimatedBrushesInfo[animationName].settings, entityID); + + Entities.editEntity(entityID, { userData: JSON.stringify(prevUserData) }); + } + }); + } + + function addElementToUndoStack(item) { + var undoDisableEvent = { type: "undoDisable", value: false }; + tablet.emitScriptEvent(JSON.stringify(undoDisableEvent)); + + // no limit to the undo stack size + // if (_undoStack.length + 1 > UNDO_STACK_SIZE) { + // _undoStack.splice(0, 1); + // } + if (item.type == "created") { + + var deleteAllStackItem = { + type: item.type, + data: Entities.getEntityProperties(item.data) + }; + + _deleteAllStack.push(deleteAllStackItem); + + } else if (item.type == "deleted") { + + _deleteAllStack = _deleteAllStack.filter(function (deleteStackItem) { + return item.data.id !== deleteStackItem.data.id; + }); + + } + + _undoStack.push(item); + } + + function onHmdChanged(isHMDActive) { + var wasHMDActive = Settings.getValue("wasHMDActive", null); + if (isHMDActive != wasHMDActive) { + Settings.setValue("wasHMDActive", isHMDActive); + if (wasHMDActive == null) { + return; + } else { + if (_isFingerPainting) { + _shouldRestoreTablet = true; + + // Make sure the tablet is being shown when we try to change the window + while (!tablet.tabletShown) { + HMD.openTablet(); + } + } + } + } + } + + function setUp() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if (!tablet) { + return; + } + tablet.webEventReceived.connect(onWebEventReceived); + // Tablet button. + button = tablet.addButton({ + icon: ICON_URL, + activeIcon: ACTIVE_ICON_URL, + text: BUTTON_NAME, + isActive: _isFingerPainting + }); + + button.clicked.connect(onButtonClicked); + // Track whether tablet is displayed or not. + tablet.screenChanged.connect(onTabletScreenChanged); + tablet.tabletShownChanged.connect(onTabletShownChanged); + HMD.displayModeChanged.connect(onHmdChanged); + } + + function tearDown() { + if (!tablet) { + return; + } + + if (_isFingerPainting) { + _isFingerPainting = false; + updateHandFunctions(); + disableProcessing(); + } + + tablet.screenChanged.disconnect(onTabletScreenChanged); + tablet.tabletShownChanged.disconnect(onTabletShownChanged); + button.clicked.disconnect(onButtonClicked); + tablet.removeButton(button); + } + + function getFingerPosition(x, y) { + var pickRay = Camera.computePickRay(x, y); + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, 5)); + } + + function mouseDrawLine(event) { + if (_rightBrush && _rightBrush.isDrawingLine()) { + _rightBrush.drawLine(getFingerPosition(event.x, event.y), MAX_LINE_WIDTH); + } + } + + function mouseStartLine(event) { + if (event.isLeftButton && _rightBrush && !_rightBrush.isDrawingLine()) { + _isMouseDrawing = true; + _rightBrush.startLine(getFingerPosition(event.x, event.y), MAX_LINE_WIDTH); + } + } + + function mouseFinishLine(event) { + _isMouseDrawing = false; + if (_rightBrush && _rightBrush.isDrawingLine()) { + _rightBrush.finishLine(getFingerPosition(event.x, event.y), MAX_LINE_WIDTH); + } + } + + setUp(); + Script.scriptEnding.connect(tearDown); +}()); \ No newline at end of file diff --git a/applications/BodyPaint4/content/animatedBrushes/animatedBrush.js b/applications/BodyPaint4/content/animatedBrushes/animatedBrush.js new file mode 100644 index 0000000..d8df6ab --- /dev/null +++ b/applications/BodyPaint4/content/animatedBrushes/animatedBrush.js @@ -0,0 +1,37 @@ +//This file is just used for the superclass +function AnimatedBrushClass() {} + +/** + * Called on every frame draw after the user stops painting + * + * @abstract + * @param deltaSeconds, the number of seconds past since the last frame was rendered + * @param entityID: the id of the polyline being drawn + * @param fingerOverlayID: the id of the overlay that shows over the finger when using fingerpaint + */ +AnimatedBrushClass.prototype.onUpdate = function(deltaSeconds, entityID) { + //To be implemented on the child + throw "Abstract method onUpdate not implemented"; +} + +/** + * This function updates the user data so in the next frame the animation gets the previous values.\ + * + * @param entityID: the id of the polyline being animated + * @param animatedBrushObject: the animation object (should be a subclass of animatedBrush) + */ +AnimatedBrushClass.prototype.updateUserData = function(entityID, animatedBrushObject) { + var prevUserData = Entities.getEntityProperties(entityID).userData; + + if (prevUserData) { + prevUserData = prevUserData == "" ? new Object() : JSON.parse(prevUserData); //preserve other possible user data + if (prevUserData.animations != null && prevUserData.animations[animatedBrushObject.NAME] != null) { + delete prevUserData.animations[animatedBrushObject.NAME]; + prevUserData.animations[animatedBrushObject.NAME] = animatedBrushObject; + } + prevUserData.timeFromLastAnimation = Date.now(); + Entities.editEntity(entityID, {userData: JSON.stringify(prevUserData)}); + } +} + +AnimatedBrush = AnimatedBrushClass; diff --git a/applications/BodyPaint4/content/animatedBrushes/animatedBrushScript.js b/applications/BodyPaint4/content/animatedBrushes/animatedBrushScript.js new file mode 100644 index 0000000..f094cc8 --- /dev/null +++ b/applications/BodyPaint4/content/animatedBrushes/animatedBrushScript.js @@ -0,0 +1,37 @@ +(function() { + Script.include("animatedBrushesList.js?v1"); + var UPDATE_TIME = 33; //run at aproximatelly 30fps + var MIN_PLAY_DISTANCE = 6; //Minimum distance from player to entity in order to play animation + var self = this; + this.preload = function(entityID) { + print("After adding script 2 : " + JSON.stringify(Entities.getEntityProperties(entityID).color)); + self.intervalID = Script.setInterval(function() { + if (MyAvatar.sessionUUID != Entities.getEntityProperties(entityID).lastEditedBy) { + Script.clearInterval(self.intervalID); + return; + } + + if (Vec3.withinEpsilon(MyAvatar.position, Entities.getEntityProperties(entityID).position, MIN_PLAY_DISTANCE)) { + var userData = Entities.getEntityProperties(entityID).userData; + if (userData) { + var userDataObject = JSON.parse(userData); + var animationObject = userDataObject.animations; + var newAnimationObject = null; + if (!userDataObject.timeFromLastAnimation) { + userDataObject.timeFromLastAnimation = Date.now(); + } + Object.keys(animationObject).forEach(function(animationName) { + newAnimationObject = animationObject[animationName]; + //print("Proto 0001: " + JSON.stringify(newAnimationObject)); + newAnimationObject.__proto__ = AnimatedBrushesInfo[animationName].proto; + //print("time from last draw " + (Date.now() - userDataObject.animations.timeFromLastDraw)); + newAnimationObject.onUpdate(Date.now() - userDataObject.timeFromLastAnimation, entityID); + }); + } + } + }, UPDATE_TIME); + }; + this.unload = function() { + Script.clearInterval(self.intervalID); + } +}); diff --git a/applications/BodyPaint4/content/animatedBrushes/animatedBrushesList.js b/applications/BodyPaint4/content/animatedBrushes/animatedBrushesList.js new file mode 100644 index 0000000..55c8292 --- /dev/null +++ b/applications/BodyPaint4/content/animatedBrushes/animatedBrushesList.js @@ -0,0 +1,36 @@ +Script.include("animatedHueBrush.js?v1"); +Script.include("animatedRotationBrush.js?v1"); +Script.include("animatedTranslationBrush.js?v1"); + +AnimatedBrushesInfo = { + animatedHueBrush: { + isEnabled: false, + proto: AnimatedHueBrush.prototype, + settings: null, + }, + + animatedRotationBrush: { + isEnabled: false, + proto: AnimatedRotationBrush.prototype, + settings: null, + }, + + animatedTranslationBrush: { + isEnabled: false, + proto: AnimatedTranslationBrush.prototype, + settings: null, + }, +} + +animatedBrushFactory = function(animatedBrushName, settings, entityID) { + switch (animatedBrushName) { + case "animatedHueBrush": + return new AnimatedHueBrush(settings, entityID); + case "animatedRotationBrush": + return new AnimatedRotationBrush(settings, entityID); + case "animatedTranslationBrush": + return new AnimatedTranslationBrush(settings, entityID); + default: + throw new Error("Invalid Brush Name. Could not instantiate " + animatedBrushName); + } +} \ No newline at end of file diff --git a/applications/BodyPaint4/content/animatedBrushes/animatedHueBrush.js b/applications/BodyPaint4/content/animatedBrushes/animatedHueBrush.js new file mode 100644 index 0000000..8594570 --- /dev/null +++ b/applications/BodyPaint4/content/animatedBrushes/animatedHueBrush.js @@ -0,0 +1,27 @@ +//Superclass +Script.include("animatedBrush.js"); +Script.include("../js/ColorUtils.js"); + +function AnimatedHueBrushClass(settings, entityID) { + AnimatedBrush.call(this); + this.hsvColor = rgb2hsv(Entities.getEntityProperties(entityID).color);// {hue: 0, saturation: 1.0, value: 1.0}; + this.animatedColor = {red: 0, green: 0, blue: 0}; +} + +AnimatedHueBrushClass.prototype.ANIMATED_BRUSH_TIME = 10; //inteval in milliseconds to update the brush width; +AnimatedHueBrushClass.prototype.ANIMATED_BRUSH_INCREMENT = 0.5; //linear increment of brush size; +AnimatedHueBrushClass.prototype.NAME = "animatedHueBrush"; + +AnimatedHueBrushClass.prototype.onUpdate = function(deltaSeconds, entityID) { + print( "My animation brushes are updating "); + this.hsvColor.hue = this.hsvColor.hue + ((deltaSeconds * this.ANIMATED_BRUSH_INCREMENT)/this.ANIMATED_BRUSH_TIME); + this.hsvColor.hue = this.hsvColor.hue >= 360 ? 0 : this.hsvColor.hue; //restart hue cycle + this.animatedColor = hsv2rgb(this.hsvColor); + Entities.editEntity(entityID, { color : this.animatedColor}); + this.parent.updateUserData(entityID, this); +} + +AnimatedHueBrushClass.prototype.constructor = AnimatedHueBrushClass; +AnimatedHueBrushClass.prototype.parent = AnimatedBrush.prototype; + +AnimatedHueBrush = AnimatedHueBrushClass; \ No newline at end of file diff --git a/applications/BodyPaint4/content/animatedBrushes/animatedRotationBrush.js b/applications/BodyPaint4/content/animatedBrushes/animatedRotationBrush.js new file mode 100644 index 0000000..413d21f --- /dev/null +++ b/applications/BodyPaint4/content/animatedBrushes/animatedRotationBrush.js @@ -0,0 +1,25 @@ +//Superclass +Script.include("animatedBrush.js"); + +function AnimatedRotationBrushClass(settings, entityID) { + AnimatedBrush.call(this); + this.angle = 0; + this.activeAxis = settings.axis; +} + +AnimatedRotationBrushClass.prototype.constructor = AnimatedRotationBrushClass; +AnimatedRotationBrushClass.prototype.parent = AnimatedBrush.prototype; + +AnimatedRotationBrushClass.prototype.ANIMATED_BRUSH_TIME = 100; //inteval in milliseconds to update the entity rotation; +AnimatedRotationBrushClass.prototype.ANIMATED_BRUSH_INCREMENT = 5; //linear increment of brush size; +AnimatedRotationBrushClass.prototype.NAME = "animatedRotationBrush"; //linear increment of brush size; + +AnimatedRotationBrushClass.prototype.onUpdate = function(deltaSeconds, entityID) { + this.angle = this.angle + ((deltaSeconds * this.ANIMATED_BRUSH_INCREMENT)/this.ANIMATED_BRUSH_TIME); + this.angle = this.angle >= 360 ? 0 : this.angle; //restart hue cycle + var rotation = Vec3.multiply(this.angle, this.activeAxis); + Entities.editEntity(entityID, {rotation : Quat.fromPitchYawRollDegrees(rotation.x, rotation.y, rotation.z)}); + this.parent.updateUserData(entityID, this); +} + +AnimatedRotationBrush = AnimatedRotationBrushClass; \ No newline at end of file diff --git a/applications/BodyPaint4/content/animatedBrushes/animatedTranslationBrush.js b/applications/BodyPaint4/content/animatedBrushes/animatedTranslationBrush.js new file mode 100644 index 0000000..4649766 --- /dev/null +++ b/applications/BodyPaint4/content/animatedBrushes/animatedTranslationBrush.js @@ -0,0 +1,41 @@ +//Superclass +Script.include("animatedBrush.js"); + +function AnimatedTranslationBrushClass(settings, entityID) { + AnimatedBrush.call(this); + this.startingPosition = null; + this.translation = 0; + this.activeAxis = settings.axis; +} + +AnimatedTranslationBrushClass.prototype.constructor = AnimatedTranslationBrushClass; +AnimatedTranslationBrushClass.prototype.parent = AnimatedBrush.prototype; + +AnimatedTranslationBrushClass.prototype.ANIMATED_BRUSH_TIME = 10; //inteval in milliseconds to update the brush width; +AnimatedTranslationBrushClass.prototype.ANIMATED_BRUSH_INCREMENT = 0.005; //linear increment of brush size; +AnimatedTranslationBrushClass.prototype.MAX_TRANSLATION = 2; +AnimatedTranslationBrushClass.prototype.NAME = "animatedTranslationBrush"; //linear increment of brush size; + +AnimatedTranslationBrushClass.prototype.onUpdate = function(deltaSeconds, entityID) { + var currentPosition = Entities.getEntityProperties(entityID).position; + if (this.startingPosition == null) { + this.startingPosition = currentPosition; + } + this.translation = this.translation + ((deltaSeconds * this.ANIMATED_BRUSH_INCREMENT)/this.ANIMATED_BRUSH_TIME); + + var translationVec = Vec3.multiply(this.translation, this.activeAxis); + var nextPosition = { + x: this.startingPosition.x + translationVec.x, + y: this.startingPosition.y + translationVec.y, + z: this.startingPosition.z + translationVec.z + }; + + if (Vec3.distance(nextPosition, this.startingPosition) > this.MAX_TRANSLATION) { + this.translation = 0; + nextPosition = this.startingPosition; + } + Entities.editEntity(entityID, {position : nextPosition}); + this.parent.updateUserData(entityID, this); +} + +AnimatedTranslationBrush = AnimatedTranslationBrushClass; \ No newline at end of file diff --git a/applications/BodyPaint4/content/appicons/body-paint-a.svg b/applications/BodyPaint4/content/appicons/body-paint-a.svg new file mode 100644 index 0000000..0113a9f --- /dev/null +++ b/applications/BodyPaint4/content/appicons/body-paint-a.svg @@ -0,0 +1,75 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/applications/BodyPaint4/content/appicons/body-paint-i.svg b/applications/BodyPaint4/content/appicons/body-paint-i.svg new file mode 100644 index 0000000..1ba62ef --- /dev/null +++ b/applications/BodyPaint4/content/appicons/body-paint-i.svg @@ -0,0 +1,74 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/applications/BodyPaint4/content/brushes/barbWire.png b/applications/BodyPaint4/content/brushes/barbWire.png new file mode 100644 index 0000000..367b2f7 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/barbWire.png differ diff --git a/applications/BodyPaint4/content/brushes/basic.png b/applications/BodyPaint4/content/brushes/basic.png new file mode 100644 index 0000000..d632abd Binary files /dev/null and b/applications/BodyPaint4/content/brushes/basic.png differ diff --git a/applications/BodyPaint4/content/brushes/breakfast.png b/applications/BodyPaint4/content/brushes/breakfast.png new file mode 100644 index 0000000..566957a Binary files /dev/null and b/applications/BodyPaint4/content/brushes/breakfast.png differ diff --git a/applications/BodyPaint4/content/brushes/bristle.png b/applications/BodyPaint4/content/brushes/bristle.png new file mode 100644 index 0000000..8c74fb9 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/bristle.png differ diff --git a/applications/BodyPaint4/content/brushes/dot.png b/applications/BodyPaint4/content/brushes/dot.png new file mode 100644 index 0000000..adc05e1 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/dot.png differ diff --git a/applications/BodyPaint4/content/brushes/dupuiz.png b/applications/BodyPaint4/content/brushes/dupuiz.png new file mode 100644 index 0000000..b247da6 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/dupuiz.png differ diff --git a/applications/BodyPaint4/content/brushes/flowers2.png b/applications/BodyPaint4/content/brushes/flowers2.png new file mode 100644 index 0000000..f43f9e2 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/flowers2.png differ diff --git a/applications/BodyPaint4/content/brushes/gouache.png b/applications/BodyPaint4/content/brushes/gouache.png new file mode 100644 index 0000000..f48c107 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/gouache.png differ diff --git a/applications/BodyPaint4/content/brushes/gouacheB.png b/applications/BodyPaint4/content/brushes/gouacheB.png new file mode 100644 index 0000000..02a64f1 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/gouacheB.png differ diff --git a/applications/BodyPaint4/content/brushes/gradient2.png b/applications/BodyPaint4/content/brushes/gradient2.png new file mode 100644 index 0000000..9afeef9 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/gradient2.png differ diff --git a/applications/BodyPaint4/content/brushes/graphite.png b/applications/BodyPaint4/content/brushes/graphite.png new file mode 100644 index 0000000..e2716fe Binary files /dev/null and b/applications/BodyPaint4/content/brushes/graphite.png differ diff --git a/applications/BodyPaint4/content/brushes/heart.png b/applications/BodyPaint4/content/brushes/heart.png new file mode 100644 index 0000000..2d15dd5 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/heart.png differ diff --git a/applications/BodyPaint4/content/brushes/hearts.png b/applications/BodyPaint4/content/brushes/hearts.png new file mode 100644 index 0000000..0a4d293 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/hearts.png differ diff --git a/applications/BodyPaint4/content/brushes/koons.png b/applications/BodyPaint4/content/brushes/koons.png new file mode 100644 index 0000000..c5b4e32 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/koons.png differ diff --git a/applications/BodyPaint4/content/brushes/leaves.png b/applications/BodyPaint4/content/brushes/leaves.png new file mode 100644 index 0000000..b2eef17 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/leaves.png differ diff --git a/applications/BodyPaint4/content/brushes/newton.png b/applications/BodyPaint4/content/brushes/newton.png new file mode 100644 index 0000000..02e0a01 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/newton.png differ diff --git a/applications/BodyPaint4/content/brushes/oil.png b/applications/BodyPaint4/content/brushes/oil.png new file mode 100644 index 0000000..3109159 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/oil.png differ diff --git a/applications/BodyPaint4/content/brushes/paintbrush1.png b/applications/BodyPaint4/content/brushes/paintbrush1.png new file mode 100644 index 0000000..f0a1265 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/paintbrush1.png differ diff --git a/applications/BodyPaint4/content/brushes/paintbrush6.png b/applications/BodyPaint4/content/brushes/paintbrush6.png new file mode 100644 index 0000000..14d44ed Binary files /dev/null and b/applications/BodyPaint4/content/brushes/paintbrush6.png differ diff --git a/applications/BodyPaint4/content/brushes/paw-print.png b/applications/BodyPaint4/content/brushes/paw-print.png new file mode 100644 index 0000000..dc043d7 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/paw-print.png differ diff --git a/applications/BodyPaint4/content/brushes/polka.png b/applications/BodyPaint4/content/brushes/polka.png new file mode 100644 index 0000000..29013fd Binary files /dev/null and b/applications/BodyPaint4/content/brushes/polka.png differ diff --git a/applications/BodyPaint4/content/brushes/snowflakes2.png b/applications/BodyPaint4/content/brushes/snowflakes2.png new file mode 100644 index 0000000..0bcf7eb Binary files /dev/null and b/applications/BodyPaint4/content/brushes/snowflakes2.png differ diff --git a/applications/BodyPaint4/content/brushes/soft.png b/applications/BodyPaint4/content/brushes/soft.png new file mode 100644 index 0000000..aff1603 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/soft.png differ diff --git a/applications/BodyPaint4/content/brushes/softy.png b/applications/BodyPaint4/content/brushes/softy.png new file mode 100644 index 0000000..41e71e8 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/softy.png differ diff --git a/applications/BodyPaint4/content/brushes/spat-fine.png b/applications/BodyPaint4/content/brushes/spat-fine.png new file mode 100644 index 0000000..518f391 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/spat-fine.png differ diff --git a/applications/BodyPaint4/content/brushes/sponge.png b/applications/BodyPaint4/content/brushes/sponge.png new file mode 100644 index 0000000..852c35d Binary files /dev/null and b/applications/BodyPaint4/content/brushes/sponge.png differ diff --git a/applications/BodyPaint4/content/brushes/spongeB.png b/applications/BodyPaint4/content/brushes/spongeB.png new file mode 100644 index 0000000..3f01450 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/spongeB.png differ diff --git a/applications/BodyPaint4/content/brushes/square.png b/applications/BodyPaint4/content/brushes/square.png new file mode 100644 index 0000000..e65d333 Binary files /dev/null and b/applications/BodyPaint4/content/brushes/square.png differ diff --git a/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.eot b/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.eot new file mode 100644 index 0000000..93f4bc8 Binary files /dev/null and b/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.eot differ diff --git a/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.svg b/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.svg new file mode 100644 index 0000000..4f1fb2e --- /dev/null +++ b/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.svg @@ -0,0 +1,157 @@ + + + +Generated by Fontastic.me + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.ttf b/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.ttf new file mode 100644 index 0000000..e85c193 Binary files /dev/null and b/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.ttf differ diff --git a/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.woff b/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.woff new file mode 100644 index 0000000..534f8e5 Binary files /dev/null and b/applications/BodyPaint4/content/glyphs/fonts/hifi-glyphs.woff differ diff --git a/applications/BodyPaint4/content/glyphs/icons-reference.html b/applications/BodyPaint4/content/glyphs/icons-reference.html new file mode 100644 index 0000000..a6ffbd5 --- /dev/null +++ b/applications/BodyPaint4/content/glyphs/icons-reference.html @@ -0,0 +1,1216 @@ + + + + + + + Font Reference - HiFi Glyphs + + + + + +
+

HiFi Glyphs

+

This font was created for use in High Fidelity

+

CSS mapping

+ +

Character mapping

+ +
+ + + diff --git a/applications/BodyPaint4/content/glyphs/styles.css b/applications/BodyPaint4/content/glyphs/styles.css new file mode 100644 index 0000000..06c14b1 --- /dev/null +++ b/applications/BodyPaint4/content/glyphs/styles.css @@ -0,0 +1,481 @@ +@charset "UTF-8"; + +@font-face { + font-family: "hifi-glyphs"; + src:url("fonts/hifi-glyphs.eot"); + src:url("fonts/hifi-glyphs.eot?#iefix") format("embedded-opentype"), + url("fonts/hifi-glyphs.woff") format("woff"), + url("fonts/hifi-glyphs.ttf") format("truetype"), + url("fonts/hifi-glyphs.svg#hifi-glyphs") format("svg"); + font-weight: normal; + font-style: normal; + +} + +[data-icon]:before { + font-family: "hifi-glyphs" !important; + content: attr(data-icon); + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +[class^="icon-"]:before, +[class*=" icon-"]:before { + font-family: "hifi-glyphs" !important; + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-hmd:before { + content: "\62"; +} +.icon-2d-screen:before { + content: "\63"; +} +.icon-keyboard:before { + content: "\64"; +} +.icon-hand-controllers:before { + content: "\65"; +} +.icon-headphones-mic:before { + content: "\66"; +} +.icon-gamepad:before { + content: "\67"; +} +.icon-headphones:before { + content: "\68"; +} +.icon-mic:before { + content: "\69"; +} +.icon-upload:before { + content: "\6a"; +} +.icon-script:before { + content: "\6b"; +} +.icon-text:before { + content: "\6c"; +} +.icon-cube:before { + content: "\6d"; +} +.icon-sphere:before { + content: "\6e"; +} +.icon-zone:before { + content: "\6f"; +} +.icon-light:before { + content: "\70"; +} +.icon-web:before { + content: "\71"; +} +.icon-web-2:before { + content: "\72"; +} +.icon-edit:before { + content: "\73"; +} +.icon-directory:before { + content: "\75"; +} +.icon-menu:before { + content: "\76"; +} +.icon-close:before { + content: "\77"; +} +.icon-close-inverted:before { + content: "\78"; +} +.icon-pin:before { + content: "\79"; +} +.icon-pin-inverted:before { + content: "\7a"; +} +.icon-resize-handle:before { + content: "\41"; +} +.icon-diclosure-expand:before { + content: "\42"; +} +.icon-reload-small:before { + content: "\61"; +} +.icon-close-small:before { + content: "\43"; +} +.icon-minimize:before { + content: "\49"; +} +.icon-maximize:before { + content: "\4a"; +} +.icon-maximize-inverted:before { + content: "\4b"; +} +.icon-disclosure-button-expand:before { + content: "\4c"; +} +.icon-disclosure-button-collapse:before { + content: "\4d"; +} +.icon-script-stop:before { + content: "\4e"; +} +.icon-script-reload:before { + content: "\4f"; +} +.icon-script-run:before { + content: "\50"; +} +.icon-script-new:before { + content: "\51"; +} +.icon-hifi-forum:before { + content: "\32"; +} +.icon-hifi-logo-small:before { + content: "\53"; +} +.icon-placemark:before { + content: "\55"; +} +.icon-box:before { + content: "\56"; +} +.icon-community:before { + content: "\30"; +} +.icon-grab-handle:before { + content: "\58"; +} +.icon-search:before { + content: "\59"; +} +.icon-disclosure-collapse:before { + content: "\5a"; +} +.icon-script-upload:before { + content: "\52"; +} +.icon-code:before { + content: "\57"; +} +.icon-avatar:before { + content: "\3c"; +} +.icon-arrows-h:before { + content: "\3a"; +} +.icon-arrows-v:before { + content: "\3b"; +} +.icon-arrows:before { + content: "\60"; +} +.icon-compress:before { + content: "\21"; +} +.icon-expand:before { + content: "\22"; +} +.icon-placemark-1:before { + content: "\23"; +} +.icon-circle:before { + content: "\24"; +} +.icon-hand-pointer:before { + content: "\39"; +} +.icon-plus-square-o:before { + content: "\25"; +} +.icon-square:before { + content: "\27"; +} +.icon-align-center:before { + content: "\38"; +} +.icon-align-justify:before { + content: "\29"; +} +.icon-align-left:before { + content: "\2a"; +} +.icon-align-right:before { + content: "\5e"; +} +.icon-bars:before { + content: "\37"; +} +.icon-circle-slash:before { + content: "\2c"; +} +.icon-sync:before { + content: "\28"; +} +.icon-key:before { + content: "\2d"; +} +.icon-link:before { + content: "\2e"; +} +.icon-location:before { + content: "\2f"; +} +.icon-carat-r:before { + content: "\33"; +} +.icon-carat-l:before { + content: "\34"; +} +.icon-folder-lg:before { + content: "\3e"; +} +.icon-folder-sm:before { + content: "\3f"; +} +.icon-level-up:before { + content: "\31"; +} +.icon-info:before { + content: "\5b"; +} +.icon-question:before { + content: "\5d"; +} +.icon-alert:before { + content: "\2b"; +} +.icon-home:before { + content: "\5f"; +} +.icon-error:before { + content: "\3d"; +} +.icon-settings:before { + content: "\40"; +} +.icon-trash:before { + content: "\7b"; +} +.icon-object-group:before { + content: "\e000"; +} +.icon-cm:before { + content: "\7d"; +} +.icon-msvg:before { + content: "\7e"; +} +.icon-deg:before { + content: "\5c"; +} +.icon-px:before { + content: "\7c"; +} +.icon-m-sq:before { + content: "\e001"; +} +.icon-m-cubed:before { + content: "\e002"; +} +.icon-acceleration:before { + content: "\e003"; +} +.icon-particles:before { + content: "\e004"; +} +.icon-voxels:before { + content: "\e005"; +} +.icon-lock:before { + content: "\e006"; +} +.icon-visible:before { + content: "\e007"; +} +.icon-model:before { + content: "\e008"; +} +.icon-avatar-2:before { + content: "\e009"; +} +.icon-arrow-dn:before { + content: "\35"; +} +.icon-arrow-up:before { + content: "\36"; +} +.icon-time:before { + content: "\e00a"; +} +.icon-transparency:before { + content: "\e00b"; +} +.icon-unmuted:before { + content: "\47"; +} +.icon-user:before { + content: "\e00c"; +} +.icon-edit-pencil:before { + content: "\e00d"; +} +.icon-muted:before { + content: "\48"; +} +.icon-vol-0:before { + content: "\e00e"; +} +.icon-vol-1:before { + content: "\e00f"; +} +.icon-vol-2:before { + content: "\e010"; +} +.icon-vol-3:before { + content: "\e011"; +} +.icon-vol-4:before { + content: "\e012"; +} +.icon-vol-x-0:before { + content: "\e013"; +} +.icon-vol-x-1:before { + content: "\e014"; +} +.icon-vol-x-2:before { + content: "\e015"; +} +.icon-vol-x-3:before { + content: "\e016"; +} +.icon-vol-x-4:before { + content: "\e017"; +} +.icon-share-ext:before { + content: "\e018"; +} +.icon-ellipsis:before { + content: "\e019"; +} +.icon-check:before { + content: "\e01a"; +} +.icon-sliders:before { + content: "\26"; +} +.icon-polyline:before { + content: "\e01b"; +} +.icon-source:before { + content: "\e01c"; +} +.icon-playback-play:before { + content: "\e01d"; +} +.icon-stop-square:before { + content: "\e01e"; +} +.icon-avatar-t-pose:before { + content: "\e01f"; +} +.icon-check-1:before { + content: "\e020"; +} +.icon-exchange:before { + content: "\e021"; +} +.icon-hfc:before { + content: "\e022"; +} +.icon-home-1:before { + content: "\e023"; +} +.icon-private-key:before { + content: "\e024"; +} +.icon-security-pic:before { + content: "\e026"; +} +.icon-wallet:before { + content: "\e027"; +} +.icon-send:before { + content: "\e028"; +} +.icon-password:before { + content: "\e029"; +} +.icon-rez:before { + content: "\e025"; +} +.icon-keyboard-collapse:before { + content: "\e02b"; +} +.icon-image:before { + content: "\e02a"; +} +.icon-environments:before { + content: "\e02c"; +} +.icon-wand:before { + content: "\e02d"; +} +.icon-market:before { + content: "\74"; +} +.icon-wear:before { + content: "\e02e"; +} +.icon-certificate:before { + content: "\e030"; +} +.icon-gift:before { + content: "\e031"; +} +.icon-update:before { + content: "\e032"; +} +.icon-uninstall:before { + content: "\e033"; +} +.icon-install:before { + content: "\e02f"; +} +.icon-ellipsis-vertical:before { + content: "\e034"; +} +.icon-backward:before { + content: "\45"; +} +.icon-40-reload:before { + content: "\46"; +} +.icon-forward:before { + content: "\44"; +} +.icon-avatar-1:before { + content: "\54"; +} diff --git a/applications/BodyPaint4/content/js/ColorUtils.js b/applications/BodyPaint4/content/js/ColorUtils.js new file mode 100644 index 0000000..4524596 --- /dev/null +++ b/applications/BodyPaint4/content/js/ColorUtils.js @@ -0,0 +1,76 @@ +//converts hsv color into rgb color space, expects hsv with the following ranges +//H(0, 359), S(0, 1), V(0, 1) to R(0, 255), G(0, 255), B(0, 255) +hsv2rgb = function (hsvColor) { + var c = hsvColor.value * hsvColor.saturation; + var x = c * (1 - Math.abs((hsvColor.hue/60) % 2 - 1)); + var m = hsvColor.value - c; + var rgbColor = new Object(); + if (hsvColor.hue >= 0 && hsvColor.hue < 60) { + rgbColor.red = Math.ceil((c + m) * 255); + rgbColor.green = Math.ceil((x + m) * 255); + rgbColor.blue = Math.ceil((0 + m) * 255); + } else if (hsvColor.hue >= 60 && hsvColor.hue < 120) { + rgbColor.red = Math.ceil((x + m) * 255); + rgbColor.green = Math.ceil((c + m) * 255); + rgbColor.blue = Math.ceil((0 + m) * 255); + } else if (hsvColor.hue >= 120 && hsvColor.hue < 180) { + rgbColor.red = Math.ceil((0 + m) * 255); + rgbColor.green = Math.ceil((c + m) * 255); + rgbColor.blue = Math.ceil((x + m) * 255); + } else if (hsvColor.hue >= 180 && hsvColor.hue < 240) { + rgbColor.red = Math.ceil((0 + m) * 255); + rgbColor.green = Math.ceil((x + m) * 255); + rgbColor.blue = Math.ceil((c + m) * 255); + } else if (hsvColor.hue >= 240 && hsvColor.hue < 300) { + rgbColor.red = Math.ceil((x + m) * 255); + rgbColor.green = Math.ceil((0 + m) * 255); + rgbColor.blue = Math.ceil((c + m) * 255); + } else if (hsvColor.hue >= 300 && hsvColor.hue < 360) { + rgbColor.red = Math.ceil((c + m) * 255); + rgbColor.green = Math.ceil((0 + m) * 255); + rgbColor.blue = Math.ceil((x + m) * 255); + } + return rgbColor; +} + + +//converts rgb color into hsv color space, expects rgb with the following ranges +//R(0, 255), G(0, 255), B(0, 255) to H(0, 359), S(0, 1), V(0, 1) +rgb2hsv = function (rgbColor) { + var r = rgbColor.red / 255.0; + var g = rgbColor.green / 255.0; + var b = rgbColor.blue / 255.0; + + var cMax = Math.max(r, Math.max(g, b)); + var cMin = Math.min(r, Math.min(g, b)); + var deltaC = cMax - cMin; + + var hsvColor = new Object(); + //hue calculatio + if (deltaC == 0) { + hsvColor.hue = 0; + + } else if (cMax == r) { + hsvColor.hue = 60 * (((g-b)/deltaC) % 6); + + } else if (cMax == g) { + hsvColor.hue = 60 * (((b-r)/deltaC) + 2); + + } else if (cMax == b) { + hsvColor.hue = 60 * (((r-g)/deltaC) + 4); + } + + if (hsvColor.hue < 0) { + hsvColor.hue += 360; + } + //saturation calculation + if (cMax == 0) { + hsvColor.saturation = 0; + } else { + hsvColor.saturation = (deltaC/cMax); + } + + hsvColor.value = (cMax); + + return hsvColor; +} \ No newline at end of file diff --git a/applications/BodyPaint4/content/tabicons/brush-icon.svg b/applications/BodyPaint4/content/tabicons/brush-icon.svg new file mode 100644 index 0000000..93947e2 --- /dev/null +++ b/applications/BodyPaint4/content/tabicons/brush-icon.svg @@ -0,0 +1 @@ +Artboard 1 \ No newline at end of file diff --git a/applications/BodyPaint4/content/tabicons/hand-icon.svg b/applications/BodyPaint4/content/tabicons/hand-icon.svg new file mode 100644 index 0000000..3355ce2 --- /dev/null +++ b/applications/BodyPaint4/content/tabicons/hand-icon.svg @@ -0,0 +1 @@ +hand \ No newline at end of file diff --git a/applications/BodyPaint4/content/tabicons/palette-icon.svg b/applications/BodyPaint4/content/tabicons/palette-icon.svg new file mode 100644 index 0000000..816cecd --- /dev/null +++ b/applications/BodyPaint4/content/tabicons/palette-icon.svg @@ -0,0 +1 @@ +palette-icon \ No newline at end of file diff --git a/applications/BodyPaint4/hifi/css/colpick.css b/applications/BodyPaint4/hifi/css/colpick.css new file mode 100644 index 0000000..564f60c --- /dev/null +++ b/applications/BodyPaint4/hifi/css/colpick.css @@ -0,0 +1,420 @@ +/* +colpick Color Picker / colpick.com +*/ + +/*Main container*/ +.colpick { + position: absolute; + width: 346px; + height: 170px; + overflow: hidden; + display: none; + font-family: Arial, Helvetica, sans-serif; + background:#ebebeb; + border: 1px solid #bbb; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + /*Prevents selecting text when dragging the selectors*/ + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +/*Color selection box with gradients*/ +.colpick_color { + position: absolute; + left: 7px; + top: 7px; + width: 156px; + height: 156px; + overflow: hidden; + outline: 1px solid #aaa; + cursor: crosshair; +} +.colpick_color_overlay1 { + position: absolute; + left:0; + top:0; + width: 156px; + height: 156px; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff')"; /* IE8 */ + background: -moz-linear-gradient(left, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,255,255,1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* IE10+ */ + background: linear-gradient(to right, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff'); /* IE6 & IE7 */ +} +.colpick_color_overlay2 { + position: absolute; + left:0; + top:0; + width: 156px; + height: 156px; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#00000000', endColorstr='#000000')"; /* IE8 */ + background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,1))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* IE10+ */ + background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#000000',GradientType=0 ); /* IE6-9 */ +} +/*Circular color selector*/ +.colpick_selector_outer { + background:none; + position: absolute; + width: 11px; + height: 11px; + margin: -6px 0 0 -6px; + border: 1px solid black; + border-radius: 50%; +} +.colpick_selector_inner{ + position: absolute; + width: 9px; + height: 9px; + border: 1px solid white; + border-radius: 50%; +} +/*Vertical hue bar*/ +.colpick_hue { + position: absolute; + top: 6px; + left: 175px; + width: 19px; + height: 156px; + border: 1px solid #aaa; + cursor: n-resize; +} +/*Hue bar sliding indicator*/ +.colpick_hue_arrs { + position: absolute; + left: -8px; + width: 35px; + height: 7px; + margin: -7px 0 0 0; +} +.colpick_hue_larr { + position:absolute; + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-left: 7px solid #858585; +} +.colpick_hue_rarr { + position:absolute; + right:0; + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 7px solid #858585; +} +/*New color box*/ +.colpick_new_color { + position: absolute; + left: 207px; + top: 6px; + width: 60px; + height: 27px; + background: #f00; + border: 1px solid #8f8f8f; +} +/*Current color box*/ +.colpick_current_color { + position: absolute; + left: 277px; + top: 6px; + width: 60px; + height: 27px; + background: #f00; + border: 1px solid #8f8f8f; +} +/*Input field containers*/ +.colpick_field, .colpick_hex_field { + position: absolute; + height: 20px; + width: 60px; + overflow:hidden; + background:#f3f3f3; + color:#b8b8b8; + font-size:12px; + border:1px solid #bdbdbd; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.colpick_rgb_r { + top: 40px; + left: 207px; +} +.colpick_rgb_g { + top: 67px; + left: 207px; +} +.colpick_rgb_b { + top: 94px; + left: 207px; +} +.colpick_hsb_h { + top: 40px; + left: 277px; +} +.colpick_hsb_s { + top: 67px; + left: 277px; +} +.colpick_hsb_b { + top: 94px; + left: 277px; +} +.colpick_hex_field { + width: 68px; + left: 207px; + top: 121px; +} +/*Text field container on focus*/ +.colpick_focus { + border-color: #999; +} +/*Field label container*/ +.colpick_field_letter { + position: absolute; + width: 12px; + height: 20px; + line-height: 20px; + padding-left: 4px; + background: #efefef; + border-right: 1px solid #bdbdbd; + font-weight: bold; + color:#777; +} +/*Text inputs*/ +.colpick_field input, .colpick_hex_field input { + position: absolute; + right: 11px; + margin: 0; + padding: 0; + height: 20px; + line-height: 20px; + background: transparent; + border: none; + font-size: 12px; + font-family: Arial, Helvetica, sans-serif; + color: #555; + text-align: right; + outline: none; +} +.colpick_hex_field input { + right: 4px; +} +/*Field up/down arrows*/ +.colpick_field_arrs { + position: absolute; + top: 0; + right: 0; + width: 9px; + height: 21px; + cursor: n-resize; +} +.colpick_field_uarr { + position: absolute; + top: 5px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-bottom: 4px solid #959595; +} +.colpick_field_darr { + position: absolute; + bottom:5px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid #959595; +} +/*Submit/Select button*/ +.colpick_submit { + position: absolute; + left: 207px; + top: 149px; + width: 130px; + height: 22px; + line-height:22px; + background: #efefef; + text-align: center; + color: #555; + font-size: 12px; + font-weight:bold; + border: 1px solid #bdbdbd; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.colpick_submit:hover { + background:#f3f3f3; + border-color:#999; + cursor: pointer; +} + +/*full layout with no submit button*/ +.colpick_full_ns .colpick_submit, .colpick_full_ns .colpick_current_color{ + display:none; +} +.colpick_full_ns .colpick_new_color { + width: 130px; + height: 25px; +} +.colpick_full_ns .colpick_rgb_r, .colpick_full_ns .colpick_hsb_h { + top: 42px; +} +.colpick_full_ns .colpick_rgb_g, .colpick_full_ns .colpick_hsb_s { + top: 73px; +} +.colpick_full_ns .colpick_rgb_b, .colpick_full_ns .colpick_hsb_b { + top: 104px; +} +.colpick_full_ns .colpick_hex_field { + top: 135px; +} + +/*rgbhex layout*/ +.colpick_rgbhex .colpick_hsb_h, .colpick_rgbhex .colpick_hsb_s, .colpick_rgbhex .colpick_hsb_b { + display:none; +} +.colpick_rgbhex { + width:282px; +} +.colpick_rgbhex .colpick_field, .colpick_rgbhex .colpick_submit { + width:68px; +} +.colpick_rgbhex .colpick_new_color { + width:34px; + border-right:none; +} +.colpick_rgbhex .colpick_current_color { + width:34px; + left:240px; + border-left:none; +} + +/*rgbhex layout, no submit button*/ +.colpick_rgbhex_ns .colpick_submit, .colpick_rgbhex_ns .colpick_current_color{ + display:none; +} +.colpick_rgbhex_ns .colpick_new_color{ + width:68px; + border: 1px solid #8f8f8f; +} +.colpick_rgbhex_ns .colpick_rgb_r { + top: 42px; +} +.colpick_rgbhex_ns .colpick_rgb_g { + top: 73px; +} +.colpick_rgbhex_ns .colpick_rgb_b { + top: 104px; +} +.colpick_rgbhex_ns .colpick_hex_field { + top: 135px; +} + +/*hex layout*/ +.colpick_hex .colpick_hsb_h, .colpick_hex .colpick_hsb_s, .colpick_hex .colpick_hsb_b, .colpick_hex .colpick_rgb_r, .colpick_hex .colpick_rgb_g, .colpick_hex .colpick_rgb_b { + display:none; +} +.colpick_hex { + width:206px; + height:201px; +} +.colpick_hex .colpick_hex_field { + width:72px; + height:25px; + top:168px; + left:80px; +} +.colpick_hex .colpick_hex_field div, .colpick_hex .colpick_hex_field input { + height: 25px; + line-height: 25px; +} +.colpick_hex .colpick_new_color { + left:9px; + top:168px; + width:30px; + border-right:none; +} +.colpick_hex .colpick_current_color { + left:39px; + top:168px; + width:30px; + border-left:none; +} +.colpick_hex .colpick_submit { + left:164px; + top: 168px; + width:30px; + height:25px; + line-height: 25px; +} + +/*hex layout, no submit button*/ +.colpick_hex_ns .colpick_submit, .colpick_hex_ns .colpick_current_color { + display:none; +} +.colpick_hex_ns .colpick_hex_field { + width:80px; +} +.colpick_hex_ns .colpick_new_color{ + width:60px; + border: 1px solid #8f8f8f; +} + +/*Dark color scheme*/ +.colpick_dark { + background: #161616; + border-color: #2a2a2a; +} +.colpick_dark .colpick_color { + outline-color: #333; +} +.colpick_dark .colpick_hue { + border-color: #555; +} +.colpick_dark .colpick_field, .colpick_dark .colpick_hex_field { + background: #101010; + border-color: #2d2d2d; +} +.colpick_dark .colpick_field_letter { + background: #131313; + border-color: #2d2d2d; + color: #696969; +} +.colpick_dark .colpick_field input, .colpick_dark .colpick_hex_field input { + color: #7a7a7a; +} +.colpick_dark .colpick_field_uarr { + border-bottom-color:#696969; +} +.colpick_dark .colpick_field_darr { + border-top-color:#696969; +} +.colpick_dark .colpick_focus { + border-color:#444; +} +.colpick_dark .colpick_submit { + background: #131313; + border-color:#2d2d2d; + color:#7a7a7a; +} +.colpick_dark .colpick_submit:hover { + background-color:#101010; + border-color:#444; +} \ No newline at end of file diff --git a/applications/BodyPaint4/hifi/css/edit-style.css b/applications/BodyPaint4/hifi/css/edit-style.css new file mode 100644 index 0000000..d2e390d --- /dev/null +++ b/applications/BodyPaint4/hifi/css/edit-style.css @@ -0,0 +1,1640 @@ +/* +// edit-style.css +// +// Created by Ryan Huffman on 13 Nov 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../fonts/Raleway-Regular.ttf); +} + +@font-face { + font-family: Raleway-Light; + src: url(../fonts/Raleway-Light.ttf); +} + +@font-face { + font-family: Raleway-Bold; + src: url(../fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../fonts/FiraSans-SemiBold.ttf); +} + +@font-face { + font-family: AnonymousPro-Regular; + src: url(../fonts/AnonymousPro-Regular.ttf); +} + +@font-face { + font-family: HiFi-Glyphs; + src: url(../fonts/hifi-glyphs.ttf); +} + +* { + margin: 0; + padding: 0; +} + +body { + padding: 21px 21px 21px 21px; + + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 15px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +table { + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #afafaf; + border-collapse: collapse; + width: 100%; + border: 2px solid #575757; + border-radius: 7px; +} + +thead { + font-family: Raleway-Regular; + font-size: 12px; + text-transform: uppercase; + background-color: #1c1c1c; + padding: 1px 0px; + border-bottom: 1px solid #575757; + width: 100%; +} + +tbody { + width: 100%; +} + +tfoot { + font-family: Raleway-Light; + font-size: 13px; + background-color: #1c1c1c; + border-top: 1px solid #575757; + width: 100%; +} + +tfoot tr { + background-color: #1c1cff; +} + +thead tr { + height: 26px; /* 28px with thead padding */ +} + +thead th { + height: 26px; + background-color: #1c1c1c; + border-right: 1px solid #575757; +} + +thead th:last-child { + border: none; +} + +tbody td { + height: 26px; +} + +tfoot td { + height: 18px; + width: 100%; + background-color: #1c1c1c; + margin-left: 12px; +} + +tr { + width: 100%; + cursor: pointer; +} + +tr:nth-child(odd) { + background-color: #2e2e2e; +} + +tr:nth-child(even) { + background-color: #1c1c1c; +} + +tr:focus { + outline: none; +} + +tr.selected { + color: #000000; + background-color: #00b4ef; +} + +tr.selected + tr.selected { + border-top: 1px solid #2e2e2e; +} + +th { + text-align: center; + word-wrap: nowrap; + white-space: nowrap; + padding-left: 12px; + padding-right: 12px; +} + +td { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + word-wrap: nowrap; + padding-left: 12px; + padding-right: 12px; +} + +td.url { + white-space: nowrap; + overflow: hidden; +} + + +input[type="text"], input[type="number"], textarea { + margin: 0; + padding: 0 12px; + color: #afafaf; + background-color: #252525; + border: none; + font-family: FiraSans-SemiBold; + font-size: 15px; +} + +textarea { + font-family: AnonymousPro-Regular; + font-size: 16px; + padding-top: 5px; + padding-bottom: 5px; + min-height: 64px; + width: 100%; + resize: vertical; +} + +input::-webkit-input-placeholder { + font-style: italic; +} + +input:focus, textarea:focus { + color: #fff; + background-color: #000; + outline: 1px solid #00b4ef; + outline-offset: -1px; +} + +input::selection, textarea::selection { + color: #000000; + background-color: #00b4ef; +} + +input.search { + border-radius: 14px; +} + +input.search:focus { + outline: none; + box-sizing: border-box; + height: 26px; + margin-top: 1px; + margin-bottom: 1px; + box-shadow: 0 0 0px 1px #00b4ef; +} + +input:disabled, textarea:disabled { + background-color: #383838; + color: #afafaf; +} + +input[type="text"] { + height: 28px; + width: 100%; +} + +input[type="number"] { + position: relative; + height: 28px; + width: 124px; +} + +input[type=number] { + padding-right: 3px; +} +input[type=number]::-webkit-inner-spin-button { + opacity: 1.0; + display: block; + position: relative; + width: 10px; + height: 90%; + overflow: hidden; + font-family: hifi-glyphs; + font-size: 32px; + color: #afafaf; + cursor: pointer; + background-color: #000000; +} +input[type=number]::-webkit-inner-spin-button:before, +input[type=number]::-webkit-inner-spin-button:after { + position:absolute; + left: -19px; + line-height: 8px; + text-align: center; +} +input[type=number]::-webkit-inner-spin-button:before { + content: "6"; + top: 4px; +} +input[type=number]::-webkit-inner-spin-button:after { + content: "5"; + bottom: 4px; +} + +input[type=number].hover-up::-webkit-inner-spin-button:before, +input[type=number].hover-down::-webkit-inner-spin-button:after { + color: #ffffff; +} + +input.no-spin::-webkit-outer-spin-button, +input.no-spin::-webkit-inner-spin-button { + display: none; + -webkit-appearance: none; + margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ + padding-right: 12px; +} + +input[type=button] { + font-family: Raleway-Bold; + font-size: 13px; + text-transform: uppercase; + vertical-align: top; + height: 28px; + min-width: 120px; + padding: 0px 18px; + margin-right: 6px; + border-radius: 5px; + border: none; + color: #fff; + background-color: #000; + background: linear-gradient(#343434 20%, #000 100%); + cursor: pointer; +} + +input[type=button].glyph { + font-family: HiFi-Glyphs; + font-size: 20px; + text-transform: none; + min-width: 32px; + padding: 0; +} + +input[type=button].red { + color: #fff; + background-color: #94132e; + background: linear-gradient(#d42043 20%, #94132e 100%); +} +input[type=button].blue { + color: #fff; + background-color: #1080b8; + background: linear-gradient(#00b4ef 20%, #1080b8 100%); +} +input[type=button].white { + color: #121212; + background-color: #afafaf; + background: linear-gradient(#fff 20%, #afafaf 100%); +} + +input[type=button]:enabled:hover { + background: linear-gradient(#000, #000); + border: none; +} +input[type=button].red:enabled:hover { + background: linear-gradient(#d42043, #d42043); + border: none; +} +input[type=button].blue:enabled:hover { + background: linear-gradient(#00b4ef, #00b4ef); + border: none; +} +input[type=button].white:enabled:hover { + background: linear-gradient(#fff, #fff); + border: none; +} + +input[type=button]:active { + background: linear-gradient(#343434, #343434); +} +input[type=button].red:active { + background: linear-gradient(#94132e, #94132e); +} +input[type=button].blue:active { + background: linear-gradient(#1080b8, #1080b8); +} +input[type=button].white:active { + background: linear-gradient(#afafaf, #afafaf); +} + +input[type=button]:disabled { + color: #252525; + background: linear-gradient(#575757 20%, #252525 100%); +} + +input[type=button][pressed=pressed] { + color: #00b4ef; +} + +input[type=checkbox] { + display: none; +} +input[type=checkbox] + label { + padding-left: 24px; + background-position-y: 6px; + background-repeat: no-repeat; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACpSURBVDhPY2xoaGD68+dPMSMjY9L////VgTQjAw4AlH8PxLOPHj1azWxjY1MBVNsBFBfBpwkEgNKcQGwtJyfHyATkF0KEiQdAzYlMQEIUyicFyDD9+/ePgRxMvsb///4zkIOZ/v0HmkAGHginYjGNGAzS+BpdkAj8mun/3//92DyPD//993cG88nTJ4+Zm5p/BSZeJYb/DEJADEzNOPF7hn8Mk69cvVIPAHN5pyfo70F5AAAAAElFTkSuQmCC); +} +input[type=checkbox]:enabled + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAClSURBVDhPY2hoaGD6//9/6b9//64D8T8gGycASr/7+/dv5/79+1kYgIxKqDjRAKiniRFIv2JgYBAFYlLAE0aQ66AckgDjjx8/yNP44cMH8jS+fPmSPI0PHz4kT+PNmzfJ03jp0iXyNJ46dYo8jYcPHyYnAbxm+vnzZz8wLhlIwd+/f5/BrKSkdExCQuLrnz9/lIBpUAiIQekXF34PTGmTT548WQ8AokXg+rhVtPYAAAAASUVORK5CYII=); +} +input[type=checkbox]:checked + label { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFLSURBVDhPjZK9SgNBFIXvbCaQRDQq7mIhQRPBRisJKcwLWOobaCsExEaxcEEQe0trGysfwg0EwWoDsbFIJUaIBJOwus547saNP3FlPzgzzJxzL5edFbZtG77v7wkhtrXWS9gFRQC/DZ07jnOYKJfL+8ie4n7mvyIGdhpay+VyQuK8y5dPZoHuVtbpZcLi4wjJ1x4t316R9dDgBlsSi8mGu7pJjyJFzVaH+r7iqyHSELSQzVADjS0UgjlDKUUsLzVO98+9kSLGV5qaHXhjU0GWNSxk3hCIwnsfeMNCjTArLmHeUBodoLiE+R+jxuHPUZP4elGE3teonx2S/Q7lJzOUlkYQ+A4/xzyegzNhXmJpwTMXry9IFjcoa84O0r+QXpcK1cugCLREZadyoA19Ergxwf96nKjd1KqlYqmLQ540TUNwItUmRWdu3T36AODjwgpY9xqqAAAAAElFTkSuQmCC); +} +input[type=checkbox]:checked + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEySURBVDhPnZLPSsNAEMa/XVPBCE0RhNy0OarP4Av4AD6JB0GwVBA8efBBxHsgh4CQswcRoUIpiIpVAm3zZ5M4szFSbQPBH3xkJvNNZskOer2eLIriKM/ze1JOcS1UHmdZduF5ngEKjr/fN4Z6+oKerwA2gxC4HAFPEWVLsAzgZAvYt3Q6Enw6jg7uBAaTFMNwhpnKdbXCkAJdy8ROu4XrXW2HTJIErHcFDD6nC02Mom8PwymeE2gvS0ZRBBaTlsOXEmdlrfLLOI7Bakrl/zWxCT8T/904f9QW/b06qtrCUdtFCqdjYs2Q2jAPX8c2XQd7Kr/wfV8vwIPs4Ga1ixe5Xrr/YFLTYfKIvWzM6ZtwXZdX7lxXG0L+sxXHcW5t254opRzawQ0S72+dPmjTroIgOP0CQSMt5LDn1T8AAAAASUVORK5CYII=); +} + +.icon-input input { + position: relative; + padding-left: 36px; +} +.icon-input span { + position: absolute; + left: 6px; + top: -2px; + font-family: hifi-glyphs; + font-size: 30px; + color: #afafaf; +} +.icon-input input:focus + span { + color: #ffffff; +} + +.selectable { + -webkit-touch-callout: text; + -webkit-user-select: text; + -khtml-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + cursor: text; +} + +.color-box { + display: inline-block; + width: 15pt; + height: 15pt; + border: 0.75pt solid black; + margin: 1.5pt; + cursor: pointer; +} + +.color-box.highlight { + width: 13.5pt; + height: 13.5pt; + border: 1.5pt solid black; +} + +.shape-section, .light-section, .model-section, .web-section, .hyperlink-section, .text-section, .zone-section { + display: table; +} + + + +#properties-list fieldset { + position: relative; + /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ + margin: 21px -21px 0px -21px; + padding: 0.1px 21px 0px 21px; + border: none; + border-top: 1px rgb(90,90,90) solid; + box-shadow: 0px -1px 0px rgb(37,37,37); +} + +#properties-list fieldset.fstuple, #properties-list fieldset.fsrow { + margin-top: 21px; + border: none; + box-shadow: none; +} + +#properties-list > fieldset[data-collapsed="true"] + fieldset { + margin-top: 0px; +} + +#properties-list > fieldset[data-collapsed="true"] > *:not(legend) { + display: none !important; +} + +#properties-list legend + fieldset { + margin-top: 0px; + border: none; + box-shadow: none; +} + +#properties-list > fieldset#properties-header { + margin-top: 0px; + padding-bottom: 0px; +} + + + +#properties-list > fieldset > legend { + position: relative; + display: table; + width: 100%; + margin: 21px -21px 0 -21px; + padding: 14px 21px 0 21px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 28px; + text-transform: uppercase; + outline: none; + background-color: #404040; + border: none; + border-top: 1px rgb(90,90,90) solid; + box-shadow: 0 -1px 0 rgb(37,37,37), 0 4px 4px 0 rgba(0,0,0,0.75); +} + +div.section-header, .sub-section-header, hr { + display: table; + width: 100%; + margin: 21px -21px 0 -21px; + padding: 14px 21px 0 21px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 28px; + text-transform: uppercase; + outline: none; +} + + + +.column .sub-section-header { + background-image: none; + padding-top: 0; +} + +.sub-section-header, .no-collapse, hr { + background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; +} + +div.section-header:first-child { + margin-top: -2px; + padding-top: 0; + background: none; + height: auto; +} + +.sub-section-header { + margin-bottom: -10px; +} + +#properties-list > fieldset > legend span, .section-header span { + font-family: HiFi-Glyphs; + font-size: 30px; + float: right; + position: absolute; + top: 4px; + right: 13px; +} + +.section-header[collapsed="true"] { + margin-bottom: -21px; +} + +hr { + border: none; + padding-top: 2px; +} + +.text-group[collapsed="true"] ~ .text-group, +.zone-group[collapsed="true"] ~ .zone-group, +.web-group[collapsed="true"] ~ .web-group, +.hyperlink-group[collapsed="true"] ~ .hyperlink-group, +.spatial-group[collapsed="true"] ~ .spatial-group, +.physical-group[collapsed="true"] ~ .physical-group, +.behavior-group[collapsed="true"] ~ .behavior-group, +.model-group[collapsed="true"] ~ .model-group, +.light-group[collapsed="true"] ~ .light-group { + display: none !important; +} + + +.property { + display: table; + width: 100%; + margin-top: 21px; + min-height: 28px; +} + +.property.checkbox { + width: auto; +} + +.property label, .number label { + display: table-cell; + vertical-align: middle; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.property label .unit, .number label .unit { + margin-left: 8px; + font-family: Raleway-Light; + font-size: 13px; +} + +.property legend, .number legend { + display: table-cell; + vertical-align: middle; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.property legend .unit, .number legend .unit { + margin-left: 8px; + font-family: Raleway-Light; + font-size: 13px; +} + +.value { + display: block; + min-height: 18px; +} +.value label { + display: inline-block; + vertical-align: top; + width: 48px; +} +.value legend { + display: inline-block; + vertical-align: top; + width: 48px; +} +.value span { + font-family: FiraSans-SemiBold; + font-size: 15px; +} + +.checkbox + .checkbox { + margin-top: 0; +} + +.checkbox-sub-props { + margin-top: 18px; +} + +.property .number { + float: left; +} +.property .number + .number { + margin-left: 10px; +} + +.text label, .url label, .number label, .textarea label, .rgb label, .xyz label, .pyr label, .dropdown label, .gen label { + float: left; + margin-left: 1px; + margin-bottom: 3px; + margin-top: -2px; +} + +.text legend, .url legend, .number legend, .textarea legend, .rgb legend, .xyz legend, .pyr legend, .dropdown legend, .gen legend { + float: left; + margin-left: 1px; + margin-bottom: 3px; + margin-top: -2px; +} + +.number > input { + clear: both; + float: left; +} +.number > span { + clear: both; + float: left; +} +.xyz > div, .pyr > div, .gen > div { + clear: both; +} + +.dropdown { + position: relative; + margin-bottom: -17px; +} + +.dropdown select { + clear: both; +} + +.dropdown dl { + clear: both; +} +.dropdown dl { + font-family: FiraSans-SemiBold; + font-size: 15px; + width: 292px; + height: 28px; + padding: 0 28px 0 12px; + color: #afafaf; + background: linear-gradient(#7d7d7d 20%, #686a68 100%); + position: relative; +} +.dropdown dl[dropped="true"] { + color: #404040; + background: linear-gradient(#afafaf, #afafaf); + z-index: 998; +} + +.dropdown dt { + height: 100%; + box-sizing: border-box; + border-right: 1px solid #121212; + width: 100%; +} +.dropdown dt:hover { + color: #404040; +} +.dropdown dt:focus { + outline: none; +} +.dropdown dt span:first-child { + display: inline-block; + position: relative; + top: 5px; +} +.dropdown dt span:last-child { + font-family: HiFi-Glyphs; + font-size: 42px; + float: right; + margin-right: -48px; + position: relative; + left: -12px; + top: -9px; +} + +.dropdown dd { + position: absolute; + top: 28px; + left: 3px; + display: none; +} +.dropdown dl[dropped="true"] dd { + display: block; +} + +.dropdown li { + list-style-type: none; + padding: 3px 0 1px 12px; + width: 320px; + height: auto; + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #404040; + background-color: #afafaf; + z-index: 999; +} +.dropdown li:hover { + background-color: #00b4ef; +} + +.dropdown dl[disabled="disabled"], .dropdown dl[disabled="disabled"][dropped="true"] { + color: #252525; + background: linear-gradient(#575757 20%, #252525 100%); +} +.dropdown dl[disabled="disabled"] dd { + display: none; +} +.dropdown dl[disabled="disabled"] dt:hover { + color: #252525; +} + + +div.refresh { + box-sizing: border-box; + padding-right: 44px; +} +div.refresh input[type="button"] { + float: right; + margin-right: -44px; +} + +.color-picker { + box-sizing: border-box; + float: left; + margin-bottom: 21px; + width: 36px; + height: 36px; + border: 4px solid #afafaf; + border-radius: 4px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABTSURBVChTjcxBDoAwCETRwTs33sFjwB6uaCE1Ggvav5qQF7CSqu40dllHjYiOT3gh3yV8Ii+Fb+RNMEP9hm3sKENmBhG5P1aImWMH/EMerSAAOAFgTC/R8ZXSXAAAAABJRU5ErkJggg==); + background-position: bottom right; + background-repeat: no-repeat; +} +.color-picker:focus { + outline: none; +} +.color-picker[active="true"] { + border-color: #000; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABDSURBVChTjcoLCkAhCETRNq0tf97Y5xGZ1gVJ45TH6njThIO+xk2UwhWFcEdH6JCqOuiQiMDi/hcii3crRRb/7ggAPvIMVihQwvSXAAAAAElFTkSuQmCC); +} + +.color-picker[disabled="disabled"] { + border-color: #afafaf; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABTSURBVChTjcxBDoAwCETRwTs33sFjwB6uaCE1Ggvav5qQF7CSqu40dllHjYiOT3gh3yV8Ii+Fb+RNMEP9hm3sKENmBhG5P1aImWMH/EMerSAAOAFgTC/R8ZXSXAAAAABJRU5ErkJggg==); +} + +.colpick[disabled="disabled"] { + display: none !important; +} + +#property-color-control1 { + display: table-cell; + float: none; +} + +#property-color-control1 + label { + border-left: 20px transparent solid; +} + +.rgb label { + float: left; + margin-top: 10px; + margin-left: 21px; +} +.rgb label + * { + clear: both; +} + +.rgb legend { + float: left; + margin-top: 10px; + margin-left: 21px; +} +.rgb legend + * { + clear: both; +} + +.tuple div { + display: inline-block; + position: relative; + margin-right: 6px; +} +.tuple div:last-child { + margin-right: 0; +} + +.tuple label { + margin-right: -6px; +} + +.rgb .tuple input { + padding-left: 65px; +} +.xyz .tuple input { + padding-left: 25px; +} +.pyr .tuple input { + padding-left: 40px; +} + +.tuple div > label:first-child { + float: left; +} +.tuple div > label + input { + clear: both; + float: left; +} +.tuple div input + label { + display: inline !important; + float: none !important; + position: absolute; + margin-top: 8px; + margin-left: 6px; + left: 0; + font-family: FiraSans-SemiBold; + font-size: 12px; +} +.tuple .red + label, .tuple .x + label, .tuple .pitch + label { + color: #e2334d; +} +.tuple .green + label, .tuple .y + label, .tuple .yaw + label { + color: #1ac567; +} +.tuple .blue + label, .tuple .z + label, .tuple .roll + label { + color: #1080b8; +} + +.tuple .red:focus, .tuple .x:focus, .tuple .pitch:focus { + outline-color: #e2334d; +} +.tuple .green:focus, .tuple .y:focus, .tuple .yaw:focus { + outline-color: #1ac567; +} +tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { + outline-color: #1080b8; +} + +.xyz .buttons input { + margin-top: 14px; +} +.xyz .buttons span { + word-wrap: nowrap; + white-space: nowrap; +} + +.row .property { + width: auto; + display: inline-block; + margin-right: 6px; +} +.row .property:last-child { + margin-right: 0; +} +.row .property input { + clear: both; + float: left; +} + +.two-column { + display: table; + width: 100%; +} +.two-column > div { + display: table-cell; + width: 50%; +} + +#properties-list fieldset .two-column { + padding-top:21px; + display: flex; +} + +#properties-list .two-column fieldset { + /*display: table-cell;*/ + width: 50%; + margin: 0; + padding: 0; + border-top: none; + box-shadow: none; +} + +#properties-list .two-column fieldset legend { + display: table; + width: 100%; + margin: 21px -21px 0px -21px; + padding: 0px 0px 0px 21px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 28px; + text-transform: uppercase; + outline: none; +} + +fieldset .checkbox-sub-props { + margin-top: 0; + } + +fieldset .checkbox-sub-props .property:first-child { + margin-top: 0; +} + +.column { + vertical-align: top; +} + +.indent { + margin-left: 24px; +} + +::-webkit-scrollbar { + width: 20px; + height: 10px; +} +::-webkit-scrollbar-track { + background-color: #2e2e2e; +} +::-webkit-scrollbar-thumb { + background-color: #696969; + border: 2px solid #2e2e2e; + border-radius: 8px; +} + +/* FIXME: Revisit textarea resizer/corner when move to Qt 5.6 or later: see if can get resizer/corner to always be visible and +have correct background color with and without scrollbars. */ +textarea:enabled::-webkit-resizer { + background-size: 10px 10px; + background: #252525 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAXSURBVChTY1RVVWXADZigNA4wMqUZGACS3gCD5UUtKAAAAABJRU5ErkJggg==) no-repeat bottom right; +} +textarea:focus::-webkit-resizer { + background-size: 10px 10px; + background: #000000 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACOSURBVChThdC5DQQhDAXQvyRI5LQxFdABARWQElAPogYkiqEWQhLYGe8xxzJaS5a/8AuQHwDG2n+Lvee0hBDQWlO+hRvy3mNZFjDG5vCDOOeIMaL3/guPKISAWiu9n+AVSSlhraXdF86Qcw6tNdoTvEOlFOScd6iUOv3JGEMopYQx9jNvaawnoHnNr8Z4AuRLPOq2gPgnAAAAAElFTkSuQmCC) no-repeat bottom right; +} +textarea:enabled[scrolling="true"]::-webkit-resizer { + background-size: 10px 10px; + background: #2e2e2e url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACKSURBVChTjdAxDsMgDAXQT4UYuQIzCwsSKxsSJ4YDoByDY7AwUOG2aZMQqX+xhd9gzIwxA3/k8a7LCCFgraX+Fk4UY4RSCoyxNfwgzjlyzhhjXOEvSimhtUbvB3hGUkp472m2wxUKIaD3TnOCd6jWim3bvlBrfdjJOUeolEJoZj/4PMH83bl/BXgCWSs2Z09IjgoAAAAASUVORK5CYII=) no-repeat bottom right; +} + + +#entity-list-header { + margin-bottom: 36px; +} + +#entity-list-header div { + display: inline-block; + width: 65px; + margin-right: 6px; +} + +#entity-list-header div input:first-child { + margin-right: 0; + float: left; + width: 33px; + border-right: 1px solid #808080; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +#entity-list-header div input:last-child { + margin-right: 0; + float: right; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +#delete { + float: right; + margin-right: 0; + background-color: #ff0000; + min-width: 90px; +} + +#entity-list { + position: relative; /* New positioning context. */ +} + +#search-area { + padding-right: 168px; + padding-bottom: 24px; +} + +#filter { + width: 98%; +} + +#in-view { + position: absolute; + right: 126px; +} + +#radius-and-unit { + float: right; + margin-right: -168px; + position: relative; + top: -17px; +} +#radius-and-unit label { + margin-left: 2px; +} +#radius-and-unit input { + width: 120px; +} + +#entity-table-scroll { + /* Height is set by JavaScript. */ + width: 100%; + overflow-x: hidden; + overflow-y: auto; + box-sizing: border-box; + padding-top: 28px; /* Space for header and footer outside of scroll region. */ + margin-top: 28px; + border-left: 2px solid #575757; + border-right: 2px solid #575757; + background-color: #1c1c1c; +} + +#entity-table-scroll .glyph { + font-family: HiFi-Glyphs; + font-size: 15px; +} + +#entity-table { + margin-top: -28px; + margin-bottom: -18px; + table-layout: fixed; + border: none; + background-color: #1c1c1c; +} + +#entity-table thead tr, #entity-table thead tr th, +#entity-table tfoot tr, #entity-table tfoot tr td { + background: none; +} + +#entity-table .glyph { + margin: 0 -2px 0 -2px; + vertical-align: middle; +} + +#entity-table thead { + box-sizing: border-box; + border: 2px solid #575757; + border-top-left-radius: 7px; + border-top-right-radius: 7px; + border-bottom: 1px solid #575757; + position: absolute; + top: 49px; + left: 0; + width: 100%; + word-wrap: nowrap; + white-space: nowrap; + overflow: hidden; +} + +.verticesCount, .texturesCount, .texturesSize, .drawCalls { + text-align: right; +} + +#entity-table th { + display: inline-block; + box-sizing: border-box; + padding: 5px 0 0 0; + vertical-align: middle; + overflow: hidden; + text-overflow: ellipsis; +} + +#entity-table th:focus { + outline: none; +} + +#entity-table th .glyph { + position: relative; + left: 4px; +} +#entity-table th .glyph + .sort-order { + position: relative; + left: 4px; +} + +#entity-table th#entity-hasScript { + overflow: visible; +} + +#entity-table th#entity-hasScript .glyph { + text-transform: none; +} + +#entity-table thead .sort-order { + display: inline-block; + width: 8px; + margin: -5px 0 -3px 0; + vertical-align: middle; +} + +#entity-table th #info-toggle { + display: inline-block; + position: absolute; + left: initial; + right: 0; + width: 11px; + background-color: #1c1c1c; + z-index: 100; +} +#entity-table th #info-toggle span { + position: relative; + left: -2px; +} + +th#entity-hasTransparent .glyph { + font-weight: normal; + font-size: 24px !important; + margin: -6px; + position: relative; + top: -6px; +} +th#entity-hasTransparent .sort-order { + position: relative; + top: -4px; +} + +#entity-table td { + box-sizing: border-box; +} + +#entity-table td.glyph { + text-align: center; + padding: 0; +} +#entity-table td.hasTransparent.glyph { + font-size: 22px; + position: relative; + top: -1px; +} + +#entity-table td.isBaked.glyph { + font-size: 22px; + position: relative; + top: -1px; +} + +#entity-table tfoot { + box-sizing: border-box; + border: 2px solid #575757; + border-bottom-left-radius: 7px; + border-bottom-right-radius: 7px; + border-top: 1px solid #575757; + position: absolute; + bottom: -21px; + left: 0; + width: 100%; +} + + +#col-type { + width: 16%; +} +#col-name { + width: 34%; +} +#col-url { + width: 34%; +} +#col-locked, #col-visible { + width: 9%; +} +#col-verticesCount, #col-texturesCount, #col-texturesSize, #col-hasTransparent, #col-isBaked, #col-drawCalls, #col-hasScript { + width: 0; +} + +.showExtraInfo #col-type { + width: 10%; +} +.showExtraInfo #col-name { + width: 19%; +} +.showExtraInfo #col-url { + width: 19%; +} +.showExtraInfo #col-locked, .showExtraInfo #col-visible { + width: 4%; +} +.showExtraInfo #col-verticesCount { + width: 8%; +} +.showExtraInfo #col-texturesCount { + width: 8%; +} +.showExtraInfo #col-texturesSize { + width: 10%; +} +.showExtraInfo #col-hasTransparent { + width: 4%; +} +.showExtraInfo #col-isBaked { + width: 8%; +} +.showExtraInfo #col-drawCalls { + width: 8%; +} +.showExtraInfo #col-hasScript { + width: 6%; +} + +th#entity-verticesCount, th#entity-texturesCount, th#entity-texturesSize, th#entity-hasTransparent, th#entity-isBaked, th#entity-drawCalls, +th#entity-hasScript { + display: none; +} + +.verticesCount, .texturesCount, .texturesSize, .hasTransparent, .isBaked, .drawCalls, .hasScript { + display: none; +} + +#entity-visible { + border: none; +} + +.showExtraInfo #entity-verticesCount, .showExtraInfo #entity-texturesCount, .showExtraInfo #entity-texturesSize, +.showExtraInfo #entity-hasTransparent, .showExtraInfo #entity-isBaked, .showExtraInfo #entity-drawCalls, .showExtraInfo #entity-hasScript { + display: inline-block; +} + +.showExtraInfo .verticesCount, .showExtraInfo .texturesCount, .showExtraInfo .texturesSize, .showExtraInfo .hasTransparent, +.showExtraInfo .isBaked, .showExtraInfo .drawCalls, .showExtraInfo .hasScript { + display: table-cell; +} + +.showExtraInfo #entity-visible { + border-right: 1px solid #575757; +} + + +#no-entities { + display: none; + position: absolute; + top: 80px; + padding: 12px; + font-family: FiraSans-SemiBold; + font-size: 15px; + font-style: italic; + color: #afafaf; +} + + +#properties-list #properties-header { + display: table-row; + height: 28px; + border-top: none; + box-shadow: none; +} + +#properties-header .property { + display: table-cell; + vertical-align: middle; +} +#properties-header .checkbox { + position: relative; + top: -1px; +} + +#properties-header #type-icon { + font-family: hifi-glyphs; + font-size: 31px; + color: #00b4ef; + margin: -4px 12px -4px -2px; + width: auto; + display: none; + vertical-align: middle; +} + +#properties-header #property-type { + padding: 5px 24px 5px 0; + border-right: 1px solid #808080; + height: 100%; + width: auto; + display: inline-block; + vertical-align: middle; +} + +#properties-header .checkbox:last-child { + padding-left: 24px; +} + +#properties-header .checkbox label { + background-position-y: 1px; +} + +#properties-header .checkbox label span { + font-family: HiFi-Glyphs; + font-size: 20px; + padding-right: 6px; + vertical-align: top; + position: relative; + top: -4px; +} + +#properties-header input[type=checkbox]:checked + label span { + color: #ffffff; +} + +#properties-header + hr { + margin-top: 12px; +} + + +#id label { + width: 24px; +} +#property-id { + display: inline-block; +} +#property-id::selection { + color: #000000; + background-color: #00b4ef; +} + +input#property-parent-id { + width: 340px; +} + +input#dimension-rescale-button { + min-width: 50px; + margin-left: 6px; +} +input#reset-to-natural-dimensions { + margin-right: 0; +} + +#animation-fps { + margin-top: 48px; +} + +#userdata-clear{ + margin-bottom: 10px; +} + + +#static-userdata{ + display: none; + z-index: 99; + position: absolute; + width: 96%; + padding-left:1%; + margin-top:5px; + margin-bottom:10px; + background-color: #2e2e2e; +} + +#userdata-saved{ + margin-top:5px; + font-size:16px; + display:none; +} + +#properties-list #collision-info > fieldset:first-of-type { + border-top: none !important; + box-shadow: none; + margin-top: 0; +} + +#properties-list { + display: flex; + flex-direction: column; +} + +/* ----- Order of Menu items for Primitive ----- */ +#properties-list.ShapeMenu #general, +#properties-list.BoxMenu #general, +#properties-list.SphereMenu #general { + order: 1; +} + +#properties-list.ShapeMenu #collision-info, +#properties-list.BoxMenu #collision-info, +#properties-list.SphereMenu #collision-info { + order: 2; +} + +#properties-list.ShapeMenu #physical, +#properties-list.BoxMenu #physical, +#properties-list.SphereMenu #physical { + order: 3; +} + +#properties-list.ShapeMenu #spatial, +#properties-list.BoxMenu #spatial, +#properties-list.SphereMenu #spatial { + order: 4; +} + +#properties-list.ShapeMenu #behavior, +#properties-list.BoxMenu #behavior, +#properties-list.SphereMenu #behavior { + order: 5; +} + +#properties-list.ShapeMenu #hyperlink, +#properties-list.BoxMenu #hyperlink, +#properties-list.SphereMenu #hyperlink { + order: 6; +} + +#properties-list.ShapeMenu #light, +#properties-list.BoxMenu #light, +#properties-list.SphereMenu #light, +#properties-list.ShapeMenu #model, +#properties-list.BoxMenu #model, +#properties-list.SphereMenu #model, +#properties-list.ShapeMenu #zone, +#properties-list.BoxMenu #zone, +#properties-list.SphereMenu #zone, +#properties-list.ShapeMenu #text, +#properties-list.BoxMenu #text, +#properties-list.SphereMenu #text, +#properties-list.ShapeMenu #web, +#properties-list.BoxMenu #web, +#properties-list.SphereMenu #web { + display: none; +} + + +/* ----- Order of Menu items for Light ----- */ +#properties-list.LightMenu #general { + order: 1; +} +#properties-list.LightMenu #light { + order: 2; +} +#properties-list.LightMenu #physical { + order: 3; +} +#properties-list.LightMenu #spatial { + order: 4; +} +#properties-list.LightMenu #behavior { + order: 5; +} +#properties-list.LightMenu #collision-info { + order: 6; +} +#properties-list.LightMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.LightMenu #model, +#properties-list.LightMenu #zone, +#properties-list.LightMenu #text, +#properties-list.LightMenu #web { + display: none; +} +/* items to hide */ +#properties-list.LightMenu .shape-group.shape-section.property.dropdown, +#properties-list.LightMenu color-section.color-control1 { + display: none +} + + +/* ----- Order of Menu items for Model ----- */ +#properties-list.ModelMenu #general { + order: 1; +} +#properties-list.ModelMenu #model { + order: 2; +} +#properties-list.ModelMenu #collision-info { + order: 3; +} +#properties-list.ModelMenu #physical { + order: 4; +} +#properties-list.ModelMenu #spatial { + order: 5; +} +#properties-list.ModelMenu #behavior { + order: 6; +} +#properties-list.ModelMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.ModelMenu #light, +#properties-list.ModelMenu #zone, +#properties-list.ModelMenu #text, +#properties-list.ModelMenu #web { + display: none; +} +/* items to hide */ +#properties-list.ModelMenu .shape-group.shape-section.property.dropdown, +#properties-list.ModelMenu .color-section.color-control1 { + display: none +} + + +/* ----- Order of Menu items for Zone ----- */ +#properties-list.ZoneMenu #general { + order: 1; +} +#properties-list.ZoneMenu #zone { + order: 2; +} +#properties-list.ZoneMenu #physical { + order: 3; +} +#properties-list.ZoneMenu #spatial { + order: 4; +} +#properties-list.ZoneMenu #behavior { + order: 5; +} +#properties-list.ZoneMenu #collision-info { + order: 6; +} +#properties-list.ZoneMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.ZoneMenu #light, +#properties-list.ZoneMenu #model, +#properties-list.ZoneMenu #text, +#properties-list.ZoneMenu #web { + display: none; +} +/* items to hide */ +#properties-list.ZoneMenu .shape-group.shape-section.property.dropdown, +#properties-list.ZoneMenu .color-section.color-control1 { + display: none +} + + +/* ----- Order of Menu items for Web ----- */ +#properties-list.WebMenu #general { + order: 1; +} +#properties-list.WebMenu #web { + order: 2; +} +#properties-list.WebMenu #collision-info { + order: 3; +} +#properties-list.WebMenu #physical { + order: 4; +} +#properties-list.WebMenu #spatial { + order: 5; +} +#properties-list.WebMenu #behavior { + order: 6; +} +#properties-list.WebMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.WebMenu #light, +#properties-list.WebMenu #model, +#properties-list.WebMenu #zone, +#properties-list.WebMenu #text { + display: none; +} +/* items to hide */ +#properties-list.WebMenu .shape-group.shape-section.property.dropdown, +#properties-list.WebMenu .color-section.color-control1 { + display: none; +} + + + +/* ----- Order of Menu items for Text ----- */ +#properties-list.TextMenu #general { + order: 1; +} +#properties-list.TextMenu #text { + order: 2; +} +#properties-list.TextMenu #collision-info { + order: 3; +} +#properties-list.TextMenu #physical { + order: 4; +} +#properties-list.TextMenu #spatial { + order: 5; +} +#properties-list.TextMenu #behavior { + order: 6; +} +#properties-list.TextMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.TextMenu #light, +#properties-list.TextMenu #model, +#properties-list.TextMenu #zone, +#properties-list.TextMenu #web { + display: none; +} +/* items to hide */ +#properties-list.TextMenu .shape-group.shape-section.property.dropdown, +#properties-list.TextMenu .color-section.color-control1 { + display: none +} + + +/* Currently always hidden */ +#properties-list #polyvox { + display: none; +} + +.skybox-section { + display: none; +} diff --git a/applications/BodyPaint4/hifi/fonts/Raleway-Bold.ttf b/applications/BodyPaint4/hifi/fonts/Raleway-Bold.ttf new file mode 100644 index 0000000..38c099c Binary files /dev/null and b/applications/BodyPaint4/hifi/fonts/Raleway-Bold.ttf differ diff --git a/applications/BodyPaint4/hifi/fonts/Raleway-Light.ttf b/applications/BodyPaint4/hifi/fonts/Raleway-Light.ttf new file mode 100644 index 0000000..91aa0c7 Binary files /dev/null and b/applications/BodyPaint4/hifi/fonts/Raleway-Light.ttf differ diff --git a/applications/BodyPaint4/hifi/fonts/Raleway-Regular.ttf b/applications/BodyPaint4/hifi/fonts/Raleway-Regular.ttf new file mode 100644 index 0000000..e570a2d Binary files /dev/null and b/applications/BodyPaint4/hifi/fonts/Raleway-Regular.ttf differ diff --git a/applications/BodyPaint4/hifi/fonts/Raleway-SemiBold.ttf b/applications/BodyPaint4/hifi/fonts/Raleway-SemiBold.ttf new file mode 100644 index 0000000..ed0a8b9 Binary files /dev/null and b/applications/BodyPaint4/hifi/fonts/Raleway-SemiBold.ttf differ diff --git a/applications/BodyPaint4/hifi/fonts/fontawesome-webfont.ttf b/applications/BodyPaint4/hifi/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..d7994e1 Binary files /dev/null and b/applications/BodyPaint4/hifi/fonts/fontawesome-webfont.ttf differ diff --git a/applications/BodyPaint4/hifi/fonts/hifi-glyphs.ttf b/applications/BodyPaint4/hifi/fonts/hifi-glyphs.ttf new file mode 100644 index 0000000..558562c Binary files /dev/null and b/applications/BodyPaint4/hifi/fonts/hifi-glyphs.ttf differ diff --git a/applications/BodyPaint4/hifi/js/colpick.js b/applications/BodyPaint4/hifi/js/colpick.js new file mode 100644 index 0000000..01a3bc1 --- /dev/null +++ b/applications/BodyPaint4/hifi/js/colpick.js @@ -0,0 +1,520 @@ +/* +colpick Color Picker +Copyright 2013 Jose Vargas. Licensed under GPL license. Based on Stefan Petre's Color Picker www.eyecon.ro, dual licensed under the MIT and GPL licenses + +For usage and examples: colpick.com/plugin + */ + +(function ($) { + var colpick = function () { + var + tpl = '
#
R
G
B
H
S
B
', + defaults = { + showEvent: 'click', + onShow: function () {}, + onBeforeShow: function(){}, + onHide: function () {}, + onChange: function () {}, + onSubmit: function () {}, + colorScheme: 'light', + color: '3289c7', + livePreview: true, + flat: false, + layout: 'full', + submit: 1, + submitText: 'OK', + height: 156 + }, + //Fill the inputs of the plugin + fillRGBFields = function (hsb, cal) { + var rgb = hsbToRgb(hsb); + $(cal).data('colpick').fields + .eq(1).val(rgb.r).end() + .eq(2).val(rgb.g).end() + .eq(3).val(rgb.b).end(); + }, + fillHSBFields = function (hsb, cal) { + $(cal).data('colpick').fields + .eq(4).val(Math.round(hsb.h)).end() + .eq(5).val(Math.round(hsb.s)).end() + .eq(6).val(Math.round(hsb.b)).end(); + }, + fillHexFields = function (hsb, cal) { + $(cal).data('colpick').fields.eq(0).val(hsbToHex(hsb)); + }, + //Set the round selector position + setSelector = function (hsb, cal) { + $(cal).data('colpick').selector.css('backgroundColor', '#' + hsbToHex({h: hsb.h, s: 100, b: 100})); + $(cal).data('colpick').selectorIndic.css({ + left: parseInt($(cal).data('colpick').height * hsb.s/100, 10), + top: parseInt($(cal).data('colpick').height * (100-hsb.b)/100, 10) + }); + }, + //Set the hue selector position + setHue = function (hsb, cal) { + $(cal).data('colpick').hue.css('top', parseInt($(cal).data('colpick').height - $(cal).data('colpick').height * hsb.h/360, 10)); + }, + //Set current and new colors + setCurrentColor = function (hsb, cal) { + $(cal).data('colpick').currentColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + setNewColor = function (hsb, cal) { + $(cal).data('colpick').newColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + //Called when the new color is changed + change = function (ev) { + var cal = $(this).parent().parent(), col; + if (this.parentNode.className.indexOf('_hex') > 0) { + cal.data('colpick').color = col = hexToHsb(fixHex(this.value)); + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + } else if (this.parentNode.className.indexOf('_hsb') > 0) { + cal.data('colpick').color = col = fixHSB({ + h: parseInt(cal.data('colpick').fields.eq(4).val(), 10), + s: parseInt(cal.data('colpick').fields.eq(5).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(6).val(), 10) + }); + fillRGBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + } else { + cal.data('colpick').color = col = rgbToHsb(fixRGB({ + r: parseInt(cal.data('colpick').fields.eq(1).val(), 10), + g: parseInt(cal.data('colpick').fields.eq(2).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(3).val(), 10) + })); + fillHexFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + } + setSelector(col, cal.get(0)); + setHue(col, cal.get(0)); + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 0]); + }, + //Change style on blur and on focus of inputs + blur = function (ev) { + $(this).parent().removeClass('colpick_focus'); + }, + focus = function () { + $(this).parent().parent().data('colpick').fields.parent().removeClass('colpick_focus'); + $(this).parent().addClass('colpick_focus'); + }, + //Increment/decrement arrows functions + downIncrement = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var field = $(this).parent().find('input').focus(); + var current = { + el: $(this).parent().addClass('colpick_slider'), + max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255), + y: ev.pageY, + field: field, + val: parseInt(field.val(), 10), + preview: $(this).parent().parent().data('colpick').livePreview + }; + $(document).mouseup(current, upIncrement); + $(document).mousemove(current, moveIncrement); + }, + moveIncrement = function (ev) { + ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val - ev.pageY + ev.data.y, 10)))); + if (ev.data.preview) { + change.apply(ev.data.field.get(0), [true]); + } + return false; + }, + upIncrement = function (ev) { + change.apply(ev.data.field.get(0), [true]); + ev.data.el.removeClass('colpick_slider').find('input').focus(); + $(document).off('mouseup', upIncrement); + $(document).off('mousemove', moveIncrement); + return false; + }, + //Hue slider functions + downHue = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + y: $(this).offset().top + }; + $(document).on('mouseup touchend',current,upHue); + $(document).on('mousemove touchmove',current,moveHue); + + var pageY = ((ev.type == 'touchstart') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + current.cal.data('colpick') + .fields.eq(4).val(parseInt(360*(current.cal.data('colpick').height - (pageY - current.y))/current.cal.data('colpick').height, 10)) + .get(0), + [current.cal.data('colpick').livePreview] + ); + return false; + }, + moveHue = function (ev) { + var pageY = ((ev.type == 'touchmove') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + ev.data.cal.data('colpick') + .fields.eq(4).val(parseInt(360*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.y))))/ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upHue = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upHue); + $(document).off('mousemove touchmove',moveHue); + return false; + }, + //Color selector functions + downSelector = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + pos: $(this).offset() + }; + current.preview = current.cal.data('colpick').livePreview; + + $(document).on('mouseup touchend',current,upSelector); + $(document).on('mousemove touchmove',current,moveSelector); + + var payeX,pageY; + if(ev.type == 'touchstart') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } + + change.apply( + current.cal.data('colpick').fields + .eq(6).val(parseInt(100*(current.cal.data('colpick').height - (pageY - current.pos.top))/current.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100*(pageX - current.pos.left)/current.cal.data('colpick').height, 10)) + .get(0), + [current.preview] + ); + return false; + }, + moveSelector = function (ev) { + var payeX,pageY; + if(ev.type == 'touchmove') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } + + change.apply( + ev.data.cal.data('colpick').fields + .eq(6).val(parseInt(100*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.pos.top))))/ev.data.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100*(Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageX - ev.data.pos.left))))/ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upSelector = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upSelector); + $(document).off('mousemove touchmove',moveSelector); + return false; + }, + //Submit button + clickSubmit = function (ev) { + var cal = $(this).parent(); + var col = cal.data('colpick').color; + cal.data('colpick').origColor = col; + setCurrentColor(col, cal.get(0)); + cal.data('colpick').onSubmit(col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el); + }, + //Show/hide the color picker + show = function (ev) { + // Prevent the trigger of any direct parent + ev.stopPropagation(); + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').onBeforeShow.apply(this, [cal.get(0)]); + var pos = $(this).offset(); + var top = pos.top + this.offsetHeight; + var left = pos.left; + var viewPort = getViewport(); + var calW = cal.width(); + if (left + calW > viewPort.l + viewPort.w) { + left -= calW; + } + cal.css({left: left + 'px', top: top + 'px'}); + if (cal.data('colpick').onShow.apply(this, [cal.get(0)]) != false) { + cal.show(); + } + //Hide when user clicks outside + $('html').mousedown({cal:cal}, hide); + cal.mousedown(function(ev){ev.stopPropagation();}) + }, + hide = function (ev) { + if (ev.data.cal.data('colpick').onHide.apply(this, [ev.data.cal.get(0)]) != false) { + ev.data.cal.hide(); + } + $('html').off('mousedown', hide); + }, + getViewport = function () { + var m = document.compatMode == 'CSS1Compat'; + return { + l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), + w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth) + }; + }, + //Fix the values if the user enters a negative or high value + fixHSB = function (hsb) { + return { + h: Math.min(360, Math.max(0, hsb.h)), + s: Math.min(100, Math.max(0, hsb.s)), + b: Math.min(100, Math.max(0, hsb.b)) + }; + }, + fixRGB = function (rgb) { + return { + r: Math.min(255, Math.max(0, rgb.r)), + g: Math.min(255, Math.max(0, rgb.g)), + b: Math.min(255, Math.max(0, rgb.b)) + }; + }, + fixHex = function (hex) { + var len = 6 - hex.length; + if (len > 0) { + var o = []; + for (var i=0; i').attr('style','height:8.333333%; filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+')";'); + huebar.append(div); + } + } else { + stopList = stops.join(','); + huebar.attr('style','background:-webkit-linear-gradient(top,'+stopList+'); background: -o-linear-gradient(top,'+stopList+'); background: -ms-linear-gradient(top,'+stopList+'); background:-moz-linear-gradient(top,'+stopList+'); -webkit-linear-gradient(top,'+stopList+'); background:linear-gradient(to bottom,'+stopList+'); '); + } + cal.find('div.colpick_hue').on('mousedown touchstart',downHue); + options.newColor = cal.find('div.colpick_new_color'); + options.currentColor = cal.find('div.colpick_current_color'); + //Store options and fill with default color + cal.data('colpick', options); + fillRGBFields(options.color, cal.get(0)); + fillHSBFields(options.color, cal.get(0)); + fillHexFields(options.color, cal.get(0)); + setHue(options.color, cal.get(0)); + setSelector(options.color, cal.get(0)); + setCurrentColor(options.color, cal.get(0)); + setNewColor(options.color, cal.get(0)); + //Append to body if flat=false, else show in place + if (options.flat) { + cal.appendTo(this).show(); + cal.css({ + position: 'relative', + display: 'block' + }); + } else { + cal.appendTo(document.body); + $(this).on(options.showEvent, show); + cal.css({ + position:'absolute' + }); + } + } + }); + }, + //Shows the picker + showPicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + show.apply(this); + } + }); + }, + //Hides the picker + hidePicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + $('#' + $(this).data('colpickId')).hide(); + } + }); + }, + //Sets a color as new and current (default) + setColor: function(col, setCurrent) { + setCurrent = (typeof setCurrent === "undefined") ? 1 : setCurrent; + if (typeof col == 'string') { + col = hexToHsb(col); + } else if (col.r != undefined && col.g != undefined && col.b != undefined) { + col = rgbToHsb(col); + } else if (col.h != undefined && col.s != undefined && col.b != undefined) { + col = fixHSB(col); + } else { + return this; + } + return this.each(function(){ + if ($(this).data('colpickId')) { + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').color = col; + cal.data('colpick').origColor = col; + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + setHue(col, cal.get(0)); + setSelector(col, cal.get(0)); + + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 1]); + if(setCurrent) { + setCurrentColor(col, cal.get(0)); + } + } + }); + } + }; + }(); + //Color space convertions + var hexToRgb = function (hex) { + var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16); + return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)}; + }; + var hexToHsb = function (hex) { + return rgbToHsb(hexToRgb(hex)); + }; + var rgbToHsb = function (rgb) { + var hsb = {h: 0, s: 0, b: 0}; + var min = Math.min(rgb.r, rgb.g, rgb.b); + var max = Math.max(rgb.r, rgb.g, rgb.b); + var delta = max - min; + hsb.b = max; + hsb.s = max != 0 ? 255 * delta / max : 0; + if (hsb.s != 0) { + if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta; + else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta; + else hsb.h = 4 + (rgb.r - rgb.g) / delta; + } else hsb.h = -1; + hsb.h *= 60; + if (hsb.h < 0) hsb.h += 360; + hsb.s *= 100/255; + hsb.b *= 100/255; + return hsb; + }; + var hsbToRgb = function (hsb) { + var rgb = {}; + var h = hsb.h; + var s = hsb.s*255/100; + var v = hsb.b*255/100; + if(s == 0) { + rgb.r = rgb.g = rgb.b = v; + } else { + var t1 = v; + var t2 = (255-s)*v/255; + var t3 = (t1-t2)*(h%60)/60; + if(h==360) h = 0; + if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3} + else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3} + else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3} + else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3} + else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3} + else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3} + else {rgb.r=0; rgb.g=0; rgb.b=0} + } + return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; + }; + var rgbToHex = function (rgb) { + var hex = [ + rgb.r.toString(16), + rgb.g.toString(16), + rgb.b.toString(16) + ]; + $.each(hex, function (nr, val) { + if (val.length == 1) { + hex[nr] = '0' + val; + } + }); + return hex.join(''); + }; + var hsbToHex = function (hsb) { + return rgbToHex(hsbToRgb(hsb)); + }; + $.fn.extend({ + colpick: colpick.init, + colpickHide: colpick.hidePicker, + colpickShow: colpick.showPicker, + colpickSetColor: colpick.setColor + }); + $.extend({ + colpick:{ + rgbToHex: rgbToHex, + rgbToHsb: rgbToHsb, + hsbToHex: hsbToHex, + hsbToRgb: hsbToRgb, + hexToHsb: hexToHsb, + hexToRgb: hexToRgb + } + }); +})(jQuery); diff --git a/applications/BodyPaint4/hifi/js/eventBridgeLoader.js b/applications/BodyPaint4/hifi/js/eventBridgeLoader.js new file mode 100644 index 0000000..4117808 --- /dev/null +++ b/applications/BodyPaint4/hifi/js/eventBridgeLoader.js @@ -0,0 +1,19 @@ + +//public slots: +// void emitWebEvent(const QString& data); +// void emitScriptEvent(const QString& data); +// +//signals: +// void webEventReceived(const QString& data); +// void scriptEventReceived(const QString& data); +// + +var EventBridge; +var WebChannel; + +openEventBridge = function(callback) { + WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) { + EventBridge = WebChannel.objects.eventBridge; + callback(EventBridge); + }); +} diff --git a/applications/BodyPaint4/hifi/js/jquery-2.1.4.min.js b/applications/BodyPaint4/hifi/js/jquery-2.1.4.min.js new file mode 100644 index 0000000..49990d6 --- /dev/null +++ b/applications/BodyPaint4/hifi/js/jquery-2.1.4.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n(" + + + + + + + + diff --git a/applications/metadata.js b/applications/metadata.js index 4dbf730..2f53f1d 100644 --- a/applications/metadata.js +++ b/applications/metadata.js @@ -153,6 +153,24 @@ var metadata = { "applications": "icon": "home/appicon_i.png", "caption": "HOME" }, + { + "isActive": true, + "directory": "BodyPaint4", + "name": "Body Paint", + "description": "Paint with your finger in VR, or with mouse in desktop mode.
You can paint in free space, on yourself, or even on other avatars, it's the fastest way to put cat-ears on all your friends.", + "jsfile": "BodyPaint4/bodyPaint4.js", + "icon": "BodyPaint4/content/appicons/body-paint-i.svg", + "caption": "BODY PAINT" + }, + { + "isActive": true, + "directory": "tabletCam", + "name": "Camera Snap-Pro", + "description": "The Camera 'Snap-Pro' allows you to take high quality in-world photos and selfies (Low, Normal, 4K, and 'EXTREME' resolution). It has two cameras on the tablet, front-facing/rear-facing, and one detachable to allow more flexibility. It supports different aspect ratio formats: 8x10, 2x3, 9x16 and 'Square'. It comes with an Optical Zoom, a trigger on the VR hand controller and other features.", + "jsfile": "tabletCam/tabletCam_app.js", + "icon": "tabletCam/appIcons/snap-pro-i.svg", + "caption": "SNAP-PRO" + }, { "isActive": true, "directory": "cam360", diff --git a/applications/tabletCam/README.md b/applications/tabletCam/README.md new file mode 100644 index 0000000..57dd463 --- /dev/null +++ b/applications/tabletCam/README.md @@ -0,0 +1,77 @@ +# Tablet Cam + +## Note + +This was a personal project by [Zach Fox](https://github.com/zfox23/), a member of the High Fidelity Experiences Team. It has not undergone the same code review or QA process as the rest of the projects in this repository. + +## Description + +The Tablet Cam app allows you to **easily take selfies and regular photos in High Fidelity/Overte** using your Tablet or hand controllers! + +## Features + +- **Front-facing and rear-facing** cameras and flashes with optional **custom positioning** +- Works in **Desktop Mode and in VR Mode** +- **Persistent "Camera Roll"** for reviewing photos that you recently took +- Optical **zoom** +- Photo **quality settings**: Low, Normal, 4k, and _EXTREME_! +- **Aspect ratio settings**: 8x10, 2x3, 9x16, Square +- Editable photo directory + +## Changelog + +### v2.4 (2022-08-22) by Alezia Kurdis + +- Fixed a bug in HMD where the rear-facing camera was pointed to the tablet instead of the scene. +- Replaced the camera model by a new one (nicer and less heavy, 198k instead of 576k, less but more optimal polygons, 1 PBR material.) +- Rebranded the tool: Camera Snap-Pro +- Replaced the icons for something more easy to identify as a device to take picture ("camera"), as an advance version of the "Snap" app. +- Added a notice in the settings page to informe the user about the trigger on the thumbstick of the right VR hand controller. + +### v2.3 (2019-10-15) + +- Removed unnecessary tone curve correction present in captured Tablet Cam output image. +- Adjusted y-offset of Desktop-mode selfie cam to better serve Virtual You avatars. +- Added `"Head"` as a backup joint to `"HeadTop_End"` for certain operations. + +### v2.2 (2019-07-09) + +- Fixed a hack in which the secondary camera feed was darkened to compensate for it being rendered too light. +- If your camera looks too light, it means you have an older version of Interface (pre PR #15862) and should go back to the previous version of this script. +- If your camera looks too dark, it means you have a newer version of Interface (post PR #15682) and should update to the current version of this script. + +### v2.1 (2019-06-27) + +- We've moved the code for this project into a new remote folder, which necessitated a version bump. There are no changes to functionality. + +### v2.0 (2019-04) + +**I've made some huge changes in v2.0!** + +- In Desktop mode, when using the rear-facing camera and while you're using Third Person Camera, Tablet Cam will now be parented to Interface's Third Person Camera! +- The camera's viewpoint can now be detached from its default position! Snap a photo from a unique viewpoint. +- Fixed a bug that caused zoom settings to be saved incorrectly between restarts. +- Fixed a bug that caused the camera preview to show up as a corrupted image in HMD mode. +- Fixed a bug that caused the position of the rear-facing camera to be incorrect in HMD mode. +- Switched Overlay usage to Local Entities. +- Now you can use Tablet Cam in Desktop mode while "Desktop Tablet becomes Toolbar" is unchecked - although I'm not sure why you'd want to do that. :) +- Fixed some interface bugs. + +### v1.1 (2019-02) + +- Fixed a bug that caused Tablet Cam to erroneously appear on the Tablet after switching domains when it was previously active. + +### Tablet Cam v1.0 (2019-01) + +- Tablet Cam v1.0 is an update to Selfie Cam v1.0. It is a complete overhaul of the app. All of its features are new! + +### Selfie Cam v1.0 (2018-12) + +- Initial Release! + +## Attributions +- "snap.wav" from "Camera Shutter, Fast, A.wav" by InspectorJ (www.jshaw.co.uk) of Freesound.org +- "switchCams.svg" from "rotate camera" by Diego Naive from the Noun Project +- "orientation.svg" from "orientation" by Atif Arshad from the Noun Project +- "camera.fbx" from "Digital camera" by Nick Ladd: https://poly.google.com/view/4A3SYVh_smq +- "camera-a.svg" and "camera-i.svg" from "Selfie" by Path Lord from the Noun Project \ No newline at end of file diff --git a/applications/tabletCam/appIcons/snap-pro-a.svg b/applications/tabletCam/appIcons/snap-pro-a.svg new file mode 100644 index 0000000..69f9dd8 --- /dev/null +++ b/applications/tabletCam/appIcons/snap-pro-a.svg @@ -0,0 +1,226 @@ + + + + + snap-pro-a + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + snap-pro-a + + + + diff --git a/applications/tabletCam/appIcons/snap-pro-i.svg b/applications/tabletCam/appIcons/snap-pro-i.svg new file mode 100644 index 0000000..9559317 --- /dev/null +++ b/applications/tabletCam/appIcons/snap-pro-i.svg @@ -0,0 +1,226 @@ + + + + + snap-pro-i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + snap-pro-i + + + + diff --git a/applications/tabletCam/models/camera.fbx b/applications/tabletCam/models/camera.fbx new file mode 100644 index 0000000..5c63092 Binary files /dev/null and b/applications/tabletCam/models/camera.fbx differ diff --git a/applications/tabletCam/modules/appUi.js b/applications/tabletCam/modules/appUi.js new file mode 100644 index 0000000..5b2904a --- /dev/null +++ b/applications/tabletCam/modules/appUi.js @@ -0,0 +1,388 @@ +"use strict"; +/* global Tablet, Script */ +// +// libraries/appUi.js +// +// Created by Howard Stearns on 3/20/18. +// Modified by Zach Fox on 2019-04-13 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +function AppUi(properties) { + var request = Script.require('request').request; + /* Example development order: + 1. var AppUi = Script.require('appUi'); + 2. Put appname-i.svg, appname-a.svg in graphicsDirectory (where non-default graphicsDirectory can be added in #3). + 3. ui = new AppUi({buttonName: "APPNAME", home: "qml-or-html-path"}); + (And if converting an existing app, + define var tablet = ui.tablet, button = ui.button; as needed. + remove button.clicked.[dis]connect and tablet.remove(button).) + 4. Define onOpened and onClosed behavior in #3, if any. + (And if converting an existing app, remove screenChanged.[dis]connect.) + 5. Define onMessage and sendMessage in #3, if any. onMessage is wired/unwired on open/close. If you + want a handler to be "always on", connect it yourself at script startup. + (And if converting an existing app, remove code that [un]wires that message handling such as + fromQml/sendToQml or webEventReceived/emitScriptEvent.) + 6. (If converting an existing app, cleanup stuff that is no longer necessary, like references to button, tablet, + and use isOpen, open(), and close() as needed.) + 7. lint! + */ + var that = this; + function defaultButton(name, suffix) { + var base = that[name] || (that.buttonPrefix + suffix); + that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); // poor man's merge + } + + // Defaults: + that.tabletName = "com.highfidelity.interface.tablet.system"; + that.inject = ""; + that.graphicsDirectory = "icons/tablet-icons/"; // Where to look for button svgs. See below. + that.additionalAppScreens = []; + that.checkIsOpen = function checkIsOpen(type, tabletUrl) { // Are we active? Value used to set isOpen. + // Actual url may have prefix or suffix. + return that.currentVisibleUrl && + ((that.home.indexOf(that.currentVisibleUrl) > -1) || + (that.additionalAppScreens.indexOf(that.currentVisibleUrl) > -1)); + }; + that.setCurrentVisibleScreenMetadata = function setCurrentVisibleScreenMetadata(type, url) { + that.currentVisibleScreenType = type; + that.currentVisibleUrl = url; + }; + that.open = function open(optionalUrl, optionalInject) { // How to open the app. + var url = optionalUrl || that.home; + var inject = optionalInject || that.inject; + + if (that.isQMLUrl(url)) { + that.tablet.loadQMLSource(url); + } else { + that.tablet.gotoWebScreen(url, inject); + } + }; + // Opens some app on top of the current app (on desktop, opens new window) + that.openNewAppOnTop = function openNewAppOnTop(url, optionalInject) { + var inject = optionalInject || ""; + if (that.isQMLUrl(url)) { + that.tablet.loadQMLOnTop(url); + } else { + that.tablet.loadWebScreenOnTop(url, inject); + } + }; + that.close = function close() { // How to close the app. + that.currentVisibleUrl = ""; + // for toolbar-mode: go back to home screen, this will close the window. + that.tablet.gotoHomeScreen(); + }; + that.buttonActive = function buttonActive(isActive) { // How to make the button active (white). + that.button.editProperties({isActive: isActive}); + }; + that.isQMLUrl = function isQMLUrl(url) { + var type = /.qml$/.test(url) ? 'QML' : 'Web'; + return type === 'QML'; + }; + that.isCurrentlyOnQMLScreen = function isCurrentlyOnQMLScreen() { + return that.currentVisibleScreenType === 'QML'; + }; + + // + // START Notification Handling Defaults + // + that.messagesWaiting = function messagesWaiting(isWaiting) { // How to indicate a message light on button. + // Note that waitingButton doesn't have to exist unless someone explicitly calls this with isWaiting true. + that.button.editProperties({ + icon: isWaiting ? that.normalMessagesButton : that.normalButton, + activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton + }); + }; + that.notificationPollTimeout = [false]; + that.notificationPollTimeoutMs = [60000]; + that.notificationPollEndpoint = [false]; + that.notificationPollStopPaginatingConditionMet = [false]; + that.notificationDataProcessPage = function (data) { + return data; + }; + that.notificationPollCallback = [that.ignore]; + that.notificationPollCaresAboutSince = [false]; + that.notificationInitialCallbackMade = [false]; + that.notificationDisplayBanner = function (message) { + if (!that.isOpen) { + Window.displayAnnouncement(message); + } + }; + // + // END Notification Handling Defaults + // + + // Handlers + that.onScreenChanged = function onScreenChanged(type, url) { + // Set isOpen, wireEventBridge, set buttonActive as appropriate, + // and finally call onOpened() or onClosed() IFF defined. + that.setCurrentVisibleScreenMetadata(type, url); + + if (that.checkIsOpen(type, url)) { + that.wireEventBridge(true); + if (!that.isOpen) { + if (that.onOpened) { + that.onOpened(); + } + that.buttonActive(true); + that.isOpen = true; + } + } else { + // A different screen is now visible, or the tablet has been closed. + // Tablet visibility is controlled separately by `tabletShownChanged()` + that.wireEventBridge(false); + if (that.isOpen) { + if (that.onClosed) { + that.onClosed(); + } + that.buttonActive(false); + that.isOpen = false; + } + } + }; + + // Overwrite with the given properties: + Object.keys(properties).forEach(function (key) { + that[key] = properties[key]; + }); + + // + // START Notification Handling + // + + var currentDataPageToRetrieve = []; + var concatenatedServerResponse = []; + for (var i = 0; i < that.notificationPollEndpoint.length; i++) { + currentDataPageToRetrieve[i] = 1; + concatenatedServerResponse[i] = new Array(); + } + + var MAX_LOG_LENGTH_CHARACTERS = 300; + function requestCallback(error, response, optionalParams) { + var indexOfRequest = optionalParams.indexOfRequest; + var urlOfRequest = optionalParams.urlOfRequest; + + if (error || (response.status !== 'success')) { + print("Error: unable to complete request from URL. Error:", error || response.status); + startNotificationTimer(indexOfRequest); + return; + } + + if (!that.notificationPollStopPaginatingConditionMet[indexOfRequest] || + that.notificationPollStopPaginatingConditionMet[indexOfRequest](response)) { + startNotificationTimer(indexOfRequest); + + var notificationData; + if (concatenatedServerResponse[indexOfRequest].length) { + notificationData = concatenatedServerResponse[indexOfRequest]; + } else { + notificationData = that.notificationDataProcessPage[indexOfRequest](response); + } + console.debug(that.buttonName, + 'truncated notification data for processing:', + JSON.stringify(notificationData).substring(0, MAX_LOG_LENGTH_CHARACTERS)); + that.notificationPollCallback[indexOfRequest](notificationData); + that.notificationInitialCallbackMade[indexOfRequest] = true; + currentDataPageToRetrieve[indexOfRequest] = 1; + concatenatedServerResponse[indexOfRequest] = new Array(); + } else { + concatenatedServerResponse[indexOfRequest] = + concatenatedServerResponse[indexOfRequest].concat(that.notificationDataProcessPage[indexOfRequest](response)); + currentDataPageToRetrieve[indexOfRequest]++; + request({ + json: true, + uri: (urlOfRequest + "&page=" + currentDataPageToRetrieve[indexOfRequest]) + }, requestCallback, optionalParams); + } + } + + + var METAVERSE_BASE = Account.metaverseServerURL; + var MS_IN_SEC = 1000; + that.notificationPoll = function (i) { + if (!that.notificationPollEndpoint[i]) { + return; + } + + // User is "appearing offline" or is not logged in + if (GlobalServices.findableBy === "none" || Account.username === "Unknown user") { + // The notification polling will restart when the user changes their availability + // or when they log in, so it's not necessary to restart a timer here. + console.debug(that.buttonName + " Notifications: User is appearing offline or not logged in. " + + that.buttonName + " will poll for notifications when user logs in and has their availability " + + "set to not appear offline."); + return; + } + + var url = METAVERSE_BASE + that.notificationPollEndpoint[i]; + + var settingsKey = "notifications/" + that.notificationPollEndpoint[i] + "/lastPoll"; + var currentTimestamp = new Date().getTime(); + var lastPollTimestamp = Settings.getValue(settingsKey, currentTimestamp); + if (that.notificationPollCaresAboutSince[i]) { + url = url + "&since=" + lastPollTimestamp / MS_IN_SEC; + } + Settings.setValue(settingsKey, currentTimestamp); + + request({ + json: true, + uri: url + }, + requestCallback, + { + indexOfRequest: i, + urlOfRequest: url + }); + }; + + // This won't do anything if there isn't a notification endpoint set + for (i = 0; i < that.notificationPollEndpoint.length; i++) { + that.notificationPoll(i); + } + + function startNotificationTimer(indexOfRequest) { + that.notificationPollTimeout[indexOfRequest] = Script.setTimeout(function () { + that.notificationPoll(indexOfRequest); + }, that.notificationPollTimeoutMs[indexOfRequest]); + } + + function restartNotificationPoll() { + for (var j = 0; j < that.notificationPollEndpoint.length; j++) { + that.notificationInitialCallbackMade[j] = false; + if (that.notificationPollTimeout[j]) { + Script.clearTimeout(that.notificationPollTimeout[j]); + that.notificationPollTimeout[j] = false; + } + that.notificationPoll(j); + } + } + // + // END Notification Handling + // + + // Properties: + that.tablet = Tablet.getTablet(that.tabletName); + // Must be after we gather properties. + that.buttonPrefix = that.buttonPrefix || that.buttonName.toLowerCase() + "-"; + defaultButton('normalButton', 'i.svg'); + defaultButton('activeButton', 'a.svg'); + defaultButton('normalMessagesButton', 'i-msg.svg'); + defaultButton('activeMessagesButton', 'a-msg.svg'); + var buttonOptions = { + icon: that.normalButton, + activeIcon: that.activeButton, + text: that.buttonName + }; + // `TabletScriptingInterface` looks for the presence of a `sortOrder` key. + // What it SHOULD do is look to see if the value inside that key is defined. + // To get around the current code, we do this instead. + if (that.sortOrder) { + buttonOptions.sortOrder = that.sortOrder; + } + that.button = that.tablet.addButton(buttonOptions); + that.ignore = function ignore() { }; + that.hasOutboundEventBridge = false; + that.hasInboundQmlEventBridge = false; + that.hasInboundHtmlEventBridge = false; + // HTML event bridge uses strings, not objects. Here we abstract over that. + // (Although injected javascript still has to use JSON.stringify/JSON.parse.) + that.sendToHtml = function (messageObject) { + that.tablet.emitScriptEvent(JSON.stringify(messageObject)); + }; + that.fromHtml = function (messageString) { + var parsedMessage = JSON.parse(messageString); + parsedMessage.messageSrc = "HTML"; + that.onMessage(parsedMessage); + }; + that.sendMessage = that.ignore; + that.wireEventBridge = function wireEventBridge(on) { + // Uniquivocally sets that.sendMessage(messageObject) to do the right thing. + // Sets has*EventBridge and wires onMessage to the proper event bridge as appropriate, IFF onMessage defined. + var isCurrentlyOnQMLScreen = that.isCurrentlyOnQMLScreen(); + // Outbound (always, regardless of whether there is an inbound handler). + if (on) { + that.sendMessage = isCurrentlyOnQMLScreen ? that.tablet.sendToQml : that.sendToHtml; + that.hasOutboundEventBridge = true; + } else { + that.sendMessage = that.ignore; + that.hasOutboundEventBridge = false; + } + + if (!that.onMessage) { + return; + } + + // Inbound + if (on) { + if (isCurrentlyOnQMLScreen && !that.hasInboundQmlEventBridge) { + console.debug(that.buttonName, 'connecting', that.tablet.fromQml); + that.tablet.fromQml.connect(that.onMessage); + that.hasInboundQmlEventBridge = true; + } else if (!isCurrentlyOnQMLScreen && !that.hasInboundHtmlEventBridge) { + console.debug(that.buttonName, 'connecting', that.tablet.webEventReceived); + that.tablet.webEventReceived.connect(that.fromHtml); + that.hasInboundHtmlEventBridge = true; + } + } else { + if (that.hasInboundQmlEventBridge) { + console.debug(that.buttonName, 'disconnecting', that.tablet.fromQml); + that.tablet.fromQml.disconnect(that.onMessage); + that.hasInboundQmlEventBridge = false; + } + if (that.hasInboundHtmlEventBridge) { + console.debug(that.buttonName, 'disconnecting', that.tablet.webEventReceived); + that.tablet.webEventReceived.disconnect(that.fromHtml); + that.hasInboundHtmlEventBridge = false; + } + } + }; + that.isOpen = false; + // To facilitate incremental development, only wire onClicked to do something when "home" is defined in properties. + that.onClicked = that.home + ? function onClicked() { + // Call open() or close(), and reset type based on current home property. + if (that.isOpen) { + that.close(); + } else { + that.open(); + } + } : that.ignore; + that.onScriptEnding = function onScriptEnding() { + // Close if necessary, clean up any remaining handlers, and remove the button. + GlobalServices.myUsernameChanged.disconnect(restartNotificationPoll); + GlobalServices.findableByChanged.disconnect(restartNotificationPoll); + that.tablet.screenChanged.disconnect(that.onScreenChanged); + if (that.isOpen) { + that.close(); + that.onScreenChanged("", ""); + } + if (that.button) { + if (that.onClicked) { + that.button.clicked.disconnect(that.onClicked); + } + that.tablet.removeButton(that.button); + } + for (var i = 0; i < that.notificationPollTimeout.length; i++) { + if (that.notificationPollTimeout[i]) { + Script.clearInterval(that.notificationPollTimeout[i]); + that.notificationPollTimeout[i] = false; + } + } + }; + // Set up the handlers. + that.tablet.screenChanged.connect(that.onScreenChanged); + that.button.clicked.connect(that.onClicked); + Script.scriptEnding.connect(that.onScriptEnding); + GlobalServices.findableByChanged.connect(restartNotificationPoll); + GlobalServices.myUsernameChanged.connect(restartNotificationPoll); + if (that.buttonName === Settings.getValue("startUpApp")) { + Settings.setValue("startUpApp", ""); + Script.setTimeout(function () { + that.open(); + }, 1000); + } +} +module.exports = AppUi; diff --git a/applications/tabletCam/sounds/flashOff.wav b/applications/tabletCam/sounds/flashOff.wav new file mode 100644 index 0000000..fef7668 Binary files /dev/null and b/applications/tabletCam/sounds/flashOff.wav differ diff --git a/applications/tabletCam/sounds/flashOn.wav b/applications/tabletCam/sounds/flashOn.wav new file mode 100644 index 0000000..f7e95c9 Binary files /dev/null and b/applications/tabletCam/sounds/flashOn.wav differ diff --git a/applications/tabletCam/sounds/snap.wav b/applications/tabletCam/sounds/snap.wav new file mode 100644 index 0000000..d0143f1 Binary files /dev/null and b/applications/tabletCam/sounds/snap.wav differ diff --git a/applications/tabletCam/tabletCam_app.js b/applications/tabletCam/tabletCam_app.js new file mode 100644 index 0000000..a2c1939 --- /dev/null +++ b/applications/tabletCam/tabletCam_app.js @@ -0,0 +1,646 @@ +"use strict"; +/*jslint vars:true, plusplus:true, forin:true*/ +/*global Tablet, Script, */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 1 }] */ +// +// tabletCam_app.js +// +// Created by Zach Fox on 2019-04-14 +// Copyright 2022 Overte e.V. +// +// Camera with more advanced features than the SNAP application, with an higher resolution capability. +// +// Distributed under the Apache License, Version 2.0 +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function () { // BEGIN LOCAL_SCOPE + var AppUi = Script.require('./modules/appUi.js'); + + var secondaryCameraConfig = Render.getConfig("SecondaryCamera"); + var tabletCamAvatarEntity = false; + var previousNearClipDistance = false; + var previousFarClipDistance = false; + var previousvFoV = false; + var NEAR_CLIP_DISTANCE = 0.001; + var FAR_CLIP_DISTANCE = 16384; + var vFoV = Settings.getValue("tabletCam/vFoV", 60); + var secondaryCameraResolutionWidth = 1000; + var secondaryCameraResolutionHeight = secondaryCameraResolutionWidth / aspectRatio; + + var PREVIEW_SHORT_SIDE_RESOLUTION = 400; + var secondaryCameraResolutionPreviewWidth = PREVIEW_SHORT_SIDE_RESOLUTION; + var secondaryCameraResolutionPreviewHeight = PREVIEW_SHORT_SIDE_RESOLUTION / aspectRatio; + + var CAMERA_ENTITY_NAME = "CAMERA SNAP-PRO OV-22"; + var APPLICATION_CAPTION = "SNAP-PRO"; + + var tabletCamRunning = false; + + var TABLET_CAM_ENTITY_PROPERTIES = { + "type": "Model", + "modelURL": Script.resolvePath("models/camera.fbx"), + "shapeType": "simple-hull", + "dimensions": {"x":0.1600, "y":0.1021, "z":0.1137}, + "damping": 0, + "angularDamping": 0, + "shape": "Cube", + "isVisibleInSecondaryCamera": false, + "name": CAMERA_ENTITY_NAME, + "grab": { + "grabbable": true + }, + "registrationPoint": { + "x": 0.42, + "y": 0.4, + "z": 0 + } + }; + + function enableTabletCam() { + if (!tabletCamRunning) { + wireSignals(true); + + setTakePhotoControllerMappingStatus(true); + + secondaryCameraConfig.enableSecondaryCameraRenderConfigs(true); + setSnapshotQuality(snapshotQuality); + var props = TABLET_CAM_ENTITY_PROPERTIES; + var dynamicProps = getDynamicTabletCamAvatarEntityProperties(); + for (var key in dynamicProps) { + props[key] = dynamicProps[key]; + } + tabletCamAvatarEntity = Entities.addEntity(props, "avatar"); + previousFarClipDistance = secondaryCameraConfig.farClipPlaneDistance; + previousNearClipDistance = secondaryCameraConfig.nearClipPlaneDistance; + previousvFoV = secondaryCameraConfig.vFoV; + secondaryCameraConfig.nearClipPlaneDistance = NEAR_CLIP_DISTANCE; + secondaryCameraConfig.farClipPlaneDistance = FAR_CLIP_DISTANCE; + secondaryCameraConfig.vFoV = vFoV; + + secondaryCameraConfig.attachedEntityId = tabletCamAvatarEntity; + tabletCamRunning = true; + } + + updateTabletCamLocalEntity(); + + // Remove the existing tabletCamAvatarEntity model from the domain if one exists. + // It's easy for this to happen if the user crashes while the Tablet Cam is on. + // We do this down here (after the new one is rezzed) so that we don't accidentally delete + // the newly-rezzed model. + var entityIDs = Entities.findEntitiesByName(CAMERA_ENTITY_NAME, MyAvatar.position, 100, false); + entityIDs.forEach(function (currentEntityID) { + var currentEntityOwner = Entities.getEntityProperties(currentEntityID, ['owningAvatarID']).owningAvatarID; + if (currentEntityOwner === MyAvatar.sessionUUID && currentEntityID !== tabletCamAvatarEntity) { + Entities.deleteEntity(currentEntityID); + } + }); + } + + var frontCamInUse = Settings.getValue("tabletCam/frontCamInUse", true); + function switchCams(forceFrontCamValue) { + if (!tabletCamAvatarEntity || (!!HMD.tabletID && !tabletCamLocalEntity)) { + console.log("User tried to switch cams, but TabletCam wasn't ready!"); + return; + } + + frontCamInUse = forceFrontCamValue || !frontCamInUse; + Settings.setValue("tabletCam/frontCamInUse", frontCamInUse); + + var newTabletCamAvatarEntityProps = getDynamicTabletCamAvatarEntityProperties(); + Entities.editEntity(tabletCamAvatarEntity, newTabletCamAvatarEntityProps); + + updateTabletCamLocalEntity(); + } + + function disableTabletCam() { + function deleteTabletCamAvatarEntity() { + if (flash) { + Entities.deleteEntity(flash); + flash = false; + } + + if (tabletCamAvatarEntity) { + Entities.deleteEntity(tabletCamAvatarEntity); + tabletCamAvatarEntity = false; + detached = false; // TO BE CONFIRMED + Settings.setValue("tabletCam/detached", detached); // TO BE CONFIRMED + } + } + + wireSignals(false); + + setTakePhotoControllerMappingStatus(false); + + if (tabletCamRunning) { + secondaryCameraConfig.farClipPlaneDistance = previousFarClipDistance; + secondaryCameraConfig.nearClipPlaneDistance = previousNearClipDistance; + secondaryCameraConfig.vFoV = previousvFoV; + secondaryCameraConfig.attachedEntityId = false; + secondaryCameraConfig.enableSecondaryCameraRenderConfigs(false); + } + + deleteTabletCamAvatarEntity(); + + if (tabletCamLocalEntity) { + Entities.deleteEntity(tabletCamLocalEntity); + tabletCamLocalEntity = false; + detached = false; // TO BE CONFIRMED + Settings.setValue("tabletCam/detached", detached); // TO BE CONFIRMED + } + + tabletCamRunning = false; + } + + var tabletCamLocalEntityWidth = 0.282; + var tabletCamLocalEntityHeight = 0.282; + var tabletCamLocalEntityDim = { x: tabletCamLocalEntityWidth, y: tabletCamLocalEntityHeight }; + var tabletCamLocalEntity = false; + var LOCAL_ENTITY_STATIC_PROPERTIES = { + type: "Image", + imageURL: "resource://spectatorCameraFrame", + emissive: true, + grab: { + "grabbable": false + }, + alpha: 1, + triggerable: false + }; + function updateTabletCamLocalEntity() { + if (!HMD.tabletID) { + return; + } + + if (tabletCamLocalEntity) { + Entities.deleteEntity(tabletCamLocalEntity); + tabletCamLocalEntity = false; + } + var props = LOCAL_ENTITY_STATIC_PROPERTIES; + props.dimensions = tabletCamLocalEntityDim; + if (!!HMD.tabletID) { + props.parentID = HMD.tabletID; + props.localPosition = [0, 0.0225, -0.008]; + if (frontCamInUse) { + props.localRotation = Quat.fromVec3Degrees([0, 180, 180]); + } else { + props.localRotation = Quat.fromVec3Degrees([0, 0, 180]); + } + } else { + props.parentID = Uuid.NULL; + props.localPosition = inFrontOf(0.5); + props.localRotation = MyAvatar.orientation; + } + + tabletCamLocalEntity = Entities.addEntity(props, "local"); + } + + function onDomainChanged() { + if (tabletCamRunning) { + disableTabletCam(); + } + } + + function tabletVisibilityChanged() { + if (!ui.tablet.tabletShown && ui.isOpen) { + ui.close(); + } + } + + var flash = Settings.getValue("tabletCam/flashEnabled", false);; + function setFlashStatus(enabled) { + if (!tabletCamAvatarEntity) { + return; + } + + Settings.setValue("tabletCam/flashEnabled", enabled); + + var cameraPosition = Entities.getEntityProperties(tabletCamAvatarEntity, ["positon"]).position; + if (enabled) { + Audio.playSound(SOUND_FLASH_ON, { + position: cameraPosition, + localOnly: true, + volume: 0.8 + }); + flash = Entities.addEntity({ + "collisionless": true, + "collidesWith": "", + "collisionMask": 0, + "color": { + "blue": 173, + "green": 252, + "red": 255 + }, + "cutoff": 90, + "dimensions": { + "x": 4, + "y": 4, + "z": 4 + }, + "dynamic": false, + "falloffRadius": 0.20000000298023224, + "intensity": 27, + "isSpotlight": true, + "localRotation": { w: 1, x: 0, y: 0, z: 0 }, + "localPosition": { x: 0, y: 0, z: -0.005 }, + "name": "Tablet Camera Flash", + "type": "Light", + "parentID": tabletCamAvatarEntity, + }, "avatar"); + } else { + if (flash) { + Audio.playSound(SOUND_FLASH_OFF, { + position: cameraPosition, + localOnly: true, + volume: 0.8 + }); + Entities.deleteEntity(flash); + flash = false; + } + } + } + + function takePhoto() { + var tabletCamAvatarEntityPosition = Entities.getEntityProperties(tabletCamAvatarEntity, ["position"]).position; + Audio.playSound(SOUND_SNAPSHOT, { + position: { x: tabletCamAvatarEntityPosition.x, y: tabletCamAvatarEntityPosition.y, z: tabletCamAvatarEntityPosition.z }, + localOnly: true, + volume: 0.2 + }); + Window.takeSecondaryCameraSnapshot(); + } + + function maybeTakePhoto() { + if (tabletCamAvatarEntity) { + secondaryCameraConfig.resetSizeSpectatorCamera(secondaryCameraResolutionWidth, secondaryCameraResolutionHeight); + // Wait a moment before taking the photo for the resolution to update + Script.setTimeout(function () { + takePhoto(); + }, 250); + } + } + + var snapshotQuality = Settings.getValue("tabletCam/quality", "normal"); + function setSnapshotQuality(quality) { + snapshotQuality = quality; + Settings.setValue("tabletCam/quality", snapshotQuality); + + var shortSideTargetResolution = 1000; + if (snapshotQuality === "low") { + shortSideTargetResolution = 500; + } else if (snapshotQuality === "normal") { + shortSideTargetResolution = 1000; + } else if (snapshotQuality === "high") { + shortSideTargetResolution = 2160; + } else if (snapshotQuality === "extreme") { + shortSideTargetResolution = 4320; + } + + if (tallOrientation && !HMD.active) { + secondaryCameraResolutionWidth = shortSideTargetResolution; + secondaryCameraResolutionHeight = secondaryCameraResolutionWidth / aspectRatio; + + secondaryCameraResolutionPreviewWidth = PREVIEW_SHORT_SIDE_RESOLUTION; + secondaryCameraResolutionPreviewHeight = secondaryCameraResolutionPreviewWidth / aspectRatio; + } else { + secondaryCameraResolutionHeight = shortSideTargetResolution; + secondaryCameraResolutionWidth = secondaryCameraResolutionHeight / aspectRatio; + + secondaryCameraResolutionPreviewHeight = PREVIEW_SHORT_SIDE_RESOLUTION; + secondaryCameraResolutionPreviewWidth = secondaryCameraResolutionPreviewHeight / aspectRatio; + } + + secondaryCameraConfig.resetSizeSpectatorCamera(secondaryCameraResolutionPreviewWidth, secondaryCameraResolutionPreviewHeight); + } + + var aspectRatio = parseFloat(Settings.getValue("tabletCam/aspectRatio", "0.8")); + function setAspectRatio(ratio) { + aspectRatio = ratio; + Settings.setValue("tabletCam/aspectRatio", aspectRatio); + + setSnapshotQuality(snapshotQuality); + } + + var tallOrientation = Settings.getValue("tabletCam/tallOrientation", true); + function setOrientation(orientation) { + tallOrientation = orientation; + Settings.setValue("tabletCam/tallOrientation", tallOrientation); + + setSnapshotQuality(snapshotQuality); + } + + function photoDirChanged(snapshotPath) { + Window.browseDirChanged.disconnect(photoDirChanged); + if (snapshotPath !== "") { // not cancelled + Snapshot.setSnapshotsLocation(snapshotPath); + ui.sendMessage({ + method: "photoDirectoryChanged", + photoDirectory: snapshotPath + }); + } + } + + function fromQml(message) { + switch (message.method) { + case 'switchCams': + switchCams(message.frontCamInUse); + break; + case 'switchOrientation': + setOrientation(!tallOrientation); + break; + case 'setFlashStatus': + setFlashStatus(message.enabled); + break; + case 'takePhoto': + maybeTakePhoto(); + break; + case 'updateCameravFoV': + vFoV = message.vFoV; + secondaryCameraConfig.vFoV = vFoV; + Settings.setValue("tabletCam/vFoV", vFoV); + break; + case 'setSnapshotQuality': + setSnapshotQuality(message.quality); + break; + case 'setAspectRatio': + setAspectRatio(message.aspectRatio); + break; + case 'activeViewChanged': + if (message.activeView === "settingsView" || message.activeView === "reviewView") { + //disableTabletCam(); + } else { + enableTabletCam(); + } + break; + case 'setPhotoDirectory': + Window.browseDirChanged.connect(photoDirChanged); + Window.browseDirAsync("Choose Photo Directory", "", ""); + break; + case 'setDetached': + detached = message.detached; + Settings.setValue("tabletCam/detached", detached); + var newTabletCamAvatarEntityProps = getDynamicTabletCamAvatarEntityProperties(); + Entities.editEntity(tabletCamAvatarEntity, newTabletCamAvatarEntityProps); + break; + default: + print('Unrecognized message from TabletCam.qml.'); + } + } + + function setTakePhotoControllerMappingStatus(status) { + if (!takePhotoControllerMapping) { + return; + } + if (status) { + takePhotoControllerMapping.enable(); + } else { + takePhotoControllerMapping.disable(); + } + } + + var takePhotoControllerMapping; + var takePhotoControllerMappingName = 'Hifi-TabletCam-Mapping-TakePhoto'; + function registerTakePhotoControllerMapping() { + takePhotoControllerMapping = Controller.newMapping(takePhotoControllerMappingName); + if (controllerType === "OculusTouch") { + takePhotoControllerMapping.from(Controller.Standard.RS).to(function (value) { + if (value === 1.0) { + maybeTakePhoto(); + } + return; + }); + } else if (controllerType === "Vive") { + takePhotoControllerMapping.from(Controller.Standard.RightPrimaryThumb).to(function (value) { + if (value === 1.0) { + maybeTakePhoto(); + } + return; + }); + } + } + + var controllerType = "Other"; + function registerButtonMappings() { + var VRDevices = Controller.getDeviceNames().toString(); + if (VRDevices) { + if (VRDevices.indexOf("Vive") !== -1) { + controllerType = "Vive"; + } else if (VRDevices.indexOf("OculusTouch") !== -1) { + controllerType = "OculusTouch"; + } else { + return; // Neither Vive nor Touch detected + } + } + + if (!takePhotoControllerMapping) { + registerTakePhotoControllerMapping(); + } + } + + function onHMDChanged(isHMDMode) { + registerButtonMappings(); + disableTabletCam(); + } + + var cameraRollPaths = JSON.parse(Settings.getValue("tabletCam/cameraRollPaths", '{"paths": []}')); + function onStillSnapshotTaken(path) { + var tempObject = {}; + tempObject.imagePath = "file:///" + path; + + cameraRollPaths.paths.unshift(tempObject); + if (cameraRollPaths.paths.length > 15) { + cameraRollPaths.paths.pop(); + } + Settings.setValue("tabletCam/cameraRollPaths", JSON.stringify(cameraRollPaths)); + + secondaryCameraConfig.resetSizeSpectatorCamera(secondaryCameraResolutionPreviewWidth, secondaryCameraResolutionPreviewHeight); + ui.sendMessage({ + method: 'stillSnapshotTaken', + lastStillSnapshotPath: tempObject.imagePath + }); + } + + var signalsWired = false; + function wireSignals(shouldWire) { + if (signalsWired === shouldWire) { + return; + } + + signalsWired = shouldWire; + + if (shouldWire) { + Window.stillSnapshotTaken.connect(onStillSnapshotTaken); + } else { + Window.stillSnapshotTaken.disconnect(onStillSnapshotTaken); + } + } + + function inFrontOf(distance, position, orientation) { + return Vec3.sum(position || MyAvatar.position, + Vec3.multiply(distance, Quat.getForward(orientation || MyAvatar.orientation))); + } + + var detached = false; + Settings.setValue("tabletCam/detached", detached); + function getDynamicTabletCamAvatarEntityProperties() { + var dynamicProps = { + dimensions: {"x":0.1600, "y":0.1021, "z":0.1137} + }; + + if (detached) { + //print("DETACHED MODE"); + dynamicProps.collisionless = false; + dynamicProps.ignoreForCollisions = false; + dynamicProps.grab = { + "grabbable": true, + "equippableLeftRotation": { + "x": -0.0000152587890625, + "y": -0.0000152587890625, + "z": -0.0000152587890625, + "w": 1 + }, + "equippableRightRotation": { + "x": -0.0000152587890625, + "y": -0.0000152587890625, + "z": -0.0000152587890625, + "w": 1 + } + }; + dynamicProps.visible = true; + dynamicProps.parentID = Uuid.NULL; + dynamicProps.parentJointIndex = 65535; + dynamicProps.triggerable = true; + if (tabletCamAvatarEntity) { + var currentProps = Entities.getEntityProperties(tabletCamAvatarEntity, ["position", "rotation"]); + if (!!HMD.tabletID) { + dynamicProps.position = inFrontOf(0.2, currentProps.position, currentProps.rotation); + } else { + dynamicProps.position = currentProps.position; + } + dynamicProps.rotation = currentProps.rotation; + } else { + dynamicProps.position = inFrontOf(0.5); + dynamicProps.rotation = MyAvatar.orientation; + } + dynamicProps.velocity = [0, 0, 0]; + dynamicProps.angularVelocity = [0, 0, 0]; + } else { + dynamicProps.triggerable = false; + dynamicProps.collisionless = true; + dynamicProps.ignoreForCollisions = true; + dynamicProps.grab = { + "grabbable": false + }; + dynamicProps.visible = false; + + if (!!HMD.tabletID) { + //print("TABLET MODE"); + dynamicProps.parentID = HMD.tabletID; + dynamicProps.parentJointIndex = 65535; + dynamicProps.dimensions = [0.01, 0.01, 0.01]; + } else { + //print("DESKTOP USER CAMERA MODE"); + var cameraMode = Camera.mode; + // If: + // - User is in third person mode + // - User is using the rear-facing camera + if (cameraMode !== "first person" && !frontCamInUse) { + dynamicProps.parentID = MyAvatar.sessionUUID; + dynamicProps.parentJointIndex = MyAvatar.getJointIndex("_CAMERA_MATRIX"); + } else { + dynamicProps.parentID = MyAvatar.sessionUUID; + var jointIndex = MyAvatar.getJointIndex("HeadTop_End"); + if (jointIndex === -1) { + jointIndex = MyAvatar.getJointIndex("Head"); + } + dynamicProps.parentJointIndex = jointIndex; + } + } + + dynamicProps.localPosition = { + "x": 0, + "y": !!HMD.tabletID ? 0.215 : (frontCamInUse ? -0.03 : (Camera.mode !== "first person" ? 0 : -0.02)), + "z": !!HMD.tabletID ? (frontCamInUse ? -0.02 : 0.1) : (frontCamInUse ? 1 : (Camera.mode !== "first person" ? 0 : 0.05)) + }; + + if (!!HMD.tabletID) { + dynamicProps.localRotation = { + "x": 0, + "y": frontCamInUse ? 0 : 1, + "z": 0, + "w": frontCamInUse ? 1 : 0 + }; + } else { + dynamicProps.localRotation = { + "x": 0, + "y": frontCamInUse || (!frontCamInUse && Camera.mode !== "first person") ? 0 : 1, + "z": 0, + "w": frontCamInUse || (!frontCamInUse && Camera.mode !== "first person") ? 1 : 0 + }; + } + } + + return dynamicProps; + } + + function onModeUpdated(newMode) { + if (tabletCamAvatarEntity) { + var newTabletCamAvatarEntityProps = getDynamicTabletCamAvatarEntityProperties(); + Entities.editEntity(tabletCamAvatarEntity, newTabletCamAvatarEntityProps); + } + } + + function onClosed() { + if (!detached) { + disableTabletCam(); + } + + if (tabletCamLocalEntity) { + Entities.deleteEntity(tabletCamLocalEntity); + tabletCamLocalEntity = false; + } + } + + function buttonActive(isActive) { + ui.button.editProperties({isActive: isActive || tabletCamRunning}); + } + + var ui; + function startup() { + ui = new AppUi({ + buttonName: APPLICATION_CAPTION, + home: Script.resolvePath("./ui/TabletCam.qml"), + // Selfie by Path Lord from the Noun Project + graphicsDirectory: Script.resolvePath("appIcons/"), + onOpened: enableTabletCam, + onClosed: onClosed, + onMessage: fromQml, + buttonActive: buttonActive + }); + + Window.domainChanged.connect(onDomainChanged); + ui.tablet.tabletShownChanged.connect(tabletVisibilityChanged); + HMD.displayModeChanged.connect(onHMDChanged); + Camera.modeUpdated.connect(onModeUpdated); + + registerButtonMappings(); + } + startup(); + + function shutdown() { + disableTabletCam(); + Window.domainChanged.disconnect(onDomainChanged); + ui.tablet.tabletShownChanged.disconnect(tabletVisibilityChanged); + HMD.displayModeChanged.disconnect(onHMDChanged); + Camera.modeUpdated.disconnect(onModeUpdated); + if (takePhotoControllerMapping) { + takePhotoControllerMapping.disable(); + } + wireSignals(false); + } + Script.scriptEnding.connect(shutdown); + + // "Camera Shutter, Fast, A.wav" by InspectorJ (www.jshaw.co.uk) of Freesound.org + var SOUND_SNAPSHOT = SoundCache.getSound(Script.resolvePath("sounds/snap.wav")); + var SOUND_FLASH_ON = SoundCache.getSound(Script.resolvePath("sounds/flashOn.wav")); + var SOUND_FLASH_OFF = SoundCache.getSound(Script.resolvePath("sounds/flashOff.wav")); +}()); // END LOCAL_SCOPE diff --git a/applications/tabletCam/ui/TabletCam.qml b/applications/tabletCam/ui/TabletCam.qml new file mode 100644 index 0000000..40103a7 --- /dev/null +++ b/applications/tabletCam/ui/TabletCam.qml @@ -0,0 +1,907 @@ +// +// TabletCam.qml +// qml/hifi +// +// Tablet Cam v2.2 +// +// Created by Zach Fox on 2019-04-14 +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 as Hifi +import QtQuick 2.7 +import QtQuick.Controls 2.3 +import stylesUit 1.0 as HifiStylesUit +import controlsUit 1.0 as HifiControlsUit + +Rectangle { + id: root; + property bool flashEnabled: Settings.getValue("tabletCam/flashEnabled", false); + property string snapshotQuality: Settings.getValue("tabletCam/quality", "normal"); + property real aspectRatio: Settings.getValue("tabletCam/aspectRatio", (8 / 10)); + property bool detached: Settings.getValue("tabletCam/detached", false); + property bool frontCamInUse: Settings.getValue("tabletCam/frontCamInUse", true); + property string activeView: "mainView"; + + HifiStylesUit.HifiConstants { id: hifi; } + color: hifi.colors.black; + + onFlashEnabledChanged: { + sendToScript({method: 'setFlashStatus', enabled: root.flashEnabled}); + } + + onDetachedChanged: { + sendToScript({method: 'setDetached', detached: root.detached}); + } + + onFrontCamInUseChanged: { + sendToScript({method: 'switchCams', frontCamInUse: root.frontCamInUse}); + } + + onActiveViewChanged: { + root.flashEnabled = false; + sendToScript({method: 'activeViewChanged', activeView: root.activeView}); + + if (root.activeView === "settingsView") { + photoDirectoryTextField.text = Settings.getValue("snapshotsLocation", ""); + } + } + + Item { + id: mainView; + visible: root.activeView === "mainView"; + anchors.fill: parent; + + Rectangle { + id: helpTextContainer; + visible: !!Settings.getValue('tabletCam/firstRun', true) && HMD.active; + width: parent.width; + height: topBarContainer_main.height; + anchors.left: parent.left; + anchors.top: parent.top; + color: "#121212"; + + HifiStylesUit.RalewaySemiBold { + text: "Try clicking right thumbstick for photos!"; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 8; + anchors.verticalCenter: parent.verticalCenter; + size: 22; + // Style + color: hifi.colors.white; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + wrapMode: Text.Wrap; + } + + HifiControlsUit.Button { + text: "OK"; + colorScheme: hifi.colorSchemes.dark; + color: hifi.buttons.blue; + anchors.verticalCenter: parent.verticalCenter; + anchors.right: parent.right; + anchors.rightMargin: 8; + width: 50; + height: 35; + onClicked: { + helpTextContainer.visible = false; + Settings.setValue('tabletCam/firstRun', false); + } + } + } + + Rectangle { + id: topBarContainer_main; + visible: !helpTextContainer.visible; + width: parent.width; + height: 42; + anchors.left: parent.left; + anchors.top: parent.top; + color: "#121212"; + + HifiControlsUit.CheckBox { + id: detachCheckbox; + text: "Detach" + checked: root.detached; + boxSize: 24; + height: 32; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: parent.left; + anchors.leftMargin: 8; + onClicked: { + root.detached = checked; + } + } + + HifiControlsUit.GlyphButton { + id: flashButton; + height: 26; + width: height; + anchors.verticalCenter: parent.verticalCenter; + anchors.right: fakeFlash.left; + anchors.rightMargin: 8; + glyph: hifi.glyphs.lightning; + color: root.flashEnabled ? hifi.buttons.blue : hifi.buttons.none; + onClicked: { + root.flashEnabled = !root.flashEnabled; + } + } + + Rectangle { + id: fakeCamera; + width: 34; + height: width; + radius: width; + anchors.centerIn: parent; + color: hifi.colors.black; + + Rectangle { + visible: root.frontCamInUse && !root.detached; + width: parent.width - 12; + height: width; + radius: width; + anchors.centerIn: parent; + color: "#230000"; + } + } + + Rectangle { + id: fakeFlash; + width: 12; + height: width; + radius: width; + anchors.verticalCenter: fakeCamera.verticalCenter; + anchors.right: fakeCamera.left; + anchors.rightMargin: 4; + color: root.flashEnabled && root.frontCamInUse ? "#fffcad" : "#000000"; + } + + Image { + id: switchCams; + height: 26; + width: height; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: fakeCamera.right; + anchors.leftMargin: 8; + source: "./images/switchCams.svg"; // rotate camera by Diego Naive from the Noun Project + mipmap: true; + MouseArea { + anchors.fill: parent; + enabled: !root.detached; + + onClicked: { + root.frontCamInUse = !root.frontCamInUse; + } + } + } + + HifiControlsUit.GlyphButton { + id: settingsButton; + height: 26; + width: height; + anchors.verticalCenter: parent.verticalCenter; + anchors.right: parent.right; + anchors.rightMargin: 8; + glyph: hifi.glyphs.settings; + color: hifi.buttons.none; + onClicked: { + root.activeView = "settingsView"; + } + } + } + + Rectangle { + visible: !secondaryCameraPreview.visible && HMD.tabletID !== "{00000000-0000-0000-0000-000000000000}"; + anchors.fill: secondaryCameraPreview; + color: hifi.colors.white; + } + + // Secondary Camera Preview + Hifi.ResourceImageItem { + id: secondaryCameraPreview; + visible: HMD.tabletID !== "{00000000-0000-0000-0000-000000000000}"; + url: "resource://spectatorCameraFrame"; + ready: visible; + mirrorVertically: true; + anchors.top: topBarContainer_main.bottom; + anchors.bottom: bottomBarContainer_main.top; + anchors.left: parent.left; + anchors.right: parent.right; + onVisibleChanged: { + update(); + } + } + + Rectangle { + id: bottomBarContainer_main; + height: 88; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.right: parent.right; + color: "#121212"; + + Item { + id: fieldOfView; + anchors.left: parent.left; + anchors.leftMargin: 12; + anchors.verticalCenter: parent.verticalCenter; + anchors.right: takeSnapshotButton.left; + anchors.rightMargin: 12; + height: 35; + + HifiControlsUit.GlyphButton { + id: resetvFoV; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: parent.left; + height: parent.height - 8; + width: height; + glyph: hifi.glyphs.reload; + onClicked: { + fieldOfViewSlider.value = 60.0; + } + } + + HifiControlsUit.Slider { + id: fieldOfViewSlider; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.right: parent.right; + anchors.left: resetvFoV.right; + anchors.leftMargin: 8; + colorScheme: hifi.colorSchemes.dark; + from: 8.0; + to: 120.0; + value: (to - Settings.getValue("tabletCam/vFoV", 60.0) + from); + stepSize: 1; + + onValueChanged: { + sendToScript({method: 'updateCameravFoV', vFoV: to - value + from}); + } + onPressedChanged: { + if (!pressed) { + sendToScript({method: 'updateCameravFoV', vFoV: to - value + from}); + } + } + } + } + + Rectangle { + id: takeSnapshotButton; + color: "#EA4C5F"; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.verticalCenter: parent.verticalCenter; + height: 72; + width: height; + radius: height; + border.width: 3; + border.color: hifi.colors.white; + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.color = "#C62147"; + } + onExited: { + parent.color = "#EA4C5F"; + } + onClicked: { + if (HMD.tabletID !== "{00000000-0000-0000-0000-000000000000}") { + secondaryCameraPreview.visible = false; + } + sendToScript({method: 'takePhoto'}); + } + } + } + + Image { + visible: !HMD.active; + source: "./images/orientation.svg"; // orientation by Atif Arshad from the Noun Project + height: 24; + width: height; + anchors.left: takeSnapshotButton.right; + anchors.leftMargin: 24; + anchors.verticalCenter: parent.verticalCenter; + + MouseArea { + anchors.fill: parent; + onClicked: { + sendToScript({method: 'switchOrientation'}); + } + } + } + + Rectangle { + id: galleryButton; + anchors.right: parent.right; + anchors.rightMargin: 12; + anchors.verticalCenter: parent.verticalCenter; + height: 72; + width: height; + color: hifi.colors.black; + + Image { + id: galleryButtonImage; + source: JSON.parse(Settings.getValue("tabletCam/cameraRollPaths", '{"paths": ["imagePath": ""]}')).paths[0].imagePath; + fillMode: Image.PreserveAspectCrop; + anchors.fill: parent; + mipmap: true; + } + + MouseArea { + enabled: galleryButtonImage.source !== ""; + anchors.fill: parent; + onClicked: { + cameraRollSwipeView.setCurrentIndex(0); + cameraRollModel.clear(); + + var settingsString = Settings.getValue("tabletCam/cameraRollPaths", '{"paths": []}'); + cameraRollModel.append(JSON.parse(settingsString).paths); + + root.activeView = "reviewView"; + } + } + } + } + } + + Item { + id: reviewView; + visible: root.activeView === "reviewView"; + anchors.fill: parent; + + Rectangle { + id: topBarContainer_review; + width: parent.width; + height: 42; + anchors.left: parent.left; + anchors.top: parent.top; + color: "#121212"; + + HifiControlsUit.Button { + text: "BACK"; + colorScheme: hifi.colorSchemes.dark; + color: hifi.buttons.noneBorderlessWhite; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: parent.left; + anchors.leftMargin: 8; + width: 50; + height: 30; + onClicked: { + root.activeView = "mainView"; + } + } + + HifiStylesUit.RalewaySemiBold { + text: "CAMERA ROLL"; + // Anchors + anchors.horizontalCenter: parent.horizontalCenter; + anchors.verticalCenter: parent.verticalCenter; + size: 22; + // Style + color: hifi.colors.white; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + wrapMode: Text.Wrap; + } + } + + ListModel { + id: cameraRollModel; + } + + SwipeView { + id: cameraRollSwipeView; + + anchors.top: topBarContainer_review.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: bottomBarContainer_review.top; + + Repeater { + model: cameraRollModel; + + Image { + source: imagePath; + fillMode: Image.PreserveAspectFit; + mipmap: true; + } + } + } + + PageIndicator { + id: indicator; + interactive: true; + count: cameraRollSwipeView.count; + currentIndex: cameraRollSwipeView.currentIndex + + anchors.bottom: cameraRollSwipeView.bottom; + anchors.horizontalCenter: cameraRollSwipeView.horizontalCenter; + + delegate: Rectangle { + implicitWidth: 15; + implicitHeight: 15; + radius: width; + color: "#00b4ef"; + opacity: index === cameraRollSwipeView.currentIndex ? 0.95 : 0.45; + + border.color: "#FFFFFF"; + border.width: index === cameraRollSwipeView.currentIndex ? 2 : 0; + + Behavior on opacity { + OpacityAnimator { + duration: 100; + } + } + } + } + + Rectangle { + id: bottomBarContainer_review; + height: 88; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.right: parent.right; + color: "#121212"; + + HifiControlsUit.Button { + text: "SHOW IN DESKTOP FILE BROWSER"; + colorScheme: hifi.colorSchemes.dark; + color: hifi.buttons.blue; + anchors.verticalCenter: parent.verticalCenter; + anchors.horizontalCenter: parent.horizontalCenter; + width: 240; + height: 30; + onClicked: { + var currentImagePath = cameraRollModel.get(cameraRollSwipeView.index).imagePath; + Qt.openUrlExternally(currentImagePath.substring(0, currentImagePath.lastIndexOf('/'))); + } + } + } + } + + Rectangle { + id: settingsView; + visible: root.activeView === "settingsView"; + anchors.fill: parent; + color: hifi.colors.black; + + Rectangle { + id: topBarContainer_settings; + width: parent.width; + height: 42; + anchors.left: parent.left; + anchors.top: parent.top; + color: "#121212"; + + HifiControlsUit.Button { + text: "BACK"; + colorScheme: hifi.colorSchemes.dark; + color: hifi.buttons.noneBorderlessWhite; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: parent.left; + anchors.leftMargin: 8; + width: 50; + height: 30; + onClicked: { + root.activeView = "mainView"; + } + } + + HifiStylesUit.RalewaySemiBold { + text: "SETTINGS"; + // Anchors + anchors.horizontalCenter: parent.horizontalCenter; + anchors.verticalCenter: parent.verticalCenter; + size: 22; + // Style + color: hifi.colors.white; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + wrapMode: Text.Wrap; + } + } + + Item { + id: settingsContainer; + anchors.top: topBarContainer_settings.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + + Item { + id: qualityContainer; + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: childrenRect.height; + + HifiStylesUit.RalewaySemiBold { + id: qualityHeaderText; + text: "Photo Quality"; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + height: 22; + size: 18; + // Style + color: hifi.colors.white; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignTop; + } + + HifiControlsUit.RadioButton { + id: lowRadioButton; + checked: root.snapshotQuality === "low"; + text: "Low"; + width: 70; + height: 35; + anchors.left: parent.left; + anchors.top: qualityHeaderText.bottom; + colorScheme: hifi.colorSchemes.dark; + onClicked: { + if (!lowRadioButton.checked) { + lowRadioButton.checked = true; + } + if (normalRadioButton.checked) { + normalRadioButton.checked = false; + } + if (highRadioButton.checked) { + highRadioButton.checked = false; + } + if (extremeRadioButton.checked) { + extremeRadioButton.checked = false; + } + } + onCheckedChanged: { + if (checked) { + sendToScript({method: 'setSnapshotQuality', quality: "low"}); + } + } + } + + HifiControlsUit.RadioButton { + id: normalRadioButton; + checked: root.snapshotQuality === "normal"; + text: "Normal"; + width: 100; + height: 35; + anchors.left: lowRadioButton.right; + anchors.leftMargin: 16; + anchors.top: qualityHeaderText.bottom; + colorScheme: hifi.colorSchemes.dark; + onClicked: { + if (lowRadioButton.checked) { + lowRadioButton.checked = false; + } + if (!normalRadioButton.checked) { + normalRadioButton.checked = true; + } + if (highRadioButton.checked) { + highRadioButton.checked = false; + } + if (extremeRadioButton.checked) { + extremeRadioButton.checked = false; + } + } + onCheckedChanged: { + if (checked) { + sendToScript({method: 'setSnapshotQuality', quality: "normal"}); + } + } + } + + HifiControlsUit.RadioButton { + id: highRadioButton; + checked: root.snapshotQuality === "high"; + text: "4k"; + width: 75; + height: 35; + anchors.left: normalRadioButton.right; + anchors.leftMargin: 16; + anchors.top: qualityHeaderText.bottom; + colorScheme: hifi.colorSchemes.dark; + onClicked: { + if (lowRadioButton.checked) { + lowRadioButton.checked = false; + } + if (normalRadioButton.checked) { + normalRadioButton.checked = false; + } + if (!highRadioButton.checked) { + highRadioButton.checked = true; + } + if (extremeRadioButton.checked) { + extremeRadioButton.checked = false; + } + } + onCheckedChanged: { + if (checked) { + sendToScript({method: 'setSnapshotQuality', quality: "high"}); + } + } + } + + HifiControlsUit.RadioButton { + id: extremeRadioButton; + checked: root.snapshotQuality === "extreme"; + text: "EXTREME"; + width: 120; + height: 35; + anchors.left: highRadioButton.right; + anchors.leftMargin: 16; + anchors.top: qualityHeaderText.bottom; + colorScheme: hifi.colorSchemes.dark; + onClicked: { + if (lowRadioButton.checked) { + lowRadioButton.checked = false; + } + if (normalRadioButton.checked) { + normalRadioButton.checked = false; + } + if (highRadioButton.checked) { + highRadioButton.checked = false; + } + if (!extremeRadioButton.checked) { + extremeRadioButton.checked = true; + } + } + onCheckedChanged: { + if (checked) { + sendToScript({method: 'setSnapshotQuality', quality: "extreme"}); + } + } + } + } + + Item { + id: aspectRatioContainer; + anchors.top: qualityContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.right: parent.right; + height: childrenRect.height; + + HifiStylesUit.RalewaySemiBold { + id: aspectRatioHeaderText; + text: "Aspect Ratio"; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + height: 22; + size: 18; + // Style + color: hifi.colors.white; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignTop; + } + + HifiControlsUit.RadioButton { + id: eightByTenRadioButton; + checked: parseFloat(root.aspectRatio) === (8 / 10); + text: "8x10"; + width: 70; + height: 35; + anchors.left: parent.left; + anchors.top: aspectRatioHeaderText.bottom; + colorScheme: hifi.colorSchemes.dark; + onClicked: { + if (!eightByTenRadioButton.checked) { + eightByTenRadioButton.checked = true; + } + if (twoByThreeRadioButton.checked) { + twoByThreeRadioButton.checked = false; + } + if (nineBySixteenRadioButton.checked) { + nineBySixteenRadioButton.checked = false; + } + if (oneByOneRadioButton.checked) { + oneByOneRadioButton.checked = false; + } + } + onCheckedChanged: { + if (checked) { + sendToScript({method: 'setAspectRatio', aspectRatio: (8 / 10)}); + } + } + } + + HifiControlsUit.RadioButton { + id: twoByThreeRadioButton; + checked: parseFloat(root.aspectRatio) === (2 / 3); + text: "2x3"; + width: 100; + height: 35; + anchors.left: eightByTenRadioButton.right; + anchors.leftMargin: 16; + anchors.top: aspectRatioHeaderText.bottom; + colorScheme: hifi.colorSchemes.dark; + onClicked: { + if (eightByTenRadioButton.checked) { + eightByTenRadioButton.checked = false; + } + if (!twoByThreeRadioButton.checked) { + twoByThreeRadioButton.checked = true; + } + if (nineBySixteenRadioButton.checked) { + nineBySixteenRadioButton.checked = false; + } + if (oneByOneRadioButton.checked) { + oneByOneRadioButton.checked = false; + } + } + onCheckedChanged: { + if (checked) { + sendToScript({method: 'setAspectRatio', aspectRatio: (2 / 3)}); + } + } + } + + HifiControlsUit.RadioButton { + id: nineBySixteenRadioButton; + checked: parseFloat(root.aspectRatio) === 9 / 16; + text: "9x16"; + width: 75; + height: 35; + anchors.left: twoByThreeRadioButton.right; + anchors.leftMargin: 16; + anchors.top: aspectRatioHeaderText.bottom; + colorScheme: hifi.colorSchemes.dark; + onClicked: { + if (eightByTenRadioButton.checked) { + eightByTenRadioButton.checked = false; + } + if (twoByThreeRadioButton.checked) { + twoByThreeRadioButton.checked = false; + } + if (!nineBySixteenRadioButton.checked) { + nineBySixteenRadioButton.checked = true; + } + if (oneByOneRadioButton.checked) { + oneByOneRadioButton.checked = false; + } + } + onCheckedChanged: { + if (checked) { + sendToScript({method: 'setAspectRatio', aspectRatio: (9 / 16)}); + } + } + } + + HifiControlsUit.RadioButton { + id: oneByOneRadioButton; + checked: parseFloat(root.aspectRatio) === 1 / 1; + text: "Square"; + width: 83; + height: 35; + anchors.left: nineBySixteenRadioButton.right; + anchors.leftMargin: 16; + anchors.top: aspectRatioHeaderText.bottom; + colorScheme: hifi.colorSchemes.dark; + onClicked: { + if (eightByTenRadioButton.checked) { + eightByTenRadioButton.checked = false; + } + if (twoByThreeRadioButton.checked) { + twoByThreeRadioButton.checked = false; + } + if (nineBySixteenRadioButton.checked) { + nineBySixteenRadioButton.checked = false; + } + if (!oneByOneRadioButton.checked) { + oneByOneRadioButton.checked = true; + } + } + onCheckedChanged: { + if (checked) { + sendToScript({method: 'setAspectRatio', aspectRatio: 1}); + } + } + } + } + + Item { + id: photoDirectoryContainer; + anchors.top: aspectRatioContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.right: parent.right; + height: childrenRect.height; + + HifiStylesUit.RalewaySemiBold { + id: photoDirectoryHeaderText; + text: "Photo Directory"; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + height: 22; + size: 18; + // Style + color: hifi.colors.white; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignTop; + } + + HifiControlsUit.TextField { + id: photoDirectoryTextField; + readOnly: true; + text: Settings.getValue("snapshotsDirectory", ""); + colorScheme: hifi.colorSchemes.dark; + // Anchors + anchors.top: photoDirectoryHeaderText.bottom; + anchors.topMargin: 8; + anchors.left: parent.left; + anchors.right: parent.right; + height: 50; + + MouseArea { + anchors.fill: parent; + + onClicked: { + sendToScript({method: 'setPhotoDirectory'}); + } + } + } + + HifiControlsUit.Button { + text: "CHANGE"; + colorScheme: hifi.colorSchemes.dark; + color: hifi.buttons.blue; + anchors.top: photoDirectoryTextField.bottom; + anchors.topMargin: 4; + anchors.right: parent.right; + width: 100; + height: 35; + onClicked: { + sendToScript({method: 'setPhotoDirectory'}); + } + } + } + + HifiStylesUit.FiraSansRegular { + text: "Hint:\nIn HMD, using the detached camera, you can press on the\nthumbsticks of your right controller to take a photo.\n\n\nv2.4"; + // Anchors + anchors.bottom: parent.bottom; + anchors.left: parent.left; + size: 16; + // Style + color: hifi.colors.lightGrayText; + } + } + } + + function fromScript(message) { + switch (message.method) { + case 'stillSnapshotTaken': + Settings.setValue('tabletCam/firstRun', false); + helpTextContainer.visible = false; + galleryButtonImage.source = message.lastStillSnapshotPath; + if (HMD.tabletID !== "{00000000-0000-0000-0000-000000000000}") { + secondaryCameraPreview.visible = true; + } + break; + case 'photoDirectoryChanged': + photoDirectoryTextField.text = message.photoDirectory; + break; + case 'inspectionCertificate_resetCert': + break; + default: + console.log('Unrecognized message from TabletCam.js.'); + } + } + signal sendToScript(var message); +} diff --git a/applications/tabletCam/ui/images/orientation.svg b/applications/tabletCam/ui/images/orientation.svg new file mode 100644 index 0000000..9bf7520 --- /dev/null +++ b/applications/tabletCam/ui/images/orientation.svg @@ -0,0 +1,3 @@ +Created by Atif Arshad +from the Noun Project + diff --git a/applications/tabletCam/ui/images/switchCams.svg b/applications/tabletCam/ui/images/switchCams.svg new file mode 100644 index 0000000..28c752e --- /dev/null +++ b/applications/tabletCam/ui/images/switchCams.svg @@ -0,0 +1,9 @@ + + rotate-cam-3 + Created with Sketch. + + + + Created by Diego Naive + from the Noun Project +