Merge branch 'master' of github.com:highfidelity/hifi into MS16480_disableOverlays

This commit is contained in:
Zach Fox 2018-07-10 15:02:05 -07:00
commit d3c0236602
90 changed files with 5890 additions and 518 deletions

View file

@ -1,8 +1,8 @@
High Fidelity (hifi) is an early-stage technology lab experimenting with Virtual Worlds and VR.
In this repository you'll find the source to many of the components in our
alpha-stage virtual world. The project embraces distributed development
and if you'd like to help, we'll pay you -- find out more at [Worklist.net](https://worklist.net).
This repository contains the source to many of the components in our
alpha-stage virtual world. The project embraces distributed development.
If you'd like to help, we'll pay you -- find out more at [Worklist.net](https://worklist.net).
If you find a small bug and have a fix, pull requests are welcome. If you'd
like to get paid for your work, make sure you report the bug via a job on
[Worklist.net](https://worklist.net).
@ -32,9 +32,10 @@ Running Interface
When you launch interface, you will automatically connect to our default domain: "root.highfidelity.io".
If you don't see anything, make sure your preferences are pointing to
root.highfidelity.io (set your domain via Cmnd+D/Cntrl+D), if you still have no luck it's possible our servers are
simply down; if you're experiencing a major bug, let us know by adding an issue to this repository.
Make sure to include details about your computer and how to reproduce the bug.
root.highfidelity.io (set your domain via Cmnd+D/Cntrl+D). If you still have no luck,
it's possible our servers are down. If you're experiencing a major bug, let us know by
adding an issue to this repository. Include details about your computer and how to
reproduce the bug in your issue.
To move around in-world, use the arrow keys (and Shift + up/down to fly up or
down) or W A S D, and E or C to fly up/down. All of the other possible options
@ -48,7 +49,8 @@ you to run the full stack of the virtual world.
In order to set up your own virtual world, you need to set up and run your own
local "domain".
The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, meta-voxels and models.
The domain-server gives a number different types of assignments to the assignment-client
for different features: audio, avatars, voxels, particles, meta-voxels and models.
Follow the instructions in the [build guide](BUILD.md) to build the various components.
@ -56,7 +58,8 @@ From the domain-server build directory, launch a domain-server.
./domain-server
Then, run an assignment-client. The assignment-client uses localhost as its assignment-server and talks to it on port 40102 (the default domain-server port).
Then, run an assignment-client. The assignment-client uses localhost as its assignment-server
and talks to it on port 40102 (the default domain-server port).
In a new Terminal window, run:
@ -64,13 +67,20 @@ In a new Terminal window, run:
Any target can be terminated with Ctrl-C (SIGINT) in the associated Terminal window.
This assignment-client will grab one assignment from the domain-server. You can tell the assignment-client what type you want it to be with the `-t` option. You can also run an assignment-client that forks off *n* assignment-clients with the `-n` option. The `-min` and `-max` options allow you to set a range of required assignment-clients, this allows you to have flexibility in the number of assignment-clients that are running. See `--help` for more options.
This assignment-client will grab one assignment from the domain-server. You can tell the
assignment-client what type you want it to be with the `-t` option. You can also run an
assignment-client that forks off *n* assignment-clients with the `-n` option. The `-min`
and `-max` options allow you to set a range of required assignment-clients. This allows
you to have flexibility in the number of assignment-clients that are running.
See `--help` for more options.
./assignment-client --min 6 --max 20
To test things out you'll want to run the Interface client.
To test things out, you'll need to run the Interface client.
To access your local domain in Interface, open your Preferences -- on OS X this is available in the Interface menu, on Linux you'll find it in the File menu. Enter "localhost" in the "Domain server" field.
To access your local domain in Interface, open your Preferences. On OS X, this is available
in the Interface menu. On Linux, you'll find it in the File menu. Enter "localhost" in the
"Domain server" field.
If everything worked you should see that you are connected to at least one server.
If everything worked, you should see that you are connected to at least one server.
Nice work!

View file

@ -566,7 +566,7 @@
},
{
"id": "idleToWalkFwd",
"interpTarget": 3,
"interpTarget": 10,
"interpDuration": 3,
"transitions": [
{ "var": "idleToWalkFwdOnDone", "state": "walkFwd" },
@ -603,8 +603,8 @@
},
{
"id": "walkBwd",
"interpTarget": 6,
"interpDuration": 6,
"interpTarget": 8,
"interpDuration": 2,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
@ -621,8 +621,8 @@
},
{
"id": "strafeRight",
"interpTarget": 6,
"interpDuration": 6,
"interpTarget": 20,
"interpDuration": 1,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
@ -639,8 +639,8 @@
},
{
"id": "strafeLeft",
"interpTarget": 6,
"interpDuration": 6,
"interpTarget": 20,
"interpDuration": 1,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },

View file

@ -0,0 +1,777 @@
{
"Anime boy": {
"attachments": [
],
"avatarEntites": [
{
"properties": {
"acceleration": {
"x": 0,
"y": 0,
"z": 0
},
"actionData": "",
"age": 6.915350914001465,
"ageAsText": "0 hours 0 minutes 6 seconds",
"angularDamping": 0.39346998929977417,
"angularVelocity": {
"x": 0,
"y": 0,
"z": 0
},
"animation": {
"allowTranslation": true,
"currentFrame": 0,
"firstFrame": 0,
"fps": 30,
"hold": false,
"lastFrame": 100000,
"loop": true,
"running": false,
"url": ""
},
"boundingBox": {
"brn": {
"x": -0.10961885005235672,
"y": -0.19444090127944946,
"z": -0.15760529041290283
},
"center": {
"x": 2.6226043701171875e-06,
"y": -0.13999652862548828,
"z": -0.04999971389770508
},
"dimensions": {
"x": 0.21924294531345367,
"y": 0.10888873785734177,
"z": 0.2152111530303955
},
"tfl": {
"x": 0.10962409526109695,
"y": -0.0855521634221077,
"z": 0.057605862617492676
}
},
"canCastShadow": true,
"certificateID": "",
"clientOnly": true,
"cloneAvatarEntity": false,
"cloneDynamic": false,
"cloneLifetime": 300,
"cloneLimit": 0,
"cloneOriginID": "{00000000-0000-0000-0000-000000000000}",
"cloneable": false,
"collidesWith": "",
"collisionMask": 0,
"collisionSoundURL": "",
"collisionless": false,
"collisionsWillMove": false,
"compoundShapeURL": "",
"created": "2018-06-06T17:27:53Z",
"damping": 0.39346998929977417,
"density": 1000,
"description": "",
"dimensions": {
"x": 0.21924294531345367,
"y": 0.07768379896879196,
"z": 0.2055898904800415
},
"dynamic": false,
"editionNumber": 15,
"entityInstanceNumber": 0,
"friction": 0.5,
"gravity": {
"x": 0,
"y": 0,
"z": 0
},
"href": "",
"id": "{5d20c775-a0d7-4163-b158-4e0a784a4625}",
"ignoreForCollisions": false,
"itemArtist": "jyoum",
"itemCategories": "Wearables",
"itemDescription": "Wear these, and others will respect your authoritah.",
"itemLicense": "",
"itemName": "Aviators",
"jointRotations": [
],
"jointRotationsSet": [
],
"jointTranslations": [
],
"jointTranslationsSet": [
],
"lastEdited": 1528306178314655,
"lastEditedBy": "{439a2669-4626-487f-9dcf-2d15e77c69a2}",
"lifetime": -1,
"limitedRun": 4294967295,
"localPosition": {
"x": 2.6226043701171875e-06,
"y": -0.13999652862548828,
"z": -0.04999971389770508
},
"localRotation": {
"w": 0.9969173073768616,
"x": -0.07845909893512726,
"y": 0,
"z": 0
},
"locked": false,
"marketplaceID": "40d879ec-93f0-4b4a-8c58-dd6349bdb058",
"modelURL": "http://mpassets.highfidelity.com/40d879ec-93f0-4b4a-8c58-dd6349bdb058-v1/Aviator.fbx",
"name": "",
"naturalDimensions": {
"x": 0.1660931408405304,
"y": 0.05885136127471924,
"z": 0.15574991703033447
},
"naturalPosition": {
"x": 0,
"y": 1.6633577346801758,
"z": 0.048884183168411255
},
"originalTextures": "{\n \"aviator:Eyewear2F\": \"http://mpassets.highfidelity.com/40d879ec-93f0-4b4a-8c58-dd6349bdb058-v1/Aviator.fbx/Aviator.fbm/aviator_Eyewear_Diffuse.png\",\n \"aviator:Eyewear2F1\": \"http://mpassets.highfidelity.com/40d879ec-93f0-4b4a-8c58-dd6349bdb058-v1/Aviator.fbx/Aviator.fbm/aviator_Eyewear_Specular.png\"\n}\n",
"owningAvatarID": "{439a2669-4626-487f-9dcf-2d15e77c69a2}",
"parentID": "{439a2669-4626-487f-9dcf-2d15e77c69a2}",
"parentJointIndex": 66,
"position": {
"x": 2.6226043701171875e-06,
"y": -0.13999652862548828,
"z": -0.04999971389770508
},
"queryAACube": {
"scale": 0.9313028454780579,
"x": -1.4091639518737793,
"y": -10.133878707885742,
"z": 1.9983724355697632
},
"registrationPoint": {
"x": 0.5,
"y": 0.5,
"z": 0.5
},
"relayParentJoints": false,
"renderInfo": {
"drawCalls": 1,
"hasTransparent": false,
"texturesCount": 2,
"texturesSize": 1310720,
"verticesCount": 982
},
"restitution": 0.5,
"rotation": {
"w": 0.9969173073768616,
"x": -0.07845909893512726,
"y": 0,
"z": 0
},
"script": "",
"scriptTimestamp": 0,
"serverScripts": "",
"shapeType": "box",
"staticCertificateVersion": 0,
"textures": "",
"type": "Model",
"userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}",
"velocity": {
"x": 0,
"y": 0,
"z": 0
},
"visible": true
}
}
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/46e0fd52-3cff-462f-ba97-927338d88295-v1/AnimeBoy2.fst",
"version": 3
},
"Anime girl": {
"attachments": [
],
"avatarEntites": [
{
"properties": {
"acceleration": {
"x": 0,
"y": 0,
"z": 0
},
"actionData": "",
"age": 19.66267967224121,
"ageAsText": "0 hours 0 minutes 19 seconds",
"angularDamping": 0.39346998929977417,
"angularVelocity": {
"x": 0,
"y": 0,
"z": 0
},
"animation": {
"allowTranslation": true,
"currentFrame": 0,
"firstFrame": 0,
"fps": 30,
"hold": false,
"lastFrame": 100000,
"loop": true,
"running": false,
"url": ""
},
"boundingBox": {
"brn": {
"x": -0.10536206513643265,
"y": -0.16647332906723022,
"z": -0.12632352113723755
},
"center": {
"x": 0,
"y": -0.12999999523162842,
"z": -0.030000001192092896
},
"dimensions": {
"x": 0.2107241302728653,
"y": 0.07294666767120361,
"z": 0.1926470398902893
},
"tfl": {
"x": 0.10536206513643265,
"y": -0.09352666139602661,
"z": 0.06632351875305176
}
},
"canCastShadow": true,
"certificateID": "",
"clientOnly": true,
"cloneAvatarEntity": false,
"cloneDynamic": false,
"cloneLifetime": 300,
"cloneLimit": 0,
"cloneOriginID": "{00000000-0000-0000-0000-000000000000}",
"cloneable": false,
"collidesWith": "",
"collisionMask": 0,
"collisionSoundURL": "",
"collisionless": false,
"collisionsWillMove": false,
"compoundShapeURL": "",
"created": "2018-06-05T00:10:37Z",
"damping": 0.39346998929977417,
"density": 1000,
"description": "",
"dimensions": {
"x": 0.2107241302728653,
"y": 0.07294666767120361,
"z": 0.1926470398902893
},
"dynamic": false,
"editionNumber": 5,
"entityInstanceNumber": 0,
"friction": 0.5,
"gravity": {
"x": 0,
"y": 0,
"z": 0
},
"href": "",
"id": "{1586b83a-2af7-4532-9bfb-82fe3f5d5ce9}",
"ignoreForCollisions": false,
"itemArtist": "moam_00",
"itemCategories": "Wearables",
"itemDescription": "Perfect for side-glancin'.",
"itemLicense": "",
"itemName": "Blacker Fem Glasses",
"jointRotations": [
],
"jointRotationsSet": [
],
"jointTranslations": [
],
"jointTranslationsSet": [
],
"lastEdited": 1528157470041658,
"lastEditedBy": "{425df1a8-289b-42fc-819c-c3b2a12d7165}",
"lifetime": -1,
"limitedRun": 4294967295,
"localPosition": {
"x": 0,
"y": -0.12999999523162842,
"z": -0.029999999329447746
},
"localRotation": {
"w": 1,
"x": -2.2351741790771484e-08,
"y": 3.4924596548080444e-10,
"z": 3.725290298461914e-09
},
"locked": false,
"marketplaceID": "06781d12-9139-48f4-ac2a-417dde090981",
"modelURL": "http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx",
"name": "Female Glasses 3 by Mario Andrade",
"naturalDimensions": {
"x": 0.16209548711776733,
"y": 0.05611282214522362,
"z": 0.14819003641605377
},
"naturalPosition": {
"x": 0,
"y": -7.636845111846924e-08,
"z": 0
},
"originalTextures": "{\n \"file49\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Mixed_AO.jpg\",\n \"file81\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Metallic.jpg\",\n \"file84\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Roughness.jpg\",\n \"file86\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Base_Color.jpg\",\n \"file87\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Normal_DirectX.jpg\"\n}\n",
"owningAvatarID": "{1277f725-fbb4-478b-ae79-1241fd90e508}",
"parentID": "{1277f725-fbb4-478b-ae79-1241fd90e508}",
"parentJointIndex": 66,
"position": {
"x": 0,
"y": -0.12999999523162842,
"z": -0.029999999329447746
},
"queryAACube": {
"scale": 0.8840523958206177,
"x": -2.6587564945220947,
"y": -10.162277221679688,
"z": -0.9548344016075134
},
"registrationPoint": {
"x": 0.5,
"y": 0.5,
"z": 0.5
},
"relayParentJoints": false,
"renderInfo": {
"drawCalls": 1,
"hasTransparent": false,
"texturesCount": 5,
"texturesSize": 0,
"verticesCount": 1156
},
"restitution": 0.5,
"rotation": {
"w": 1,
"x": -2.2351741790771484e-08,
"y": 3.4924596548080444e-10,
"z": 3.725290298461914e-09
},
"script": "",
"scriptTimestamp": 0,
"serverScripts": "",
"shapeType": "box",
"staticCertificateVersion": 0,
"textures": "",
"type": "Model",
"userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}",
"velocity": {
"x": 0,
"y": 0,
"z": 0
},
"visible": true
}
}
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/0dce3426-55c8-4641-8dd5-d76eb575b64a-v1/Anime_F_Outfit.fst",
"version": 3
},
"Last Legends: Male": {
"attachments": [
],
"avatarEntites": [
{
"properties": {
"acceleration": {
"x": 0,
"y": 0,
"z": 0
},
"actionData": "",
"age": 14.011327743530273,
"ageAsText": "0 hours 0 minutes 14 seconds",
"angularDamping": 0.39346998929977417,
"angularVelocity": {
"x": 0,
"y": 0,
"z": 0
},
"animation": {
"allowTranslation": true,
"currentFrame": 0,
"firstFrame": 0,
"fps": 30,
"hold": false,
"lastFrame": 100000,
"loop": true,
"running": false,
"url": ""
},
"boundingBox": {
"brn": {
"x": -0.20154684782028198,
"y": 0.03644842654466629,
"z": -0.2641940414905548
},
"center": {
"x": -0.030000001192092896,
"y": 0.12999820709228516,
"z": -0.07000023126602173
},
"dimensions": {
"x": 0.3430936932563782,
"y": 0.18709957599639893,
"z": 0.38838762044906616
},
"tfl": {
"x": 0.1415468454360962,
"y": 0.22354799509048462,
"z": 0.12419357895851135
}
},
"canCastShadow": true,
"certificateID": "",
"clientOnly": true,
"cloneAvatarEntity": false,
"cloneDynamic": false,
"cloneLifetime": 300,
"cloneLimit": 0,
"cloneOriginID": "{00000000-0000-0000-0000-000000000000}",
"cloneable": false,
"collidesWith": "",
"collisionMask": 0,
"collisionSoundURL": "",
"collisionless": false,
"collisionsWillMove": false,
"compoundShapeURL": "",
"created": "2018-06-06T17:25:42Z",
"damping": 0.39346998929977417,
"density": 1000,
"description": "",
"dimensions": {
"x": 0.33466479182243347,
"y": 0.16981728374958038,
"z": 0.38838762044906616
},
"dynamic": false,
"editionNumber": 19,
"entityInstanceNumber": 0,
"friction": 0.5,
"gravity": {
"x": 0,
"y": 0,
"z": 0
},
"href": "",
"id": "{6b0a2b08-e8e3-4d43-95cc-dfc4f7a4b0c9}",
"ignoreForCollisions": false,
"itemArtist": "jyoum",
"itemCategories": "Wearables",
"itemDescription": "A stylish and classic piece of headwear for your avatar.",
"itemLicense": "",
"itemName": "Fedora",
"jointRotations": [
],
"jointRotationsSet": [
],
"jointTranslations": [
],
"jointTranslationsSet": [
],
"lastEdited": 1528306032827319,
"lastEditedBy": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"lifetime": -1,
"limitedRun": 4294967295,
"localPosition": {
"x": -0.030000008642673492,
"y": 0.12999820709228516,
"z": -0.07000023126602173
},
"localRotation": {
"w": 0.9996573328971863,
"x": 0,
"y": 0,
"z": 0.026176949962973595
},
"locked": false,
"marketplaceID": "11c4208d-15d7-4449-9758-a08da6dbd3dc",
"modelURL": "http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx",
"name": "",
"naturalDimensions": {
"x": 0.2765824794769287,
"y": 0.14034485816955566,
"z": 0.320981502532959
},
"naturalPosition": {
"x": 0.000143393874168396,
"y": 1.7460365295410156,
"z": 0.022502630949020386
},
"originalTextures": "{\n \"file5\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Base_Color.png\",\n \"file7\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Roughness.png\"\n}\n",
"owningAvatarID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"parentID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"parentJointIndex": 64,
"position": {
"x": -0.030000008642673492,
"y": 0.12999820709228516,
"z": -0.07000023126602173
},
"queryAACube": {
"scale": 1.6202316284179688,
"x": -0.5601736903190613,
"y": -10.668098449707031,
"z": -0.8933582305908203
},
"registrationPoint": {
"x": 0.5,
"y": 0.5,
"z": 0.5
},
"relayParentJoints": false,
"renderInfo": {
"drawCalls": 1,
"hasTransparent": false,
"texturesCount": 2,
"texturesSize": 327680,
"verticesCount": 719
},
"restitution": 0.5,
"rotation": {
"w": 0.9996573328971863,
"x": 0,
"y": 0,
"z": 0.026176949962973595
},
"script": "",
"scriptTimestamp": 0,
"serverScripts": "",
"shapeType": "box",
"staticCertificateVersion": 0,
"textures": "",
"type": "Model",
"userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}",
"velocity": {
"x": 0,
"y": 0,
"z": 0
},
"visible": true
}
},
{
"properties": {
"acceleration": {
"x": 0,
"y": 0,
"z": 0
},
"actionData": "",
"age": 14.011027336120605,
"ageAsText": "0 hours 0 minutes 14 seconds",
"angularDamping": 0.39346998929977417,
"angularVelocity": {
"x": 0,
"y": 0,
"z": 0
},
"animation": {
"allowTranslation": true,
"currentFrame": 0,
"firstFrame": 0,
"fps": 30,
"hold": false,
"lastFrame": 100000,
"loop": true,
"running": false,
"url": ""
},
"boundingBox": {
"brn": {
"x": -0.04381517320871353,
"y": 0.20789726078510284,
"z": -0.0394962802529335
},
"center": {
"x": -1.9073486328125e-06,
"y": 0.2300434112548828,
"z": 1.2159347534179688e-05
},
"dimensions": {
"x": 0.08762653172016144,
"y": 0.04429228603839874,
"z": 0.07901687920093536
},
"tfl": {
"x": 0.043811358511447906,
"y": 0.2521895468235016,
"z": 0.03952059894800186
}
},
"canCastShadow": true,
"certificateID": "",
"clientOnly": true,
"cloneAvatarEntity": false,
"cloneDynamic": false,
"cloneLifetime": 300,
"cloneLimit": 0,
"cloneOriginID": "{00000000-0000-0000-0000-000000000000}",
"cloneable": false,
"collidesWith": "",
"collisionMask": 0,
"collisionSoundURL": "",
"collisionless": false,
"collisionsWillMove": false,
"compoundShapeURL": "",
"created": "2018-06-06T17:25:42Z",
"damping": 0.39346998929977417,
"density": 1000,
"description": "",
"dimensions": {
"x": 0.03022606298327446,
"y": 0.06644226610660553,
"z": 0.07229919731616974
},
"dynamic": false,
"editionNumber": 58,
"entityInstanceNumber": 0,
"friction": 0.5,
"gravity": {
"x": 0,
"y": 0,
"z": 0
},
"href": "",
"id": "{d018c6ea-b2f4-441e-85e1-d17373ae6f34}",
"ignoreForCollisions": false,
"itemArtist": "jyoum",
"itemCategories": "Wearables",
"itemDescription": "A cool scifi watch for your avatar!",
"itemLicense": "",
"itemName": "Scifi Watch",
"jointRotations": [
],
"jointRotationsSet": [
],
"jointTranslations": [
],
"jointTranslationsSet": [
],
"lastEdited": 1528306032505220,
"lastEditedBy": "{b46f9c9e-4cd3-4964-96d6-cf3954abb908}",
"lifetime": -1,
"limitedRun": 4294967295,
"localPosition": {
"x": -1.9073486328125e-06,
"y": 0.2300434112548828,
"z": 1.2159347534179688e-05
},
"localRotation": {
"w": 0.5910987257957458,
"x": -0.48726412653923035,
"y": -0.4088631868362427,
"z": 0.49599069356918335
},
"locked": false,
"marketplaceID": "0685794d-fddb-4bad-a608-6d7789ceda90",
"modelURL": "http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx",
"name": "Scifi Watch by Jimi",
"naturalDimensions": {
"x": 0.023250818252563477,
"y": 0.0511094331741333,
"z": 0.055614765733480453
},
"naturalPosition": {
"x": 0.6493338942527771,
"y": 1.4500460624694824,
"z": -0.06031447649002075
},
"originalTextures": "{\n \"file4\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Base_Color.png\",\n \"file5\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Normal_OpenGL.png\",\n \"file6\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Metallic.png\",\n \"file7\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Roughness.png\",\n \"file8\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Emissive.png\"\n}\n",
"owningAvatarID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"parentID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"parentJointIndex": 16,
"position": {
"x": -1.9073486328125e-06,
"y": 0.2300434112548828,
"z": 1.2159347534179688e-05
},
"queryAACube": {
"scale": 0.3082179129123688,
"x": -0.19203892350196838,
"y": -10.429610252380371,
"z": -0.4076632857322693
},
"registrationPoint": {
"x": 0.5,
"y": 0.5,
"z": 0.5
},
"relayParentJoints": false,
"renderInfo": {
"drawCalls": 1,
"hasTransparent": false,
"texturesCount": 5,
"texturesSize": 786432,
"verticesCount": 273
},
"restitution": 0.5,
"rotation": {
"w": 0.5910987257957458,
"x": -0.48726412653923035,
"y": -0.4088631868362427,
"z": 0.49599069356918335
},
"script": "",
"scriptTimestamp": 0,
"serverScripts": "",
"shapeType": "box",
"staticCertificateVersion": 0,
"textures": "",
"type": "Model",
"userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"[LR]ForeArm\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}",
"velocity": {
"x": 0,
"y": 0,
"z": 0
},
"visible": true
}
}
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/28569047-6f1a-4100-af67-8054ec397cc3-v1/LLMale2.fst",
"version": 3
},
"Last legends Female": {
"attachments": [
],
"avatarEntites": [
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/8d823be5-6197-4418-b984-eb94160ed956-v1/LLFemale_Clothes.fst",
"version": 3
},
"Matthew": {
"attachments": [
],
"avatarEntites": [
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/b652081b-a199-425e-ae5c-7815721bdc09-v1/matthew.fst",
"version": 3
},
"Priscilla": {
"attachments": [
],
"avatarEntites": [
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339-v1/priscilla.fst",
"version": 3
},
"Woody": {
"attachments": [
],
"avatarEntites": [
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/ad348528-de38-420c-82bb-054cb22163f5-v1/mannequin.fst",
"version": 3
}
}

View file

@ -35,8 +35,6 @@
<glyph glyph-name="diclosure-expand" unicode="&#66;" d="M239 187c-3 0-6 1-8 3-5 5-5 12 0 17l42 42-43 44c-5 4-5 12 0 17 4 4 12 4 17 0l60-61-60-59c-2-2-5-3-8-3z"/>
<glyph glyph-name="reload-small" unicode="&#97;" d="M334 253c-9 0-18-7-18-16-3-27-25-47-52-47-29 0-52 24-52 52 0 11 2 27 14 38 6 5 14 9 24 12-5-7-4-16 2-22 3-3 8-4 12-4 5 0 9 1 13 5l28 28c1 2 3 4 3 7 3 6 1 13-3 18l-29 29c-3 3-7 5-12 5-5 0-9-2-12-5-4-3-5-8-5-12 0-5 1-9 5-13l0 0c-20-3-37-11-50-23-16-16-25-38-25-63 0-47 39-86 86-86 45 0 82 33 87 78 1 9-6 18-16 19z"/>
<glyph glyph-name="close-small" unicode="&#67;" d="M291 259l43 44c8 7 8 19 0 27-7 7-19 7-26 0l-44-44-44 44c-7 7-19 7-26 0-8-8-8-20 0-27l43-44-43-43c-8-8-8-20 0-27 7-7 19-7 26 0l44 44 44-44c7-7 19-7 26 0 8 8 8 19 0 27z"/>
<glyph glyph-name="backward" unicode="&#69;" d="M292 349c-5 3-12 3-18-1l-94-71c-4-3-7-8-7-13 0-5 2-10 6-14l95-80c3-2 7-4 11-4 2 0 5 1 7 2 6 3 10 9 10 15l0 151c0 6-4 12-10 15"/>
<glyph glyph-name="reload" unicode="&#70;" d="M365 261c-9 1-17-5-18-15-4-45-43-80-89-80-49 0-89 40-89 89 0 19 4 45 25 65 16 15 39 23 68 25l-15-16c-6-6-6-17 0-24 4-3 8-4 12-4 4 0 9 1 12 5l43 44c2 2 3 4 4 6 2 6 1 13-4 18l-44 44c-6 6-17 6-23 0-7-7-7-17 0-24l15-15c-38-2-69-14-91-35-23-21-36-53-36-88 0-68 55-123 123-123 64 0 116 47 122 110 1 9-5 18-15 18"/>
<glyph glyph-name="minimize" unicode="&#73;" d="M154 282l198 0c10 0 18-8 18-18 0-10-8-18-18-18l-198 0c-10 0-18 8-18 18 0 10 8 18 18 18"/>
<glyph glyph-name="maximize" unicode="&#74;" d="M157 244l77 0 0-75c0-9 8-17 17-17 9 0 17 8 17 17l0 75 75 0c10 0 17 8 17 17 0 10-7 18-17 18l-75 0 0 76c0 10-8 17-17 17-9 0-17-7-17-17l0-76-77 0c-10 0-17-8-17-18 0-9 8-17 17-17z"/>
<glyph glyph-name="maximize-inverted" unicode="&#75;" d="M251 434c-96 0-173-78-173-173 0-96 77-173 173-173 95 0 173 77 173 173 0 95-78 173-173 173z m93-190l-77 0 0-76c0-10-7-17-16-17-9 0-16 7-16 17l0 76-77 0c-10 0-17 8-17 17 0 9 7 17 17 17l77 0 0 76c0 9 7 17 16 17 9 0 16-8 16-17l0-76 77 0c9 0 17-8 17-17 0-9-8-17-17-17z"/>
@ -48,7 +46,6 @@
<glyph glyph-name="script-new" unicode="&#81;" d="M298 80l-145 0c-30 0-55 15-72 43-12 20-16 40-16 41l-3 16 267 0 0-12c0-1 1-15 9-29 10-18 26-27 49-27 13 0 22 4 29 12 16 18 16 54 14 66l0 1 0 206-269 0c-7 0-13 6-13 13 0 7 6 13 13 13l295 0 0-230c1-8 5-57-21-86-12-14-28-21-48-21-41 0-62 23-72 41-5 10-8 19-10 28l-210 0c2-7 5-12 8-18 13-20 29-30 50-30l145 0c7 0 13-7 13-14 0-7-6-13-13-13z m95 260l-180 0c-7 0-14 6-14 14 0 8 7 14 14 14l180 0c7 0 14-6 14-14 0-8-7-14-14-14z m0-59l-135 0c-8 0-14 7-14 15 0 7 6 14 14 14l135 0c7 0 14-7 14-14 0-8-7-15-14-15z m0-58l-180 0c-7 0-14 7-14 14 0 8 7 15 14 15l180 0c7 0 14-7 14-15 0-7-7-14-14-14z m-250 90l0 53c0 9-7 16-16 16-9 0-16-7-16-16l0-53-54 0c-9 0-16-8-16-17 0-9 8-17 16-17l54 0 0-53c0-9 7-16 16-16 9 0 16 8 16 17l0 52 54 0c9 0 16 8 16 17 0 9-8 16-17 16z"/>
<glyph glyph-name="hifi-forum" unicode="&#50;" d="M265 410c-83 0-150-68-150-150 0-24 6-47 16-67l-27-79 80 20c23-16 51-25 81-25 83 0 150 68 150 150 0 83-67 151-150 151z m38-248c-9 0-17 7-17 17 0 7 4 14 12 16l0 46-74 33 0-56c7-2 12-8 12-16 0-10-7-18-17-18-10 0-19 8-19 18 0 7 6 13 10 16l0 111c-4 2-10 9-10 16 0 10 9 17 19 17 9 0 17-7 17-17 0-8-5-14-12-16l0-41 74-33 0 51c-8 3-12 9-12 16 0 10 7 18 17 18 10 0 17-8 17-18 0-7-5-14-12-16l0-110c7-3 12-9 12-17 0-10-7-17-17-17z"/>
<glyph glyph-name="hifi-logo-small" unicode="&#83;" d="M374 374c-32 32-74 49-119 49-46 0-88-17-120-49-32-32-49-74-49-119 0-45 17-87 49-118 32-32 74-49 119-49 45 0 88 17 119 49 32 32 49 73 49 118 1 45-17 87-48 119z m-17-221c-28-28-65-43-103-43-39 0-75 15-103 43-27 27-42 64-42 102 0 39 15 75 42 103 28 28 64 43 103 43 38 0 75-15 103-43 27-28 42-64 42-103 0-39-15-76-42-102z m-145 47c-5 0-9 3-9 6l0 126c0 3 4 7 9 7 6 0 10-4 10-7l0-125c0-4-4-7-10-7z m0 118c-5 0-10 2-14 6-7 7-7 20 0 27 5 5 9 6 14 6 6 0 11-2 14-6 4-4 5-8 5-14 0-5-2-10-5-13-4-4-8-6-14-6z m0-144c-5 0-10 2-14 5-4 5-5 9-5 14 0 6 2 11 5 14 5 4 9 5 14 5 6 0 11-2 14-5 4-4 5-8 5-14 0-5-2-10-5-14-4-3-8-5-14-5z m85 2c-6 0-10 3-10 7l0 121c0 4 4 7 10 7 5 0 9-3 9-7l0-121c0-5-4-7-9-7z m0 120c-6 0-11 2-14 5-8 8-8 20 0 28 4 4 8 5 14 5 5 0 10-2 14-5 4-4 5-9 5-14 0-5-2-11-5-14-4-3-9-5-14-5z m0-144c-6 0-11 2-14 5-8 7-8 20 0 28 4 4 8 5 14 5 5 0 10-2 14-5 4-5 5-9 5-14 0-5-2-11-5-14-4-3-9-5-14-5z m1 73l-85 40 1 18 86-40z"/>
<glyph glyph-name="avatar-1" unicode="&#84;" d="M293 71c-1 0-1 0-1 0-14-1-14-1-16 12-6 43-11 86-16 128-1 2-1 4-1 6-3 0-6 0-9 0-2-10-3-20-4-31-4-36-8-72-12-109-1-7-2-8-9-8-2 0-5 0-7 0 0 74 1 181 1 254-1-1-33 0-44 1-15 0-62 1-79 1-8 0-14 3-18 10-1 2-2 4-4 7 8 0 15 0 22 1 35 1 99 8 100 12 14 11 23 10 36 10 15 0 31 0 46-1 11 0 24 3 37-10 20-10 81-10 123-11 0 0 1 0 1 0-4-12-12-17-25-17-29-1-77-3-127-1 2-73 4-181 6-254z m-32 371c16-6 14-20 13-32 0-5-1-10-1-14-2-11-10-18-20-18-11 0-19 8-20 18-1 6-1 13-2 20-1 7 3 13 11 15 10 3 10 3 19 11z"/>
<glyph glyph-name="placemark" unicode="&#85;" d="M134 98c31-32 73-49 119-49l1 0c45 0 86 16 117 47 31 30 48 71 48 114 1 46-16 88-46 120-9 9-20 17-31 24-3-7-6-15-10-22 5-4 11-8 16-13 15-14 28-32 33-46l1-1c0-1 0-2-1-3l-1 0c-12-7-25-10-38-12-2-1-4-1-7-2l-1 0c0 0-1 0-2 1 0 0-1 1-1 1l0 1c-3 15-6 31-12 46l-7-15c4-11 6-21 8-32l0-1c0-1 0-1 0-2-1-1-1-1-2-1l-1 0-55-3-4 0c0 0-1 0-1 0-1 1-1 2-1 2l0 74c-5 9-8 17-11 26 0-1 0-1 0-1l-1 0 0-97c0 0 0-1 0-2 0-1-1-2-2-2l-1 0c0 0-1 0-1 0l-51 3c-1 0-2 0-2 1-1 1-1 2 0 2l0 1c5 27 13 58 35 83 6 6 12 13 20 16 0 0 1 0 1 0-3 7-6 15-9 22-37-4-71-20-99-47-30-30-47-70-48-113-1-46 16-88 47-120z m198 72c0 4 1 7 1 10l0 0c0 7 1 14 1 21 0 9 0 18 0 27 0 3 0 6 0 9l0 1c-1 1-1 6 5 7 11 3 23 5 34 8l1 0c3 1 7 2 11 2l1 1c1 0 3-1 3-2l0-1c0-1 1-1 1-2 1-3 1-5 2-8 3-14 4-27 3-40-2-12-8-21-18-25-8-3-17-5-25-8-3 0-7-1-10-2-1-1-3-1-4-1-1 0-1 0-2 0l-1-1c0 0 0 0 0 0-1 0-2 1-2 1-1 1-1 1-1 2z m2-17c12 4 23 7 35 10l14 4c1 0 2 0 2-1 1 0 1-2 1-2l-1-2c-7-18-17-34-30-47-13-13-28-24-46-31l-2-1c0 0-1 0-1 0-1 0-1 0-2 0 0 1-1 2-1 3l1 1c0 1 0 1 0 1 0 1 0 2 1 3 6 9 15 24 22 55 1 3 2 6 7 7z m-78 83c0 1 1 3 2 3l3 0 41 2 13 1c0 0 1 0 1 0l1 0c0 0 0-1 1-1l1 0c1 0 2-1 2-3 0-1 0-3 0-5 0-2 0-4 0-6l0-5c1-7 1-15 0-22l0-4c0-5 0-11-1-17 0-1 0-3 0-5 0-1 0-3 0-4 0-2-1-7-7-8-10 0-20-1-30-2l-2 0c-3-1-8-1-12-1-5-1-7-1-8-1-1 0-1 0-2 0l-2 1c-1 1-1 2-1 3l0 74z m0-92c0 1 1 3 2 3l3 0 51 3c1 0 2 0 2-1 1 0 1-1 1-2l0-1c0 0 0-1 0-1 0 0 0 0-1-1 0 0 0-1 0-1l0-1c0-1-1-3-1-4-1-2-2-5-3-7l-1-3c-3-9-7-18-11-27-4-8-9-14-14-19-5-6-13-9-23-10l-3 0c-1 0-1 0-2 1 0 0 0 1 0 2l0 69z m-52-59c-1-1-1-2-2-2 0 0-1 1-1 1l-2 1c-27 6-64 40-78 79l-1 1c0 1 0 2 1 3 1 1 2 1 3 1l13-4c12-4 24-7 36-11 3-1 6-2 7-7 5-24 12-42 23-57l1-2c1-1 1-3 0-3z m40-10c0-1-1-1-1-2-1 0-2 0-2 0 0 0-1 0-1 0-1 0-14 4-20 11-15 19-23 41-29 61 0 0 0 0 0 1l0 1c0 1 0 1 1 2 0 1 1 1 2 1l1 0 29-2 17-1c2-1 3-2 3-3l0-69z m-21 165l18-2c2 0 3-1 3-2l0-75c0-1-1-2-1-2-1-1-2-1-2-1 0 0 0 0-1 0 0 0-14 2-21 2-8 1-17 2-26 3-4 0-6 4-6 7-2 18-3 35-3 47 0 5-1 22-1 22 0 1 1 2 1 2 1 1 1 1 2 1z m-98 32l1 1c12 31 42 59 76 71l3 1c1 0 2 0 3-1 0-1 0-2 0-3l-2-3c-18-25-25-53-31-79l0-1c0-1 0-2-1-2 0 0-1-1-1-1-1 0-1 0-1 0l-1 1c-5 1-11 3-17 4-8 2-18 4-27 8l0 1c-2 0-2 2-2 3z m-8-18l1 1c0 1 1 2 3 2l49-13c1 0 2-1 2-3l2-71c0-1 0-2-1-2-1-1-1-1-2-1 0 0 0 0 0 0l-1 0c-1 0-2 1-3 1l0 0c-2 0-4 1-7 1-16 4-29 9-39 17-6 4-9 8-9 15-1 19 1 37 5 53z m178 42c-8 16-14 31-21 46-8 17-17 35-24 52-14 31 3 65 36 71 28 5 56-16 59-44 0-9-1-18-5-26-14-32-29-64-44-97 0 0-1-1-1-2z m27 117c0 15-12 28-27 28-15 0-27-12-28-27 0-16 13-28 28-28 15 0 27 12 27 27z"/>
<glyph glyph-name="box" unicode="&#86;" d="M318 74l126 89 15-22-126-88z m-137 101l0-99 27 0 0 96z m145-125c-1 0-1 0-2 0l-262 26c-7 1-12 7-12 13l0 263c0 4 1 7 4 10 3 2 7 4 10 3l263-26c7 0 12-6 12-13l0-262c0-4-2-8-4-10-3-3-6-4-9-4z m-250 51l236-23 0 236-236 23z m377 326l-263 26c-3 1-7-1-10-3-3-3-4-6-4-10l0-21c3 2 7 3 11 3 0 0 0 0 1 0 3 2 7 4 11 4 2 0 3 0 5-1l234-23 0-236c0-7-3-10-10-14 1-2 1-5 1-7 1-2 0-3 0-5l21-2c1 0 1 0 2 0 3 0 6 1 8 4 3 2 5 6 5 10l0 262c0 7-5 13-12 13z m-397-64l125 88 16-22-126-88z m262-26l126 88 15-22-126-88z m-256-123l2 27 263-26-3-26z m146 37l0 91-27 0 0-88c9-1 18-2 27-3z"/>
<glyph glyph-name="community" unicode="&#48;" d="M50 175c-4 0-8 2-11 6-4 6-2 14 4 18l24 16 69 48 89-64c6-4 7-13 3-19-5-6-13-7-19-3l-74 53-53-37-24-16c-3-1-5-2-8-2z m130-10l-44 32-47-32-22-14 0-63 135 0 0 60z m120 10c-4 0-9 2-11 6-4 6-3 14 3 18l25 16 68 48 89-64c6-4 7-13 3-19-4-6-13-7-19-3l-73 53-54-37-24-16c-2-1-5-2-7-2z m129-10l-46 32-45-32-22-14 0-63 135 0 0 60z m-256 202c-4 0-9 2-11 5-4 7-3 15 4 19l24 16 68 48 89-65c6-4 7-12 3-18-4-6-12-7-18-3l-74 53-53-37-25-16c-2-2-5-2-7-2z m129-11l-46 32-45-31-22-15 0-62 135 0 0 60z"/>
@ -103,7 +100,6 @@
<glyph glyph-name="lock" unicode="&#57350;" d="M389 233l0 62c0 68-55 124-123 124-69 0-124-56-124-124l0-62c-24-4-44-26-44-52l0-74c0-29 24-52 52-52l230 0c29 0 53 23 53 52l0 74c0 26-18 48-44 52z m-123 129c37 0 67-30 67-67l0-61-135 0 0 61c0 37 31 67 68 67z"/>
<glyph glyph-name="visible" unicode="&#57351;" d="M258 116c-55 0-106 17-147 51-31 25-47 51-47 52-4 7-4 16 1 23 2 4 66 98 195 96 133-3 192-93 195-97 4-6 4-15 0-22 0-1-15-27-46-53-29-23-79-50-151-50 0 0 0 0 0 0z m-148 113c7-7 17-18 30-29 34-27 73-40 118-40 0 0 0 0 0 0 47 0 88 13 122 40 13 10 23 21 29 29-7 7-16 16-30 26-34 25-74 38-119 38-81 2-130-42-150-64z m-27 1z m227-4c0-25-21-46-47-46-26 0-47 21-47 46 0 26 21 47 47 47 26 0 47-21 47-47z"/>
<glyph glyph-name="model" unicode="&#57352;" d="M494 395c-2 5-8 8-13 7l-90-16 45 72c3 5 2 11-1 15-4 4-10 5-15 3l-213-98c-15 5-72 27-111 43 0 0-1 0-1 0 0 0 0 0 0 0 0 0-1 0-1 1 0 0-1 0-1 0 0 0-1 0-1 0 0 0 0 0 0 0-1 0-1 0-2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-1 0-1 0 0 0 0 0 0-1-1 0-1 0-2 0 0 0 0 0 0 0 0 0-1 0-1 0 0 0 0-1 0-1-1 0-2 0-3-1 0 0 0 0 0 0 0-1-1-1-1-1 0 0 0 0 0 0 0 0 0-1 0-1-1 0-1 0-1 0 0 0 0 0 0-1 0 0 0 0-1-1l-27-52-33-40c-3-4-3-10 0-15 2-3 6-5 10-5 1 0 2 0 4 1l50 17 40 2-26-51c-3-4-2-9 1-13 1-1 26-30 52-58 15-17 28-30 38-40 6-6 11-11 15-14l-16-61-46-18c-6-3-9-10-6-16 2-5 6-8 11-8 1 0 3 1 4 1l45 18 17-15c2-3 5-4 8-4 4 0 7 2 9 5 5 5 4 12-1 17l-17 15 16 61 76-90c2-2 6-4 9-4 1 0 2 0 3 0l85 23c5 2 8 6 9 11 0 5-2 9-7 12l-136 72 45 91 178 123c5 3 6 9 4 15z m-200-117l-122 55 41 21 181 83z m-148 73l-24 33c16-6 39-15 54-21z m-59-6l-9 13 15 29 27-38 2-2z m36-77l23 44c18-45 35-91 47-121-19 20-45 49-70 77z m194-194l-57 68 105-55z m-94 101c-5 14-42 104-30 77 0-2-21 49-28 66l121-59-48-95z m108 120l43 63 70 16z"/>
<glyph glyph-name="forward" unicode="&#68;" d="M330 278l-95 70c-5 4-12 5-18 2-5-3-9-9-9-16l0-150c0-7 4-13 10-16 2-1 5-2 7-2 4 0 8 2 11 5l95 79c4 4 6 9 6 14-1 5-3 10-7 14"/>
<glyph glyph-name="avatar-2" unicode="&#57353;" d="M256 88c-93 0-169 75-169 168 0 93 76 169 169 169 93 0 169-76 169-169 0-93-76-168-169-168z m0 316c-81 0-148-66-148-148 0-81 67-147 148-147 81 0 148 66 148 147 0 82-67 148-148 148z m97-90l-1 1c-3 3-7 4-10 4-1 0-61-9-86-9-1 0-1 0-2 0-25 0-87 10-87 10-5 0-10-2-13-6l-1-2c-2-3-2-7-1-10 1-4 3-6 6-8 12-5 49-20 60-22 2 0 5 0 6-7 1-8-3-46-7-65-5-17-13-40-13-41-2-6 1-13 7-15l8-3c3-1 6-1 9 1 3 1 5 4 6 7l21 65 20-67c1-3 3-6 6-7 2-1 4-1 5-1 2 0 3 0 5 0l7 3c5 2 8 8 7 14 0 0-6 24-11 44-3 12-4 30-5 45 0 9-1 16-2 22 0 1 0 4 5 5 0 0 1 0 2 0l55 22c4 2 6 5 7 9 1 4 0 8-3 11z m-68 37c0-16-13-29-29-29-16 0-29 13-29 29 0 16 13 29 29 29 16 0 29-13 29-29z"/>
<glyph glyph-name="arrow-dn" unicode="&#53;" d="M258 219l-43 55 86 0z"/>
<glyph glyph-name="arrow-up" unicode="&#54;" d="M258 283l43-55-86 0z"/>
@ -154,4 +150,8 @@
<glyph glyph-name="uninstall" unicode="&#57395;" d="M83 227c-7 0-13-6-13-13l0-78c0-19 16-35 35-35l297 0c19 0 35 16 35 35l0 78c0 7-6 13-13 13-7 0-13-6-13-13l0-78c0-5-4-9-9-9l-297 0c-5 0-9 4-9 9l0 78c0 7-6 13-13 13z m191 47l50 50c5 5 5 14 0 19-5 5-13 5-19 0l-50-50-50 50c-5 5-13 5-19 0-5-5-5-14 0-19l50-50-50-50c-5-5-5-14 0-19 5-5 14-5 19 0l50 50 50-50c5-5 14-5 19 0 5 5 5 14 0 19z"/>
<glyph glyph-name="install" unicode="&#57391;" d="M83 227c-7 0-13-6-13-13l0-78c0-19 16-35 35-35l297 0c19 0 35 16 35 35l0 78c0 7-6 13-13 13-7 0-13-6-13-13l0-78c0-5-4-9-9-9l-297 0c-5 0-9 4-9 9l0 78c0 7-6 13-13 13z m170 171l0-155-33 27c-6 5-14 5-19-1-4-5-4-14 2-18l54-48c3-2 5-3 8-3 3 0 7 1 9 3l54 48c5 4 6 13 1 18-4 5-13 6-18 1l-32-27 0 154c0 8-6 13-13 13-7 0-13-5-13-12z"/>
<glyph glyph-name="ellipsis-vertical" unicode="&#57396;" d="M276 178c0-14-11-24-24-24-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24z m0 78c0-14-11-24-24-24-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24z m0 78c0-14-11-24-24-24-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24z"/>
<glyph glyph-name="backward" unicode="&#69;" d="M292 349c-5 3-12 3-18-1l-94-71c-4-3-7-8-7-13 0-5 2-10 6-14l95-80c3-2 7-4 11-4 2 0 5 1 7 2 6 3 10 9 10 15l0 151c0 6-4 12-10 15"/>
<glyph glyph-name="40-reload" unicode="&#70;" d="M365 261c-9 1-17-5-18-15-4-45-43-80-89-80-49 0-89 40-89 89 0 19 4 45 25 65 16 15 39 23 68 25l-15-16c-6-6-6-17 0-24 4-3 8-4 12-4 4 0 9 1 12 5l43 44c2 2 3 4 4 6 2 6 1 13-4 18l-44 44c-6 6-17 6-23 0-7-7-7-17 0-24l15-15c-38-2-69-14-91-35-23-21-36-53-36-88 0-68 55-123 123-123 64 0 116 47 122 110 1 9-5 18-15 18"/>
<glyph glyph-name="forward" unicode="&#68;" d="M330 278l-95 70c-5 4-12 5-18 2-5-3-9-9-9-16l0-150c0-7 4-13 10-16 2-1 5-2 7-2 4 0 8 2 11 5l95 79c4 4 6 9 6 14-1 5-3 10-7 14"/>
<glyph glyph-name="avatar-1" unicode="&#84;" d="M396 344l-2 2c-4 4-9 5-15 5-1 0-88-13-124-14-1 0-2 0-3 0-37 0-126 15-127 15-7 1-14-2-18-8l-2-4c-3-4-3-9-2-14 2-5 5-9 10-11 16-7 69-22 85-29 3-1 10-4 10-14 1-11-4-67-10-93-7-26-18-60-19-60-3-9 2-19 11-22l11-4c4-2 9-1 13 1 5 2 8 6 9 10l31 94 28-96c2-5 5-9 9-11 3-1 5-2 8-2 2 0 4 0 7 1l10 4c8 3 12 12 10 20 0 1-8 36-16 65-4 17-6 43-7 64-1 13-1 21-3 30 0 1 2 11 10 14 10 4 81 29 80 28 6 2 10 7 11 13 1 6-1 12-5 16z m-98 54c0-24-19-43-43-43-24 0-43 19-43 43 0 23 19 42 43 42 24 0 43-19 43-42z"/>
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View file

@ -127,14 +127,6 @@
<div class="icon icon-close-small"></div>
<input type="text" readonly="readonly" value="close-small">
</li>
<li>
<div class="icon icon-backward"></div>
<input type="text" readonly="readonly" value="backward">
</li>
<li>
<div class="icon icon-reload"></div>
<input type="text" readonly="readonly" value="reload">
</li>
<li>
<div class="icon icon-minimize"></div>
<input type="text" readonly="readonly" value="minimize">
@ -179,10 +171,6 @@
<div class="icon icon-hifi-logo-small"></div>
<input type="text" readonly="readonly" value="hifi-logo-small">
</li>
<li>
<div class="icon icon-avatar-1"></div>
<input type="text" readonly="readonly" value="avatar-1">
</li>
<li>
<div class="icon icon-placemark"></div>
<input type="text" readonly="readonly" value="placemark">
@ -399,10 +387,6 @@
<div class="icon icon-model"></div>
<input type="text" readonly="readonly" value="model">
</li>
<li>
<div class="icon icon-forward"></div>
<input type="text" readonly="readonly" value="forward">
</li>
<li>
<div class="icon icon-avatar-2"></div>
<input type="text" readonly="readonly" value="avatar-2">
@ -603,6 +587,22 @@
<div class="icon icon-ellipsis-vertical"></div>
<input type="text" readonly="readonly" value="ellipsis-vertical">
</li>
<li>
<div class="icon icon-backward"></div>
<input type="text" readonly="readonly" value="backward">
</li>
<li>
<div class="icon icon-40-reload"></div>
<input type="text" readonly="readonly" value="40-reload">
</li>
<li>
<div class="icon icon-forward"></div>
<input type="text" readonly="readonly" value="forward">
</li>
<li>
<div class="icon icon-avatar-1"></div>
<input type="text" readonly="readonly" value="avatar-1">
</li>
</ul>
<h2>Character mapping</h2>
<ul class="glyphs character-mapping">
@ -718,14 +718,6 @@
<div data-icon="C" class="icon"></div>
<input type="text" readonly="readonly" value="C">
</li>
<li>
<div data-icon="E" class="icon"></div>
<input type="text" readonly="readonly" value="E">
</li>
<li>
<div data-icon="F" class="icon"></div>
<input type="text" readonly="readonly" value="F">
</li>
<li>
<div data-icon="I" class="icon"></div>
<input type="text" readonly="readonly" value="I">
@ -770,10 +762,6 @@
<div data-icon="S" class="icon"></div>
<input type="text" readonly="readonly" value="S">
</li>
<li>
<div data-icon="T" class="icon"></div>
<input type="text" readonly="readonly" value="T">
</li>
<li>
<div data-icon="U" class="icon"></div>
<input type="text" readonly="readonly" value="U">
@ -990,10 +978,6 @@
<div data-icon="&#xe008;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe008;">
</li>
<li>
<div data-icon="D" class="icon"></div>
<input type="text" readonly="readonly" value="D">
</li>
<li>
<div data-icon="&#xe009;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe009;">
@ -1194,6 +1178,22 @@
<div data-icon="&#xe034;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe034;">
</li>
<li>
<div data-icon="E" class="icon"></div>
<input type="text" readonly="readonly" value="E">
</li>
<li>
<div data-icon="F" class="icon"></div>
<input type="text" readonly="readonly" value="F">
</li>
<li>
<div data-icon="D" class="icon"></div>
<input type="text" readonly="readonly" value="D">
</li>
<li>
<div data-icon="T" class="icon"></div>
<input type="text" readonly="readonly" value="T">
</li>
</ul>
</div>
<script>(function() {

View file

@ -122,12 +122,6 @@
.icon-close-small:before {
content: "\43";
}
.icon-backward:before {
content: "\45";
}
.icon-reload:before {
content: "\46";
}
.icon-minimize:before {
content: "\49";
}
@ -161,9 +155,6 @@
.icon-hifi-logo-small:before {
content: "\53";
}
.icon-avatar-1:before {
content: "\54";
}
.icon-placemark:before {
content: "\55";
}
@ -326,9 +317,6 @@
.icon-model:before {
content: "\e008";
}
.icon-forward:before {
content: "\44";
}
.icon-avatar-2:before {
content: "\e009";
}
@ -479,3 +467,15 @@
.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";
}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
<g>
<path d="M78.3,31.3l-0.4-0.4c-0.8-0.8-2-1.2-3.1-1c-0.2,0-18.6,2.7-26.2,2.8c-0.2,0-0.3,0-0.4,0c-7.8,0-26.6-3-26.8-3.1
c-1.5-0.2-3,0.4-3.7,1.7l-0.5,0.8c-0.6,0.9-0.7,2-0.4,3c0.3,1,1.1,1.8,2,2.3c3.4,1.5,14.9,6.3,18.2,6.7c0.6,0.1,1.7,0.2,1.8,2.2
c0.2,2.4-0.8,14.1-2.2,19.7c-1.4,5.4-3.8,12.5-3.9,12.6c-0.7,1.9,0.3,4,2.2,4.7l2.3,0.8c0.9,0.3,2,0.3,2.8-0.2
c0.9-0.4,1.6-1.2,1.9-2.2l6.4-19.7l6,20.3c0.3,1,1,1.8,1.9,2.3c0.5,0.2,1.1,0.4,1.6,0.4c0.5,0,0.9-0.1,1.3-0.2l2.1-0.8
c1.7-0.7,2.7-2.5,2.2-4.3c0-0.1-1.8-7.4-3.3-13.5c-0.9-3.7-1.3-9.2-1.6-13.6c-0.2-2.7-0.3-5-0.6-6.8c-0.1-0.3-0.2-1.1,1.3-1.4
c0.3,0,0.4-0.1,0.6-0.2l16.9-6.7c1.2-0.5,2-1.5,2.3-2.7C79.5,33.5,79.2,32.2,78.3,31.3z"/>
<ellipse cx="48.7" cy="20" rx="9" ry="9"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M78.3,31.3l-0.4-0.4c-0.8-0.8-2-1.2-3.1-1c-0.2,0-18.6,2.7-26.2,2.8c-0.2,0-0.3,0-0.4,0
c-7.8,0-26.6-3-26.8-3.1c-1.5-0.2-3,0.4-3.7,1.7l-0.5,0.8c-0.6,0.9-0.7,2-0.4,3c0.3,1,1.1,1.8,2,2.3c3.4,1.5,14.9,6.3,18.2,6.7
c0.6,0.1,1.7,0.2,1.8,2.2c0.2,2.4-0.8,14.1-2.2,19.7c-1.4,5.4-3.8,12.5-3.9,12.6c-0.7,1.9,0.3,4,2.2,4.7l2.3,0.8
c0.9,0.3,2,0.3,2.8-0.2c0.9-0.4,1.6-1.2,1.9-2.2l6.4-19.7l6,20.3c0.3,1,1,1.8,1.9,2.3c0.5,0.2,1.1,0.4,1.6,0.4
c0.5,0,0.9-0.1,1.3-0.2l2.1-0.8c1.7-0.7,2.7-2.5,2.2-4.3c0-0.1-1.8-7.4-3.3-13.5c-0.9-3.7-1.3-9.2-1.6-13.6c-0.2-2.7-0.3-5-0.6-6.8
c-0.1-0.3-0.2-1.1,1.3-1.4c0.3,0,0.4-0.1,0.6-0.2l16.9-6.7c1.2-0.5,2-1.5,2.3-2.7C79.5,33.5,79.2,32.2,78.3,31.3z"/>
<ellipse class="st0" cx="48.7" cy="20" rx="9" ry="9"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,9 @@
<svg width="24" height="22" viewBox="0 0 24 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Canvas" fill="none">
<g id="Vector">
<path d="M 4.29197 19.3019C 4.20267 19.3019 4.11337 19.2697 4.02407 19.2052C 3.96826 19.1622 3.81199 19.0548 3.90129 18.6894L 4.21383 17.4427C 4.55987 16.0778 4.89474 14.7236 5.24078 13.3588C 5.43054 12.6064 5.20729 11.9509 4.60452 11.4672C 3.80082 10.8117 2.9748 10.1453 2.18227 9.51126C 1.54601 8.99539 0.898591 8.46878 0.262332 7.95292C -0.0278912 7.72723 -0.0167287 7.57677 0.0167586 7.41556C 0.0390835 7.32958 0.0948956 7.07165 0.53023 7.05015C 2.65109 6.91044 4.51522 6.78147 6.23423 6.66326C 7.02677 6.60952 7.6407 6.19038 7.93092 5.48106C 8.34393 4.51382 8.75694 3.54657 9.15879 2.56858C 9.47134 1.83777 9.78389 1.09621 10.0964 0.365404C 10.2192 0.0752303 10.3643 0.0107472 10.5987 1.02493e-08L 10.6099 1.02493e-08C 10.8108 1.02493e-08 10.9671 0.193449 11.0452 0.365404L 11.8266 2.20317C 12.2954 3.29939 12.7643 4.3956 13.2219 5.50256C 13.5121 6.17963 14.1038 6.59877 14.8628 6.65251C 15.7558 6.71699 16.6488 6.78147 17.5195 6.83521C 18.0664 6.86745 18.6245 6.91044 19.1715 6.94268C 19.2943 6.95343 19.4171 6.96418 19.5399 6.96418C 19.9752 6.99642 20.377 7.01791 20.79 7.07165C 21.1249 7.11464 21.1584 7.46929 21.1584 7.57677L 21.1584 7.60901C 21.1584 7.71648 20.9686 7.87769 20.8905 7.94217C 20.4217 8.31832 19.9417 8.68373 19.4729 9.05988C 18.5129 9.81218 17.5306 10.586 16.5595 11.3383C 15.8563 11.8864 15.5995 12.6064 15.8116 13.4447C 16.1242 14.6377 16.4255 15.8521 16.7269 17.0343C 16.8609 17.5716 16.9948 18.109 17.1288 18.6464C 17.1734 18.8398 17.1511 19.0225 17.0506 19.1515C 16.9725 19.2375 16.872 19.2912 16.7381 19.2912C 16.7158 19.2912 16.6934 19.2912 16.6711 19.2912C 16.6488 19.2912 16.5818 19.2697 16.4702 19.2052C 14.7958 18.2272 13.1438 17.2492 11.5587 16.3035C 11.2238 16.0993 10.8666 16.0026 10.5094 16.0026C 10.1522 16.0026 9.79505 16.11 9.44901 16.3142C 8.24347 17.0343 7.02677 17.7651 5.84355 18.4744L 4.61568 19.2052C 4.49289 19.2697 4.38127 19.3019 4.29197 19.3019Z" transform="translate(1.5 1)" fill="#FFB017"/>
<path d="M 4.29197 19.3019C 4.20267 19.3019 4.11337 19.2697 4.02407 19.2052C 3.96826 19.1622 3.81199 19.0548 3.90129 18.6894L 4.21383 17.4427C 4.55987 16.0778 4.89474 14.7236 5.24078 13.3588C 5.43054 12.6064 5.20729 11.9509 4.60452 11.4672C 3.80082 10.8117 2.9748 10.1453 2.18227 9.51126C 1.54601 8.99539 0.898591 8.46878 0.262332 7.95292C -0.0278912 7.72723 -0.0167287 7.57677 0.0167586 7.41556C 0.0390835 7.32958 0.0948956 7.07165 0.53023 7.05015C 2.65109 6.91044 4.51522 6.78147 6.23423 6.66326C 7.02677 6.60952 7.6407 6.19038 7.93092 5.48106C 8.34393 4.51382 8.75694 3.54657 9.15879 2.56858C 9.47134 1.83777 9.78389 1.09621 10.0964 0.365404C 10.2192 0.0752303 10.3643 0.0107472 10.5987 1.02493e-08L 10.6099 1.02493e-08C 10.8108 1.02493e-08 10.9671 0.193449 11.0452 0.365404L 11.8266 2.20317C 12.2954 3.29939 12.7643 4.3956 13.2219 5.50256C 13.5121 6.17963 14.1038 6.59877 14.8628 6.65251C 15.7558 6.71699 16.6488 6.78147 17.5195 6.83521C 18.0664 6.86745 18.6245 6.91044 19.1715 6.94268C 19.2943 6.95343 19.4171 6.96418 19.5399 6.96418C 19.9752 6.99642 20.377 7.01791 20.79 7.07165C 21.1249 7.11464 21.1584 7.46929 21.1584 7.57677L 21.1584 7.60901C 21.1584 7.71648 20.9686 7.87769 20.8905 7.94217C 20.4217 8.31832 19.9417 8.68373 19.4729 9.05988C 18.5129 9.81218 17.5306 10.586 16.5595 11.3383C 15.8563 11.8864 15.5995 12.6064 15.8116 13.4447C 16.1242 14.6377 16.4255 15.8521 16.7269 17.0343C 16.8609 17.5716 16.9948 18.109 17.1288 18.6464C 17.1734 18.8398 17.1511 19.0225 17.0506 19.1515C 16.9725 19.2375 16.872 19.2912 16.7381 19.2912C 16.7158 19.2912 16.6934 19.2912 16.6711 19.2912C 16.6488 19.2912 16.5818 19.2697 16.4702 19.2052C 14.7958 18.2272 13.1438 17.2492 11.5587 16.3035C 11.2238 16.0993 10.8666 16.0026 10.5094 16.0026C 10.1522 16.0026 9.79505 16.11 9.44901 16.3142C 8.24347 17.0343 7.02677 17.7651 5.84355 18.4744L 4.61568 19.2052C 4.49289 19.2697 4.38127 19.3019 4.29197 19.3019Z" stroke-width="1.75" transform="translate(1.5 1)" stroke="#121212"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,8 @@
<svg width="24" height="22" viewBox="0 0 24 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Canvas" fill="none">
<g id="Vector">
<path d="M 4.29197 19.3019C 4.20267 19.3019 4.11337 19.2697 4.02407 19.2052C 3.96826 19.1622 3.81199 19.0548 3.90129 18.6894L 4.21383 17.4427C 4.55987 16.0778 4.89474 14.7236 5.24078 13.3588C 5.43054 12.6064 5.20729 11.9509 4.60452 11.4672C 3.80082 10.8117 2.9748 10.1453 2.18227 9.51126C 1.54601 8.99539 0.898591 8.46878 0.262332 7.95292C -0.0278912 7.72723 -0.0167287 7.57677 0.0167586 7.41556C 0.0390835 7.32958 0.0948956 7.07165 0.53023 7.05015C 2.65109 6.91044 4.51522 6.78147 6.23423 6.66326C 7.02677 6.60952 7.6407 6.19038 7.93092 5.48106C 8.34393 4.51382 8.75694 3.54657 9.15879 2.56858C 9.47134 1.83777 9.78389 1.09621 10.0964 0.365404C 10.2192 0.0752303 10.3643 0.0107472 10.5987 1.02493e-08L 10.6099 1.02493e-08C 10.8108 1.02493e-08 10.9671 0.193449 11.0452 0.365404L 11.8266 2.20317C 12.2954 3.29939 12.7643 4.3956 13.2219 5.50256C 13.5121 6.17963 14.1038 6.59877 14.8628 6.65251C 15.7558 6.71699 16.6488 6.78147 17.5195 6.83521C 18.0664 6.86745 18.6245 6.91044 19.1715 6.94268C 19.2943 6.95343 19.4171 6.96418 19.5399 6.96418C 19.9752 6.99642 20.377 7.01791 20.79 7.07165C 21.1249 7.11464 21.1584 7.46929 21.1584 7.57677L 21.1584 7.60901C 21.1584 7.71648 20.9686 7.87769 20.8905 7.94217C 20.4217 8.31832 19.9417 8.68373 19.4729 9.05988C 18.5129 9.81218 17.5306 10.586 16.5595 11.3383C 15.8563 11.8864 15.5995 12.6064 15.8116 13.4447C 16.1242 14.6377 16.4255 15.8521 16.7269 17.0343C 16.8609 17.5716 16.9948 18.109 17.1288 18.6464C 17.1734 18.8398 17.1511 19.0225 17.0506 19.1515C 16.9725 19.2375 16.872 19.2912 16.7381 19.2912C 16.7158 19.2912 16.6934 19.2912 16.6711 19.2912C 16.6488 19.2912 16.5818 19.2697 16.4702 19.2052C 14.7958 18.2272 13.1438 17.2492 11.5587 16.3035C 11.2238 16.0993 10.8666 16.0026 10.5094 16.0026C 10.1522 16.0026 9.79505 16.11 9.44901 16.3142C 8.24347 17.0343 7.02677 17.7651 5.84355 18.4744L 4.61568 19.2052C 4.49289 19.2697 4.38127 19.3019 4.29197 19.3019Z" stroke-width="1.75" transform="translate(1.5 1)" stroke="#121212"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 134 134" style="enable-background:new 0 0 134 134;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1FC6A6;}
</style>
<path class="st0" d="M5,0h124c2.8,0,5,2.2,5,5v124c0,2.8-2.2,5-5,5H5c-2.8,0-5-2.2-5-5V5C0,2.2,2.2,0,5,0z"/>
<path d="M100.5,99c-9.2,9.2-20.3,13.8-33.5,13.8S42.6,108.2,33.3,99c-9.2-9.2-13.8-20.3-13.8-33.5s4.6-24.3,13.8-33.5
C42.6,22.7,53.9,18,67,18s24.3,4.7,33.5,14.1c9.4,9.2,14.1,20.3,14.1,33.5S109.8,89.9,100.5,99z M96.3,36.3
C88.2,28.1,78.4,23.9,67,23.9s-21.3,4.1-29.5,12.4c-8.1,8.1-12.1,17.8-12.1,29.3s4,21.2,12.1,29.3c8.3,8.1,18.1,12.1,29.5,12.1
s21.2-4,29.3-12.1c8.3-8.1,12.4-17.8,12.4-29.3S104.5,44.4,96.3,36.3z M94.3,49.3L94,49c-0.8-0.8-1.7-1.1-2.8-1.1l-1.7,0.3
c-1.3,0.2-3,0.5-5.1,0.8c-1.9,0.2-3.8,0.4-5.9,0.6c-2.1,0.2-4.2,0.4-6.5,0.6c-2.1,0.2-3.8,0.3-5.1,0.3h-0.6L42,47.6
c-1.7,0-2.9,0.6-3.7,1.7L38,49.8c-0.6,0.8-0.7,1.7-0.3,2.8c0.2,0.9,0.8,1.7,1.7,2.3c8.8,3.8,14.4,5.8,16.9,6.2c0.9,0,1.5,0.7,1.7,2
c0.4,3.8-0.3,9.8-2,18.3c-0.4,1.3-0.9,3-1.7,5.1c-0.6,1.9-1,3.4-1.4,4.5l-0.6,2c-0.8,2.1-0.1,3.5,2,4.2l2.3,0.8
c0.9,0.4,1.8,0.3,2.5-0.3c0.8-0.2,1.3-0.8,1.7-2l5.9-18.3l5.6,18.8c0.4,1.1,0.9,1.8,1.7,2c0.4,0.2,0.8,0.3,1.4,0.3h1.4l2-0.8
c1.7-0.8,2.3-2.1,2-3.9l-3.1-12.4c-0.6-2.1-1-6.3-1.4-12.7c0-1.9-0.2-3.9-0.6-6.2c0-0.8,0.5-1.2,1.4-1.4h0.6l15.5-6.2
c0.9-0.6,1.6-1.4,2-2.5S95.2,50.2,94.3,49.3z M72.6,33.2c1.7,1.5,2.5,3.4,2.5,5.6s-0.8,4.2-2.5,5.9C71.1,46.3,69.3,47,67,47
s-4.2-0.8-5.9-2.3c-1.5-1.7-2.3-3.7-2.3-5.9s0.8-4.1,2.3-5.6c1.7-1.7,3.7-2.5,5.9-2.5S71.1,31.5,72.6,33.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -120,12 +120,8 @@ Windows.Window {
Component.onCompleted: {
// Fix for parent loss on OSX:
parent.heightChanged.connect(function() {
updateContentParent();
});
parent.widthChanged.connect(function() {
updateContentParent();
});
parent.heightChanged.connect(updateContentParent);
parent.widthChanged.connect(updateContentParent);
x = interactiveWindowPosition.x;
y = interactiveWindowPosition.y;
@ -194,6 +190,11 @@ Windows.Window {
initialized = true;
}
Component.onDestruction: {
parent.heightChanged.disconnect(updateContentParent);
parent.widthChanged.disconnect(updateContentParent);
}
// Handle message traffic from the script that launched us to the loaded QML
function fromScript(message) {
if (root.dynamicContent && root.dynamicContent.fromScript) {

View file

@ -19,6 +19,8 @@ Original.Button {
property int color: 0
property int colorScheme: hifi.colorSchemes.light
property int fontSize: hifi.fontSizes.buttonLabel
property alias implicitTextWidth: buttonText.implicitWidth
property string buttonGlyph: "";
width: hifi.dimensions.buttonWidth
@ -108,7 +110,7 @@ Original.Button {
font.capitalization: Font.AllUppercase
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
size: hifi.fontSizes.buttonLabel
size: control.fontSize
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: control.text

View file

@ -46,6 +46,7 @@ FocusScope {
hoverEnabled: true
visible: true
height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control.
textRole: "text"
function previousItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count - 1) % comboBox.count; }
function nextItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count + 1) % comboBox.count; }

View file

@ -25,10 +25,16 @@ Original.RadioButton {
property int colorScheme: hifi.colorSchemes.light
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
readonly property int boxSize: 14
readonly property int boxRadius: 3
readonly property int checkSize: 10
readonly property int checkRadius: 2
property real letterSpacing: 1
property int fontSize: hifi.fontSizes.inputLabel
property int boxSize: defaultBoxSize
property real scaleFactor: boxSize / defaultBoxSize
readonly property int defaultBoxSize: 14
readonly property int boxRadius: 3 * scaleFactor
readonly property int checkSize: 10 * scaleFactor
readonly property int checkRadius: 2 * scaleFactor
readonly property int indicatorRadius: 7 * scaleFactor
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
@ -44,7 +50,7 @@ Original.RadioButton {
id: box
width: boxSize
height: boxSize
radius: 7
radius: indicatorRadius
x: radioButton.leftPadding
y: parent.height / 2 - height / 2
gradient: Gradient {
@ -66,7 +72,7 @@ Original.RadioButton {
id: check
width: checkSize
height: checkSize
radius: 7
radius: indicatorRadius
anchors.centerIn: parent
color: "#00B4EF"
border.width: 1
@ -77,7 +83,8 @@ Original.RadioButton {
contentItem: RalewaySemiBold {
text: radioButton.text
size: hifi.fontSizes.inputLabel
size: radioButton.fontSize
font.letterSpacing: letterSpacing
color: isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter

View file

@ -27,6 +27,9 @@ SpinBox {
property string suffix: ""
property string labelInside: ""
property color colorLabelInside: hifi.colors.white
property color backgroundColor: isLightColorScheme
? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray)
: (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow)
property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0)
property int decimals: 2;
property real factor: Math.pow(10, decimals)
@ -55,10 +58,14 @@ SpinBox {
onValueModified: realValue = value/factor
onValueChanged: realValue = value/factor
onRealValueChanged: {
var newValue = Math.round(realValue*factor);
if(value != newValue) {
value = newValue;
}
}
stepSize: realStepSize*factor
value: Math.round(realValue*factor)
to : realTo*factor
from : realFrom*factor
@ -69,9 +76,7 @@ SpinBox {
y: spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0
background: Rectangle {
color: isLightColorScheme
? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray)
: (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow)
color: backgroundColor
border.color: spinBoxLabelInside.visible ? spinBoxLabelInside.color : hifi.colors.primaryHighlight
border.width: spinBox.activeFocus ? spinBoxLabelInside.visible ? 2 : 1 : 0
}

View file

@ -26,11 +26,9 @@ Preference {
preference.save();
}
Item {
Row {
id: control
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: Math.max(spinnerLabel.height, spinner.controlHeight)
@ -40,15 +38,14 @@ Preference {
text: root.label + ":"
colorScheme: hifi.colorSchemes.dark
anchors {
left: parent.left
right: spinner.left
rightMargin: hifi.dimensions.labelPadding
verticalCenter: parent.verticalCenter
}
horizontalAlignment: Text.AlignRight
wrapMode: Text.Wrap
}
spacing: hifi.dimensions.labelPadding
SpinBox {
id: spinner
decimals: preference.decimals
@ -56,7 +53,6 @@ Preference {
maximumValue: preference.max
width: 100
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
}
colorScheme: hifi.colorSchemes.dark

View file

@ -0,0 +1,840 @@
import QtQuick 2.6
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQml.Models 2.1
import QtGraphicalEffects 1.0
import "../controls-uit" as HifiControls
import "../styles-uit"
import "avatarapp"
Rectangle {
id: root
width: 480
height: 706
property bool keyboardEnabled: true
property bool keyboardRaised: false
property bool punctuationMode: false
HifiControls.Keyboard {
id: keyboard
z: 1000
raised: parent.keyboardEnabled && parent.keyboardRaised
numeric: parent.punctuationMode
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
}
color: style.colors.white
property string getAvatarsMethod: 'getAvatars'
signal sendToScript(var message);
function emitSendToScript(message) {
sendToScript(message);
}
ListModel { // the only purpose of this model is to convert JS object to ListElement
id: currentAvatarModel
dynamicRoles: true;
function makeAvatarEntry(avatarObject) {
clear();
append(avatarObject);
return get(count - 1);
}
}
property var jointNames;
property var currentAvatarSettings;
function fetchAvatarModelName(marketId, avatar) {
var xmlhttp = new XMLHttpRequest();
var url = "https://highfidelity.com/api/v1/marketplace/items/" + marketId;
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState === XMLHttpRequest.DONE && xmlhttp.status === 200) {
try {
var marketResponse = JSON.parse(xmlhttp.responseText.trim())
if(marketResponse.status === 'success') {
avatar.modelName = marketResponse.data.title;
}
}
catch(err) {
console.error(err);
}
}
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
}
function getAvatarModelName() {
if(currentAvatar === null) {
return '';
}
if(currentAvatar.modelName !== undefined) {
return currentAvatar.modelName;
} else {
var marketId = allAvatars.extractMarketId(currentAvatar.avatarUrl);
if(marketId !== '') {
fetchAvatarModelName(marketId, currentAvatar);
}
}
var avatarUrl = currentAvatar.avatarUrl;
var splitted = avatarUrl.split('/');
return splitted[splitted.length - 1];
}
property string avatarName: currentAvatar ? currentAvatar.name : ''
property string avatarUrl: currentAvatar ? currentAvatar.thumbnailUrl : null
property bool isAvatarInFavorites: currentAvatar ? allAvatars.findAvatar(currentAvatar.name) !== undefined : false
property int avatarWearablesCount: currentAvatar ? currentAvatar.wearables.count : 0
property var currentAvatar: null;
function setCurrentAvatar(avatar, bookmarkName) {
var currentAvatarObject = allAvatars.makeAvatarObject(avatar, bookmarkName);
currentAvatar = currentAvatarModel.makeAvatarEntry(currentAvatarObject);
}
property url externalAvatarThumbnailUrl: '../../images/avatarapp/guy-in-circle.svg'
function fromScript(message) {
if(message.method === 'initialize') {
jointNames = message.data.jointNames;
emitSendToScript({'method' : getAvatarsMethod});
} else if(message.method === 'wearableUpdated') {
adjustWearables.refreshWearable(message.entityID, message.wearableIndex, message.properties, message.updateUI);
} else if(message.method === 'wearablesUpdated') {
var wearablesModel = currentAvatar.wearables;
wearablesModel.clear();
message.wearables.forEach(function(wearable) {
wearablesModel.append(wearable);
});
adjustWearables.refresh(currentAvatar);
} else if(message.method === 'scaleChanged') {
currentAvatar.avatarScale = message.value;
updateCurrentAvatarInBookmarks(currentAvatar);
} else if(message.method === 'externalAvatarApplied') {
currentAvatar.avatarUrl = message.avatarURL;
currentAvatar.thumbnailUrl = allAvatars.makeThumbnailUrl(message.avatarURL);
currentAvatar.entry.avatarUrl = currentAvatar.avatarUrl;
currentAvatar.modelName = undefined;
updateCurrentAvatarInBookmarks(currentAvatar);
} else if(message.method === 'settingChanged') {
currentAvatarSettings[message.name] = message.value;
} else if(message.method === 'changeSettings') {
currentAvatarSettings = message.settings;
} else if(message.method === 'bookmarkLoaded') {
setCurrentAvatar(message.data.currentAvatar, message.data.name);
var avatarIndex = allAvatars.findAvatarIndex(currentAvatar.name);
allAvatars.move(avatarIndex, 0, 1);
view.setPage(0);
} else if(message.method === 'bookmarkAdded') {
var avatar = allAvatars.findAvatar(message.bookmarkName);
if(avatar !== undefined) {
var avatarObject = allAvatars.makeAvatarObject(message.bookmark, message.bookmarkName);
for(var prop in avatarObject) {
avatar[prop] = avatarObject[prop];
}
if(currentAvatar.name === message.bookmarkName) {
currentAvatar = currentAvatarModel.makeAvatarEntry(avatarObject);
}
} else {
allAvatars.addAvatarEntry(message.bookmark, message.bookmarkName);
}
updateCurrentAvatarInBookmarks(currentAvatar);
} else if(message.method === 'bookmarkDeleted') {
pageOfAvatars.isUpdating = true;
var index = pageOfAvatars.findAvatarIndex(message.name);
var absoluteIndex = view.currentPage * view.itemsPerPage + index
allAvatars.remove(absoluteIndex)
pageOfAvatars.remove(index);
var itemsOnPage = pageOfAvatars.count;
var newItemIndex = view.currentPage * view.itemsPerPage + itemsOnPage;
if(newItemIndex <= (allAvatars.count - 1)) {
pageOfAvatars.append(allAvatars.get(newItemIndex));
} else {
if(!pageOfAvatars.hasGetAvatars())
pageOfAvatars.appendGetAvatars();
}
pageOfAvatars.isUpdating = false;
} else if(message.method === getAvatarsMethod) {
var getAvatarsData = message.data;
allAvatars.populate(getAvatarsData.bookmarks);
setCurrentAvatar(getAvatarsData.currentAvatar, '');
displayNameInput.text = getAvatarsData.displayName;
currentAvatarSettings = getAvatarsData.currentAvatarSettings;
updateCurrentAvatarInBookmarks(currentAvatar);
} else if(message.method === 'updateAvatarInBookmarks') {
updateCurrentAvatarInBookmarks(currentAvatar);
} else if(message.method === 'selectAvatarEntity') {
adjustWearables.selectWearableByID(message.entityID);
}
}
function updateCurrentAvatarInBookmarks(avatar) {
var bookmarkAvatarIndex = allAvatars.findAvatarIndexByValue(avatar);
if(bookmarkAvatarIndex === -1) {
avatar.name = '';
view.setPage(0);
} else {
var bookmarkAvatar = allAvatars.get(bookmarkAvatarIndex);
avatar.name = bookmarkAvatar.name;
view.selectAvatar(bookmarkAvatar);
}
}
property bool isInManageState: false
Component.onCompleted: {
}
AvatarAppStyle {
id: style
}
AvatarAppHeader {
id: header
z: 100
property string currentPage: "Avatar"
property bool mainPageVisible: !settings.visible && !adjustWearables.visible
Binding on currentPage {
when: settings.visible
value: "Avatar Settings"
}
Binding on currentPage {
when: adjustWearables.visible
value: "Adjust Wearables"
}
Binding on currentPage {
when: header.mainPageVisible
value: "Avatar"
}
pageTitle: currentPage
avatarIconVisible: mainPageVisible
settingsButtonVisible: mainPageVisible
onSettingsClicked: {
settings.open(currentAvatarSettings, currentAvatar.avatarScale);
}
}
Settings {
id: settings
anchors.left: parent.left
anchors.right: parent.right
anchors.top: header.bottom
anchors.bottom: parent.bottom
z: 3
onSaveClicked: function() {
var avatarSettings = {
dominantHand : settings.dominantHandIsLeft ? 'left' : 'right',
collisionsEnabled : settings.avatarCollisionsOn,
animGraphUrl : settings.avatarAnimationJSON,
collisionSoundUrl : settings.avatarCollisionSoundUrl
};
emitSendToScript({'method' : 'saveSettings', 'settings' : avatarSettings, 'avatarScale': settings.scaleValue})
close();
}
onCancelClicked: function() {
emitSendToScript({'method' : 'revertScale', 'avatarScale' : avatarScaleBackup});
close();
}
onScaleChanged: {
emitSendToScript({'method' : 'setScale', 'avatarScale' : scale})
}
}
AdjustWearables {
id: adjustWearables
anchors.left: parent.left
anchors.right: parent.right
anchors.top: header.bottom
anchors.bottom: parent.bottom
jointNames: root.jointNames
onWearableUpdated: {
emitSendToScript({'method' : 'adjustWearable', 'entityID' : id, 'wearableIndex' : index, 'properties' : properties})
}
onWearableDeleted: {
emitSendToScript({'method' : 'deleteWearable', 'entityID' : id, 'avatarName' : avatarName});
}
onAdjustWearablesOpened: {
emitSendToScript({'method' : 'adjustWearablesOpened', 'avatarName' : avatarName});
}
onAdjustWearablesClosed: {
emitSendToScript({'method' : 'adjustWearablesClosed', 'save' : status, 'avatarName' : avatarName});
}
onWearableSelected: {
emitSendToScript({'method' : 'selectWearable', 'entityID' : id});
}
z: 3
}
Rectangle {
id: mainBlock
anchors.left: parent.left
anchors.leftMargin: 30
anchors.right: parent.right
anchors.rightMargin: 30
anchors.top: header.bottom
anchors.bottom: favoritesBlock.top
// TextStyle1
RalewaySemiBold {
size: 24;
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: 34
}
// TextStyle1
RalewaySemiBold {
id: displayNameLabel
size: 24;
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: 25
text: 'Display Name'
}
InputField {
id: displayNameInput
font.family: "Fira Sans"
font.pixelSize: 15
anchors.left: displayNameLabel.right
anchors.leftMargin: 30
anchors.verticalCenter: displayNameLabel.verticalCenter
anchors.right: parent.right
width: 232
text: 'ThisIsDisplayName'
onEditingFinished: {
emitSendToScript({'method' : 'changeDisplayName', 'displayName' : text})
focus = false;
}
}
ShadowImage {
id: avatarImage
width: 134
height: 134
anchors.top: displayNameLabel.bottom
anchors.topMargin: 31
Binding on source {
when: avatarUrl !== ''
value: avatarUrl
}
visible: avatarImage.status !== Image.Loading && avatarImage.status !== Image.Error
fillMode: Image.PreserveAspectCrop
}
ShadowImage {
id: customAvatarImage
anchors.fill: avatarImage;
visible: avatarUrl === '' || avatarImage.status === Image.Error
source: externalAvatarThumbnailUrl
}
ShadowRectangle {
anchors.fill: avatarImage;
color: 'white'
visible: avatarImage.status === Image.Loading
radius: avatarImage.radius
dropShadowRadius: avatarImage.dropShadowRadius;
dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset
dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset
Spinner {
id: spinner
visible: parent.visible
anchors.fill: parent;
}
}
AvatarWearablesIndicator {
anchors.right: avatarImage.right
anchors.bottom: avatarImage.bottom
anchors.rightMargin: -radius
anchors.bottomMargin: 6.08
wearablesCount: avatarWearablesCount
visible: avatarWearablesCount !== 0
}
RowLayout {
id: star
anchors.top: avatarImage.top
anchors.topMargin: 11
anchors.left: avatarImage.right
anchors.leftMargin: 30.5
anchors.right: parent.right
spacing: 12.3
Image {
width: 21.2
height: 19.3
source: isAvatarInFavorites ? '../../images/FavoriteIconActive.svg' : '../../images/FavoriteIconInActive.svg'
anchors.verticalCenter: parent.verticalCenter
}
// TextStyle5
FiraSansSemiBold {
size: 22;
Layout.fillWidth: true
text: isAvatarInFavorites ? avatarName : "Add to Favorites"
elide: Qt.ElideRight
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
enabled: !isAvatarInFavorites
anchors.fill: star
onClicked: {
createFavorite.onSaveClicked = function() {
var entry = currentAvatar.entry;
var wearables = [];
for(var i = 0; i < currentAvatar.wearables.count; ++i) {
wearables.push(currentAvatar.wearables.get(i));
}
entry.avatarEntites = wearables;
currentAvatar.name = createFavorite.favoriteNameText;
emitSendToScript({'method': 'addAvatar', 'name' : currentAvatar.name});
createFavorite.close();
}
var avatarThumbnail = (avatarUrl === '' || avatarImage.status === Image.Error) ?
externalAvatarThumbnailUrl : avatarUrl;
createFavorite.open(root.currentAvatar.wearables.count, avatarThumbnail);
}
}
// TextStyle3
RalewayRegular {
id: avatarNameLabel
size: 22;
text: getAvatarModelName();
elide: Qt.ElideRight
anchors.right: linkLabel.left
anchors.left: avatarImage.right
anchors.leftMargin: 30
anchors.top: star.bottom
anchors.topMargin: 11
property bool hasMarketId: currentAvatar && allAvatars.extractMarketId(currentAvatar.avatarUrl) !== '';
MouseArea {
enabled: avatarNameLabel.hasMarketId
anchors.fill: parent;
onClicked: emitSendToScript({'method' : 'navigate', 'url' : allAvatars.makeMarketItemUrl(currentAvatar.avatarUrl)})
}
color: hasMarketId ? style.colors.blueHighlight : 'black'
}
// TextStyle3
RalewayRegular {
id: wearablesLabel
size: 22;
anchors.left: avatarImage.right
anchors.leftMargin: 30
anchors.top: avatarNameLabel.bottom
anchors.topMargin: 16
color: 'black'
text: 'Wearables'
}
SquareLabel {
id: linkLabel
anchors.right: parent.right
anchors.verticalCenter: avatarNameLabel.verticalCenter
glyphText: "."
glyphSize: 22
MouseArea {
anchors.fill: parent
onClicked: {
popup.showSpecifyAvatarUrl(function() {
var url = popup.inputText.text;
emitSendToScript({'method' : 'applyExternalAvatar', 'avatarURL' : url})
}, function(link) {
Qt.openUrlExternally(link);
});
}
}
}
SquareLabel {
anchors.right: parent.right
anchors.verticalCenter: wearablesLabel.verticalCenter
glyphText: "\ue02e"
visible: avatarWearablesCount !== 0
MouseArea {
anchors.fill: parent
onClicked: {
adjustWearables.open(currentAvatar);
}
}
}
// TextStyle3
RalewayRegular {
size: 22;
anchors.right: parent.right
anchors.verticalCenter: wearablesLabel.verticalCenter
font.underline: true
text: "Add"
color: 'black'
visible: avatarWearablesCount === 0
MouseArea {
anchors.fill: parent
onClicked: {
popup.showGetWearables(function() {
emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'})
}, function(link) {
emitSendToScript({'method' : 'navigate', 'url' : link})
});
}
}
}
}
Rectangle {
id: favoritesBlock
height: 407
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
color: style.colors.lightGrayBackground
// TextStyle1
RalewaySemiBold {
id: favoritesLabel
size: 24;
anchors.top: parent.top
anchors.topMargin: 15
anchors.left: parent.left
anchors.leftMargin: 30
text: "Favorites"
}
// TextStyle8
RalewaySemiBold {
id: manageLabel
color: style.colors.blueHighlight
size: 20;
anchors.top: parent.top
anchors.topMargin: 20
anchors.right: parent.right
anchors.rightMargin: 30
text: isInManageState ? "Back" : "Manage"
MouseArea {
anchors.fill: parent
onClicked: {
isInManageState = isInManageState ? false : true;
}
}
}
Item {
anchors.left: parent.left
anchors.leftMargin: 30
anchors.right: parent.right
anchors.rightMargin: 0
anchors.top: favoritesLabel.bottom
anchors.topMargin: 20
anchors.bottom: parent.bottom
GridView {
id: view
anchors.fill: parent
interactive: false;
currentIndex: currentAvatarIndexInBookmarksPage();
function currentAvatarIndexInBookmarksPage() {
return (currentAvatar && currentAvatar.name !== '' && !pageOfAvatars.isUpdating) ? pageOfAvatars.findAvatarIndex(currentAvatar.name) : -1;
}
property int horizontalSpacing: 18
property int verticalSpacing: 44
property int thumbnailWidth: 92
property int thumbnailHeight: 92
function selectAvatar(avatar) {
emitSendToScript({'method' : 'selectAvatar', 'name' : avatar.name})
}
function deleteAvatar(avatar) {
emitSendToScript({'method' : 'deleteAvatar', 'name' : avatar.name})
}
AvatarsModel {
id: allAvatars
}
property int itemsPerPage: 8
property int totalPages: Math.ceil((allAvatars.count + 1) / itemsPerPage)
property int currentPage: 0;
onCurrentPageChanged: {
currentIndex = Qt.binding(currentAvatarIndexInBookmarksPage);
}
property bool hasNext: currentPage < (totalPages - 1)
property bool hasPrev: currentPage > 0
function setPage(pageIndex) {
pageOfAvatars.isUpdating = true;
pageOfAvatars.clear();
var start = pageIndex * itemsPerPage;
var end = Math.min(start + itemsPerPage, allAvatars.count);
for(var itemIndex = 0; start < end; ++start, ++itemIndex) {
var avatarItem = allAvatars.get(start)
pageOfAvatars.append(avatarItem);
}
if(pageOfAvatars.count !== itemsPerPage)
pageOfAvatars.appendGetAvatars();
currentPage = pageIndex;
pageOfAvatars.isUpdating = false;
}
model: AvatarsModel {
id: pageOfAvatars
property bool isUpdating: false;
property var getMoreAvatarsEntry: {'thumbnailUrl' : '', 'name' : '', 'getMoreAvatars' : true}
function appendGetAvatars() {
append(getMoreAvatarsEntry);
}
function hasGetAvatars() {
return count != 0 && get(count - 1).getMoreAvatars
}
function removeGetAvatars() {
if(hasGetAvatars()) {
remove(count - 1)
}
}
}
flow: GridView.FlowLeftToRight
cellHeight: thumbnailHeight + verticalSpacing
cellWidth: thumbnailWidth + horizontalSpacing
delegate: Item {
id: delegateRoot
height: GridView.view.cellHeight
width: GridView.view.cellWidth
Item {
id: container
width: 92
height: 92
Behavior on y {
NumberAnimation {
duration: 100
}
}
states: [
State {
name: "hovered"
when: favoriteAvatarMouseArea.containsMouse;
PropertyChanges { target: favoriteAvatarMouseArea; anchors.bottomMargin: -5 }
PropertyChanges { target: container; y: -5 }
PropertyChanges { target: favoriteAvatarImage; dropShadowRadius: 10 }
PropertyChanges { target: favoriteAvatarImage; dropShadowVerticalOffset: 6 }
}
]
property bool highlighted: delegateRoot.GridView.isCurrentItem
AvatarThumbnail {
id: favoriteAvatarImage
externalAvatarThumbnailUrl: root.externalAvatarThumbnailUrl
avatarUrl: thumbnailUrl
border.color: container.highlighted ? style.colors.blueHighlight : 'transparent'
border.width: container.highlighted ? 4 : 0
wearablesCount: {
return !getMoreAvatars ? wearables.count : 0
}
visible: !getMoreAvatars
MouseArea {
id: favoriteAvatarMouseArea
anchors.fill: parent
anchors.margins: 0
enabled: !container.highlighted
hoverEnabled: enabled
onClicked: {
if(isInManageState) {
var currentItem = delegateRoot.GridView.view.model.get(index);
popup.showDeleteFavorite(currentItem.name, function() {
view.deleteAvatar(currentItem);
});
} else {
if(delegateRoot.GridView.view.currentIndex !== index) {
var currentItem = delegateRoot.GridView.view.model.get(index);
popup.showLoadFavorite(currentItem.name, function() {
view.selectAvatar(currentItem);
});
}
}
}
}
}
Rectangle {
anchors.fill: favoriteAvatarImage
color: '#AFAFAF'
opacity: 0.4
radius: 5
visible: isInManageState && !container.highlighted && !getMoreAvatars
}
HiFiGlyphs {
anchors.fill: parent
text: "{"
visible: isInManageState && !container.highlighted && !getMoreAvatars
horizontalAlignment: Text.AlignHCenter
size: 56
}
ShadowRectangle {
width: 92
height: 92
radius: 5
color: style.colors.blueHighlight
visible: getMoreAvatars && !isInManageState
HiFiGlyphs {
anchors.centerIn: parent
color: 'white'
size: 60
text: "K"
}
MouseArea {
anchors.fill: parent
onClicked: {
popup.showBuyAvatars(function() {
emitSendToScript({'method' : 'navigate', 'url' : 'hifi://BodyMart'})
}, function(link) {
emitSendToScript({'method' : 'navigate', 'url' : link})
});
}
}
}
}
// TextStyle7
FiraSansRegular {
id: text
size: 18;
lineHeightMode: Text.FixedHeight
lineHeight: 16.9;
width: view.thumbnailWidth
height: view.verticalSpacing
elide: Qt.ElideRight
anchors.top: container.bottom
anchors.topMargin: 8
anchors.horizontalCenter: container.horizontalCenter
verticalAlignment: Text.AlignTop
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: getMoreAvatars ? 'Get More Avatars' : name
visible: !getMoreAvatars || !isInManageState
}
}
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
width: 40
height: 40
color: 'transparent'
PageIndicator {
x: 1
hasNext: view.hasNext
hasPrev: view.hasPrev
onClicked: view.setPage(view.currentPage - 1)
}
}
spacing: 0
Rectangle {
width: 40
height: 40
color: 'transparent'
PageIndicator {
x: -1
isPrevious: false
hasNext: view.hasNext
hasPrev: view.hasPrev
onClicked: view.setPage(view.currentPage + 1)
}
}
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
}
}
MessageBoxes {
id: popup
}
CreateFavoriteDialog {
avatars: allAvatars
id: createFavorite
}
}

View file

@ -765,7 +765,7 @@ Rectangle {
TableViewColumn {
id: connectionsUserNameHeader;
role: "userName";
title: connectionsTable.rowCount + (connectionsTable.rowCount === 1 ? " NAME" : " NAMES");
title: connectionsUserModel.totalEntries + (connectionsUserModel.totalEntries === 1 ? " NAME" : " NAMES");
width: connectionsNameCardWidth;
movable: false;
resizable: false;

View file

@ -0,0 +1,359 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
Rectangle {
id: root;
visible: false;
width: 480
height: 706
color: 'white'
signal wearableUpdated(var id, int index, var properties);
signal wearableSelected(var id);
signal wearableDeleted(string avatarName, var id);
signal adjustWearablesOpened(var avatarName);
signal adjustWearablesClosed(bool status, var avatarName);
property bool modified: false;
Component.onCompleted: {
modified = false;
}
property var jointNames;
property string avatarName: ''
property var wearablesModel;
function open(avatar) {
adjustWearablesOpened(avatar.name);
visible = true;
avatarName = avatar.name;
wearablesModel = avatar.wearables;
refresh(avatar);
}
function refresh(avatar) {
wearablesCombobox.model.clear();
for(var i = 0; i < avatar.wearables.count; ++i) {
var wearable = avatar.wearables.get(i).properties;
for(var j = (wearable.modelURL.length - 1); j >= 0; --j) {
if(wearable.modelURL[j] === '/') {
wearable.text = wearable.modelURL.substring(j + 1) + ' [%jointIndex%]'.replace('%jointIndex%', jointNames[wearable.parentJointIndex]);
break;
}
}
wearablesCombobox.model.append(wearable);
}
wearablesCombobox.currentIndex = 0;
}
function refreshWearable(wearableID, wearableIndex, properties, updateUI) {
if(wearableIndex === -1) {
wearableIndex = wearablesCombobox.model.findIndexById(wearableID);
}
var wearable = wearablesCombobox.model.get(wearableIndex);
if(!wearable) {
return;
}
var wearableModelItemProperties = wearablesModel.get(wearableIndex).properties;
for(var prop in properties) {
wearable[prop] = properties[prop];
wearableModelItemProperties[prop] = wearable[prop];
if(updateUI) {
if(prop === 'localPosition') {
position.set(wearable[prop]);
} else if(prop === 'localRotationAngles') {
rotation.set(wearable[prop]);
} else if(prop === 'dimensions') {
scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x);
}
}
}
wearablesModel.setProperty(wearableIndex, 'properties', wearableModelItemProperties);
}
function getCurrentWearable() {
return wearablesCombobox.model.get(wearablesCombobox.currentIndex)
}
function selectWearableByID(entityID) {
for(var i = 0; i < wearablesCombobox.model.count; ++i) {
var wearable = wearablesCombobox.model.get(i);
if(wearable.id === entityID) {
wearablesCombobox.currentIndex = i;
break;
}
}
}
function close(status) {
visible = false;
adjustWearablesClosed(status, avatarName);
}
HifiConstants { id: hifi }
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
hoverEnabled: true;
}
Column {
anchors.top: parent.top
anchors.topMargin: 15
anchors.horizontalCenter: parent.horizontalCenter
spacing: 20
width: parent.width - 30 * 2
HifiControlsUit.ComboBox {
id: wearablesCombobox
anchors.left: parent.left
anchors.right: parent.right
comboBox.textRole: "text"
model: ListModel {
function findIndexById(id) {
for(var i = 0; i < count; ++i) {
var wearable = get(i);
if(wearable.id === id) {
return i;
}
}
return -1;
}
}
comboBox.onCurrentIndexChanged: {
var currentWearable = getCurrentWearable();
if(currentWearable) {
position.set(currentWearable.localPosition);
rotation.set(currentWearable.localRotationAngles);
scalespinner.set(currentWearable.dimensions.x / currentWearable.naturalDimensions.x)
wearableSelected(currentWearable.id);
}
}
}
Column {
width: parent.width
spacing: 5
Row {
spacing: 20
// TextStyle5
FiraSansSemiBold {
id: positionLabel
size: 22;
text: "Position"
}
// TextStyle7
FiraSansRegular {
size: 18;
lineHeightMode: Text.FixedHeight
lineHeight: 16.9;
text: "m"
anchors.verticalCenter: positionLabel.verticalCenter
}
}
Vector3 {
id: position
backgroundColor: "lightgray"
function set(localPosition) {
notify = false;
xvalue = localPosition.x
yvalue = localPosition.y
zvalue = localPosition.z
notify = true;
}
function notifyPositionChanged() {
modified = true;
var properties = {
localPosition: { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue }
};
wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties);
}
property bool notify: false;
onXvalueChanged: if(notify) notifyPositionChanged();
onYvalueChanged: if(notify) notifyPositionChanged();
onZvalueChanged: if(notify) notifyPositionChanged();
decimals: 2
realFrom: -10
realTo: 10
realStepSize: 0.01
}
}
Column {
width: parent.width
spacing: 5
Row {
spacing: 20
// TextStyle5
FiraSansSemiBold {
id: rotationLabel
size: 22;
text: "Rotation"
}
// TextStyle7
FiraSansRegular {
size: 18;
lineHeightMode: Text.FixedHeight
lineHeight: 16.9;
text: "deg"
anchors.verticalCenter: rotationLabel.verticalCenter
}
}
Vector3 {
id: rotation
backgroundColor: "lightgray"
function set(localRotationAngles) {
notify = false;
xvalue = localRotationAngles.x
yvalue = localRotationAngles.y
zvalue = localRotationAngles.z
notify = true;
}
function notifyRotationChanged() {
modified = true;
var properties = {
localRotationAngles: { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue }
};
wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties);
}
property bool notify: false;
onXvalueChanged: if(notify) notifyRotationChanged();
onYvalueChanged: if(notify) notifyRotationChanged();
onZvalueChanged: if(notify) notifyRotationChanged();
decimals: 0
realFrom: -180
realTo: 180
realStepSize: 1
}
}
Column {
width: parent.width
spacing: 5
// TextStyle5
FiraSansSemiBold {
size: 22;
text: "Scale"
}
Item {
width: parent.width
height: childrenRect.height
HifiControlsUit.SpinBox {
id: scalespinner
decimals: 2
realStepSize: 0.1
realFrom: 0.1
realTo: 3.0
realValue: 1.0
backgroundColor: "lightgray"
width: position.spinboxWidth
colorScheme: hifi.colorSchemes.light
property bool notify: false;
onValueChanged: if(notify) notifyScaleChanged();
function set(value) {
notify = false;
realValue = value
notify = true;
}
function notifyScaleChanged() {
modified = true;
var currentWearable = getCurrentWearable();
var naturalDimensions = currentWearable.naturalDimensions;
var properties = {
dimensions: {
'x' : realValue * naturalDimensions.x,
'y' : realValue * naturalDimensions.y,
'z' : realValue * naturalDimensions.z
}
};
wearableUpdated(currentWearable.id, wearablesCombobox.currentIndex, properties);
}
}
HifiControlsUit.Button {
fontSize: 18
height: 40
anchors.right: parent.right
color: hifi.buttons.red;
colorScheme: hifi.colorSchemes.dark;
text: "TAKE IT OFF"
onClicked: wearableDeleted(root.avatarName, getCurrentWearable().id);
enabled: wearablesCombobox.model.count !== 0
anchors.verticalCenter: scalespinner.verticalCenter
}
}
}
}
DialogButtons {
anchors.bottom: parent.bottom
anchors.bottomMargin: 30
anchors.left: parent.left
anchors.leftMargin: 30
anchors.right: parent.right
anchors.rightMargin: 30
yesText: "SAVE"
noText: "CANCEL"
onYesClicked: function() {
root.close(true);
}
onNoClicked: function() {
root.close(false);
}
}
}

View file

@ -0,0 +1,58 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import "../../styles-uit"
ShadowRectangle {
id: header
anchors.left: parent.left
anchors.right: parent.right
height: 60
property alias pageTitle: title.text
property alias avatarIconVisible: avatarIcon.visible
property alias settingsButtonVisible: settingsButton.visible
signal settingsClicked;
AvatarAppStyle {
id: style
}
color: style.colors.lightGrayBackground
HiFiGlyphs {
id: avatarIcon
anchors.left: parent.left
anchors.leftMargin: 23
anchors.verticalCenter: header.verticalCenter
size: 38
text: "<"
}
// TextStyle6
RalewaySemiBold {
id: title
size: 22;
anchors.left: avatarIcon.visible ? avatarIcon.right : avatarIcon.left
anchors.leftMargin: 4
anchors.verticalCenter: avatarIcon.verticalCenter
text: 'Avatar'
}
HiFiGlyphs {
id: settingsButton
anchors.right: parent.right
anchors.rightMargin: 30
anchors.verticalCenter: avatarIcon.verticalCenter
text: "&"
MouseArea {
id: settingsMouseArea
anchors.fill: parent
onClicked: {
settingsClicked();
}
}
}
}

View file

@ -0,0 +1,29 @@
//
// HiFiConstants.qml
//
// Created by Alexander Ivash on 17 Apr 2018
// 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
//
import QtQuick 2.5
import QtQuick.Window 2.2
import "../../styles-uit"
QtObject {
readonly property QtObject colors: QtObject {
readonly property color lightGrayBackground: "#f2f2f2"
readonly property color black: "#000000"
readonly property color white: "#ffffff"
readonly property color blueHighlight: "#00b4ef"
readonly property color inputFieldBackground: "#d4d4d4"
readonly property color yellowishOrange: "#ffb017"
readonly property color blueAccent: "#0093c5"
readonly property color greenHighlight: "#1fc6a6"
readonly property color lightGray: "#afafaf"
readonly property color redHighlight: "#ea4c5f"
readonly property color orangeAccent: "#ff6309"
}
}

View file

@ -0,0 +1,73 @@
import QtQuick 2.9
import QtGraphicalEffects 1.0
Item {
width: 92
height: 92
property alias wearableIndicator: indicator
property int wearablesCount: 0
onWearablesCountChanged: {
console.debug('AvatarThumbnail: wearablesCount = ', wearablesCount)
}
property alias dropShadowRadius: avatarImage.dropShadowRadius
property alias dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset
property alias dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset
property url externalAvatarThumbnailUrl;
property var avatarUrl;
property alias border: avatarImage.border
ShadowImage {
id: avatarImage
anchors.fill: parent
radius: 5
fillMode: Image.PreserveAspectCrop
Binding on source {
when: avatarUrl !== ''
value: avatarUrl
}
onSourceChanged: {
console.debug('avatarImage: source = ', source);
}
visible: avatarImage.status !== Image.Loading && avatarImage.status !== Image.Error
}
ShadowImage {
id: customAvatarImage
anchors.fill: avatarImage;
visible: avatarUrl === '' || avatarImage.status === Image.Error
source: externalAvatarThumbnailUrl
}
ShadowRectangle {
anchors.fill: parent;
color: 'white'
visible: avatarImage.status === Image.Loading
radius: avatarImage.radius
border.width: avatarImage.border.width
border.color: avatarImage.border.color
dropShadowRadius: avatarImage.dropShadowRadius;
dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset
dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset
Spinner {
id: spinner
visible: parent.visible
anchors.fill: parent;
}
}
AvatarWearablesIndicator {
id: indicator
anchors.left: avatarImage.left
anchors.bottom: avatarImage.bottom
anchors.leftMargin: 57
wearablesCount: parent.wearablesCount
visible: parent.wearablesCount !== 0
}
}

View file

@ -0,0 +1,46 @@
import QtQuick 2.9
import "../../controls-uit"
import "../../styles-uit"
ShadowRectangle {
property int wearablesCount: 0
dropShadowRadius: 4
dropShadowHorizontalOffset: 0
dropShadowVerticalOffset: 0
width: 46.5
height: 46.5
radius: width / 2
AvatarAppStyle {
id: style
}
color: style.colors.greenHighlight
HiFiGlyphs {
width: 26.5
height: 13.8
anchors.top: parent.top
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: "\ue02e"
}
Item {
width: 46.57
height: 23
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 2.76
// TextStyle2
RalewayBold {
size: 15;
anchors.horizontalCenter: parent.horizontalCenter
text: wearablesCount
}
}
}

View file

@ -0,0 +1,228 @@
import QtQuick 2.9
ListModel {
id: model
function extractMarketId(avatarUrl) {
var guidRegexp = '([A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12})';
var regexp = new RegExp(guidRegexp,["i"]);
var match = regexp.exec(avatarUrl);
if (match !== null) {
return match[1];
}
return '';
}
function makeMarketItemUrl(avatarUrl) {
var marketItemUrl = "https://highfidelity.com/marketplace/items/%marketId%"
.split('%marketId%').join(extractMarketId(avatarUrl));
return marketItemUrl;
}
function makeThumbnailUrl(avatarUrl) {
var marketId = extractMarketId(avatarUrl);
if(marketId === '')
return '';
var avatarThumbnailUrl = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/%marketId%/large/hifi-mp-%marketId%.jpg"
.split('%marketId%').join(marketId);
return avatarThumbnailUrl;
}
function makeAvatarObject(avatar, avatarName) {
var avatarThumbnailUrl = makeThumbnailUrl(avatar.avatarUrl);
return {
'name' : avatarName,
'avatarScale' : avatar.avatarScale,
'thumbnailUrl' : avatarThumbnailUrl,
'avatarUrl' : avatar.avatarUrl,
'wearables' : avatar.avatarEntites ? avatar.avatarEntites : [],
'attachments' : avatar.attachments ? avatar.attachments : [],
'entry' : avatar,
'getMoreAvatars' : false
};
}
function addAvatarEntry(avatar, avatarName) {
var avatarEntry = makeAvatarObject(avatar, avatarName);
append(avatarEntry);
return allAvatars.count - 1;
}
function populate(bookmarks) {
clear();
for(var avatarName in bookmarks) {
var avatar = bookmarks[avatarName];
var avatarEntry = makeAvatarObject(avatar, avatarName);
append(avatarEntry);
}
}
function arraysAreEqual(a1, a2, comparer) {
if(Array.isArray(a1) && Array.isArray(a2)) {
if(a1.length !== a2.length) {
return false;
}
for(var i = 0; i < a1.length; ++i) {
if(!comparer(a1[i], a2[i])) {
return false;
}
}
} else if(Array.isArray(a1)) {
return a1.length === 0;
} else if(Array.isArray(a2)) {
return a2.length === 0;
}
return true;
}
function modelsAreEqual(m1, m2, comparer) {
if(m1.count !== m2.count) {
return false;
}
for(var i = 0; i < m1.count; ++i) {
var e1 = m1.get(i);
var allDifferent = true;
// it turns out order of wearables can randomly change so make position-independent comparison here
for(var j = 0; j < m2.count; ++j) {
var e2 = m2.get(j);
if(comparer(e1, e2)) {
allDifferent = false;
break;
}
}
if(allDifferent) {
return false;
}
}
return true;
}
function compareNumericObjects(o1, o2) {
if(o1 === undefined && o2 !== undefined)
return false;
if(o1 !== undefined && o2 === undefined)
return false;
for(var prop in o1) {
if(o1.hasOwnProperty(prop) && o2.hasOwnProperty(prop)) {
var v1 = o1[prop];
var v2 = o2[prop];
if(v1 !== v2 && Math.round(v1 * 500) != Math.round(v2 * 500)) {
return false;
}
}
}
return true;
}
function compareObjects(o1, o2, props, arrayProp) {
for(var i = 0; i < props.length; ++i) {
var prop = props[i];
var propertyName = prop.propertyName;
var comparer = prop.comparer;
var o1Value = arrayProp ? o1[arrayProp][propertyName] : o1[propertyName];
var o2Value = arrayProp ? o2[arrayProp][propertyName] : o2[propertyName];
if(comparer) {
if(comparer(o1Value, o2Value) === false) {
return false;
}
} else {
if(JSON.stringify(o1Value) !== JSON.stringify(o2Value)) {
return false;
}
}
}
return true;
}
function compareWearables(w1, w2) {
return compareObjects(w1, w2, [{'propertyName' : 'modelURL'},
{'propertyName' : 'parentJointIndex'},
{'propertyName' : 'marketplaceID'},
{'propertyName' : 'itemName'},
{'propertyName' : 'script'},
{'propertyName' : 'localPosition', 'comparer' : compareNumericObjects},
{'propertyName' : 'localRotationAngles', 'comparer' : compareNumericObjects},
{'propertyName' : 'dimensions', 'comparer' : compareNumericObjects}], 'properties')
}
function compareAttachments(a1, a2) {
return compareObjects(a1, a2, [{'propertyName' : 'position', 'comparer' : compareNumericObjects},
{'propertyName' : 'orientation'},
{'propertyName' : 'parentJointIndex'},
{'propertyName' : 'modelurl'}])
}
function findAvatarIndexByValue(avatar) {
var index = -1;
// 2DO: find better way of determining selected avatar in bookmarks
for(var i = 0; i < allAvatars.count; ++i) {
var thesame = true;
var bookmarkedAvatar = allAvatars.get(i);
if(bookmarkedAvatar.avatarUrl !== avatar.avatarUrl)
continue;
if(bookmarkedAvatar.avatarScale !== avatar.avatarScale)
continue;
if(!modelsAreEqual(bookmarkedAvatar.attachments, avatar.attachments, compareAttachments)) {
continue;
}
if(!modelsAreEqual(bookmarkedAvatar.wearables, avatar.wearables, compareWearables)) {
continue;
}
if(thesame) {
index = i;
break;
}
}
return index;
}
function findAvatarIndex(avatarName) {
for(var i = 0; i < count; ++i) {
if(get(i).name === avatarName) {
return i;
}
}
return -1;
}
function findAvatar(avatarName) {
var avatarIndex = findAvatarIndex(avatarName);
if(avatarIndex === -1)
return undefined;
return get(avatarIndex);
}
}

View file

@ -0,0 +1,15 @@
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
HifiControlsUit.Button {
HifiConstants {
id: hifi
}
width: Math.max(hifi.dimensions.buttonWidth, implicitTextWidth + 20)
fontSize: 18
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
height: 40
}

View file

@ -0,0 +1,184 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
Rectangle {
id: root;
visible: false;
anchors.fill: parent;
color: Qt.rgba(0, 0, 0, 0.5);
z: 999;
property string titleText: 'Create Favorite'
property string favoriteNameText: favoriteName.text
property string avatarImageUrl: null
property int wearablesCount: 0
property string button1color: hifi.buttons.noneBorderlessGray;
property string button1text: 'CANCEL'
property string button2color: hifi.buttons.blue;
property string button2text: 'CONFIRM'
property var avatars;
property var onSaveClicked;
property var onCancelClicked;
function open(wearables, thumbnail) {
favoriteName.text = '';
favoriteName.forceActiveFocus();
avatarImageUrl = thumbnail;
wearablesCount = wearables;
visible = true;
}
function close() {
console.debug('closing');
visible = false;
}
HifiConstants {
id: hifi
}
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
hoverEnabled: true;
}
Rectangle {
id: mainContainer;
width: Math.max(parent.width * 0.8, 400)
property int margin: 30;
height: childrenRect.height + margin * 2
onHeightChanged: {
console.debug('mainContainer: height = ', height)
}
anchors.centerIn: parent
color: "white"
// TextStyle1
RalewaySemiBold {
id: title
size: 24;
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 30
anchors.leftMargin: 30
anchors.rightMargin: 30
text: root.titleText
}
Item {
id: contentContainer
width: parent.width - 50
height: childrenRect.height
anchors.top: title.bottom
anchors.topMargin: 20
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.rightMargin: 30;
AvatarThumbnail {
id: avatarThumbnail
avatarUrl: avatarImageUrl
onAvatarUrlChanged: {
console.debug('CreateFavoritesDialog: onAvatarUrlChanged: ', avatarUrl);
}
wearablesCount: avatarWearablesCount
}
InputTextStyle4 {
id: favoriteName
anchors.right: parent.right
height: 40
anchors.left: avatarThumbnail.right
anchors.leftMargin: 44
anchors.verticalCenter: avatarThumbnail.verticalCenter
placeholderText: "Enter Favorite Name"
RalewayRegular {
id: wrongName
anchors.top: parent.bottom;
anchors.topMargin: 2
anchors.left: parent.left
anchors.right: parent.right;
anchors.rightMargin: -contentContainer.anchors.rightMargin // allow text to render beyond favorite input text
wrapMode: Text.WordWrap
text: 'Favorite name exists. Overwrite existing favorite?'
size: 15
color: 'red'
visible: {
for(var i = 0; i < avatars.count; ++i) {
var avatarName = avatars.get(i).name;
if(avatarName === favoriteName.text) {
return true;
}
}
return false;
}
}
}
}
DialogButtons {
anchors.top: contentContainer.bottom
anchors.topMargin: 20
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 30
yesButton.enabled: favoriteNameText !== ''
yesText: root.button2text
noText: root.button1text
Binding on yesButton.text {
when: wrongName.visible
value: "OVERWRITE";
}
Binding on yesButton.color {
when: wrongName.visible
value: hifi.buttons.red;
}
Binding on yesButton.colorScheme {
when: wrongName.visible
value: hifi.colorSchemes.dark;
}
onYesClicked: function() {
if(onSaveClicked) {
onSaveClicked();
} else {
root.close();
}
}
onNoClicked: function() {
if(onCancelClicked) {
onCancelClicked();
} else {
root.close();
}
}
}
}
}

View file

@ -0,0 +1,41 @@
import QtQuick 2.9
Row {
id: root
property string yesText;
property string noText;
property var onYesClicked;
property var onNoClicked;
property alias yesButton: yesButton
property alias noButton: noButton
height: childrenRect.height
layoutDirection: Qt.RightToLeft
spacing: 30
BlueButton {
id: yesButton;
text: yesText;
onClicked: {
console.debug('bluebutton.clicked', onYesClicked);
if(onYesClicked) {
onYesClicked();
}
}
}
WhiteButton {
id: noButton
text: noText;
onClicked: {
console.debug('whitebutton.clicked', onNoClicked);
if(onNoClicked) {
onNoClicked();
}
}
}
}

View file

@ -0,0 +1,48 @@
import QtQuick 2.5
import QtQuick.Controls 2.2
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
TextField {
id: textField
property bool error: false;
text: 'ThisIsDisplayName'
states: [
State {
name: "hovered"
when: textField.hovered && !textField.focus && !textField.error;
PropertyChanges { target: background; color: '#afafaf' }
},
State {
name: "focused"
when: textField.focus && !textField.error
PropertyChanges { target: background; color: '#f2f2f2' }
PropertyChanges { target: background; border.color: '#00b4ef' }
},
State {
name: "error"
when: textField.error
PropertyChanges { target: background; color: '#f2f2f2' }
PropertyChanges { target: background; border.color: '#e84e62' }
}
]
background: Rectangle {
id: background
implicitWidth: 200
implicitHeight: 40
color: '#d4d4d4'
border.color: '#afafaf'
border.width: 1
radius: 2
}
HiFiGlyphs {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
size: 36
text: "\ue00d"
}
}

View file

@ -0,0 +1,16 @@
import "../../controls-uit" as HifiControlsUit
import "../../styles-uit"
import QtQuick 2.0
import QtQuick.Controls 2.2
HifiControlsUit.TextField {
id: control
font.family: "Fira Sans"
font.pixelSize: 15;
implicitHeight: 40
AvatarAppStyle {
id: style
}
}

View file

@ -0,0 +1,184 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
Rectangle {
id: root;
visible: false;
anchors.fill: parent;
color: Qt.rgba(0, 0, 0, 0.5);
z: 999;
property string titleText: ''
property string bodyText: ''
property alias inputText: input;
property string imageSource: null
onImageSourceChanged: {
console.debug('imageSource = ', imageSource)
}
property string button1color: hifi.buttons.noneBorderlessGray;
property string button1text: ''
property string button2color: hifi.buttons.blue;
property string button2text: ''
property var onButton2Clicked;
property var onButton1Clicked;
property var onLinkClicked;
function open() {
visible = true;
}
function close() {
visible = false;
onButton1Clicked = null;
onButton2Clicked = null;
button1text = '';
button2text = '';
imageSource = null;
inputText.visible = false;
inputText.placeholderText = '';
inputText.text = '';
}
HifiConstants {
id: hifi
}
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
hoverEnabled: true;
}
Rectangle {
id: mainContainer;
width: Math.max(parent.width * 0.8, 400)
property int margin: 30;
height: childrenRect.height + margin * 2
onHeightChanged: {
console.debug('mainContainer: height = ', height)
}
anchors.centerIn: parent
color: "white"
// TextStyle1
RalewaySemiBold {
id: title
size: 24;
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 30
anchors.leftMargin: 30
anchors.rightMargin: 30
text: root.titleText
elide: Qt.ElideRight
}
Column {
id: contentContainer
spacing: 15
anchors.top: title.bottom
anchors.topMargin: 10
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.rightMargin: 30;
InputTextStyle4 {
id: input
visible: false
height: visible ? implicitHeight : 0
anchors.left: parent.left;
anchors.right: parent.right;
}
// TextStyle3
RalewayRegular {
id: body
AvatarAppStyle {
id: style
}
size: 18
text: root.bodyText;
linkColor: style.colors.blueHighlight
anchors.left: parent.left;
anchors.right: parent.right;
height: paintedHeight;
verticalAlignment: Text.AlignTop;
wrapMode: Text.WordWrap;
onLinkActivated: {
if(onLinkClicked)
onLinkClicked(link);
}
}
Image {
id: image
Binding on height {
when: imageSource === null
value: 0
}
anchors.left: parent.left;
anchors.right: parent.right;
Binding on source {
when: imageSource !== null
value: imageSource
}
visible: imageSource !== null ? true : false
}
}
DialogButtons {
id: buttons
anchors.top: contentContainer.bottom
anchors.topMargin: 30
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 30
yesButton.enabled: !input.visible || input.text.length !== 0
yesText: root.button2text
noText: root.button1text
onYesClicked: function() {
if(onButton2Clicked) {
onButton2Clicked();
} else {
root.close();
}
}
onNoClicked: function() {
if(onButton1Clicked) {
onButton1Clicked();
} else {
root.close();
}
}
}
}
}

View file

@ -0,0 +1,122 @@
import QtQuick 2.5
MessageBox {
id: popup
function showSpecifyAvatarUrl(callback, linkCallback) {
popup.onButton2Clicked = callback;
popup.titleText = 'Specify Avatar URL'
popup.bodyText = 'This will not overwrite your existing favorite if you are wearing one.<br>' +
'<a href="https://docs.highfidelity.com/create-and-explore/avatars/create-avatars">' +
'Learn to make a custom avatar by opening this link on your desktop.' +
'</a>'
popup.inputText.visible = true;
popup.inputText.placeholderText = 'Enter Avatar Url';
popup.button1text = 'CANCEL';
popup.button2text = 'CONFIRM';
popup.onButton2Clicked = function() {
if(callback)
callback();
popup.close();
}
popup.onLinkClicked = function(link) {
if(linkCallback)
linkCallback(link);
}
popup.open();
popup.inputText.forceActiveFocus();
}
property url getWearablesUrl: '../../../images/avatarapp/AvatarIsland.jpg'
function showGetWearables(callback, linkCallback) {
popup.button2text = 'AvatarIsland'
popup.button1text = 'CANCEL'
popup.titleText = 'Get Wearables'
popup.bodyText = 'Buy wearables from <a href="app://marketplace">Marketplace</a>' + '<br/>' +
'Wear wearables from <a href="app://purchases">My Purchases</a>' + '<br/>' +
'You can visit the domain “AvatarIsland” to get wearables'
popup.imageSource = getWearablesUrl;
popup.onButton2Clicked = function() {
popup.close();
if(callback)
callback();
}
popup.onLinkClicked = function(link) {
popup.close();
if(linkCallback)
linkCallback(link);
}
popup.open();
}
function showDeleteFavorite(favoriteName, callback) {
popup.titleText = 'Delete Favorite: {AvatarName}'.replace('{AvatarName}', favoriteName)
popup.bodyText = 'This will delete your favorite. You will retain access to the wearables and avatar that made up the favorite from My Purchases.'
popup.imageSource = null;
popup.button1text = 'CANCEL'
popup.button2text = 'DELETE'
popup.onButton2Clicked = function() {
popup.close();
if(callback)
callback();
}
popup.open();
}
function showLoadFavorite(favoriteName, callback) {
popup.button2text = 'CONFIRM'
popup.button1text = 'CANCEL'
popup.titleText = 'Load Favorite: {AvatarName}'.replace('{AvatarName}', favoriteName)
popup.bodyText = 'This will switch your current avatar and wearables that you are wearing with a new avatar and wearables.'
popup.imageSource = null;
popup.onButton2Clicked = function() {
popup.close();
if(callback)
callback();
}
popup.open();
}
property url getAvatarsUrl: '../../../images/avatarapp/BodyMart.PNG'
function showBuyAvatars(callback, linkCallback) {
popup.button2text = 'BodyMart'
popup.button1text = 'CANCEL'
popup.titleText = 'Get Avatars'
popup.bodyText = 'Buy avatars from <a href="app://marketplace">Marketplace</a>' + '<br/>' +
'Wear avatars from <a href="app://purchases">My Purchases</a>' + '<br/>' +
'You can visit the domain “BodyMart” to get avatars'
popup.imageSource = getAvatarsUrl;
popup.onButton2Clicked = function() {
popup.close();
if(callback)
callback();
}
popup.onLinkClicked = function(link) {
popup.close();
if(linkCallback)
linkCallback(link);
}
popup.open();
}
}

View file

@ -0,0 +1,44 @@
import QtQuick 2.9
ShadowGlyph {
id: indicator
property bool isPrevious: true;
property bool hasNext: false
property bool hasPrev: false
property bool isEnabled: isPrevious ? hasPrev : hasNext
signal clicked;
states: [
State {
name: "hovered"
when: pageIndicatorMouseArea.containsMouse;
PropertyChanges { target: pageIndicatorMouseArea; anchors.bottomMargin: -5 }
PropertyChanges { target: indicator; y: -5 }
PropertyChanges { target: indicator; dropShadowVerticalOffset: 9 }
}
]
Behavior on y {
NumberAnimation {
duration: 100
}
}
text: isPrevious ? "E" : "D";
width: 40
height: 40
font.pixelSize: 100
color: isEnabled ? 'black' : 'gray'
visible: hasNext || hasPrev
horizontalAlignment: Text.AlignHCenter
MouseArea {
id: pageIndicatorMouseArea
anchors.fill: parent
enabled: isEnabled
hoverEnabled: enabled
onClicked: {
parent.clicked();
}
}
}

View file

@ -0,0 +1,36 @@
import QtQuick 2.0
Item {
property alias border: borderRectangle.border
property alias source: image.source
property alias fillMode: image.fillMode
property alias radius: mask.radius
property alias status: image.status
property alias progress: image.progress
Image {
id: image
anchors.fill: parent
anchors.margins: borderRectangle.border.width
}
Rectangle {
id: mask
anchors.fill: image
}
TransparencyMask {
anchors.fill: image
source: image
maskSource: mask
}
Rectangle {
id: borderRectangle
anchors.fill: parent
radius: mask.radius
color: "transparent"
}
}

View file

@ -0,0 +1,353 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
Rectangle {
id: root
color: 'white'
visible: false;
signal scaleChanged(real scale);
property alias onSaveClicked: dialogButtons.onYesClicked
property alias onCancelClicked: dialogButtons.onNoClicked
property real scaleValue: scaleSlider.value / 10
property alias dominantHandIsLeft: leftHandRadioButton.checked
property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked
property alias avatarAnimationJSON: avatarAnimationUrlInputText.text
property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text
property real avatarScaleBackup;
function open(settings, avatarScale) {
console.debug('Settings.qml: open: ', JSON.stringify(settings, 0, 4));
avatarScaleBackup = avatarScale;
scaleSlider.notify = false;
scaleSlider.value = Math.round(avatarScale * 10);
scaleSlider.notify = true;;
if(settings.dominantHand === 'left') {
leftHandRadioButton.checked = true;
} else {
rightHandRadioButton.checked = true;
}
if(settings.collisionsEnabled) {
collisionsEnabledRadiobutton.checked = true;
} else {
collisionsDisabledRadioButton.checked = true;
}
avatarAnimationJSON = settings.animGraphUrl;
avatarCollisionSoundUrl = settings.collisionSoundUrl;
visible = true;
}
function close() {
visible = false
}
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
hoverEnabled: true;
}
Item {
anchors.left: parent.left
anchors.leftMargin: 27
anchors.top: parent.top
anchors.topMargin: 25
anchors.right: parent.right
anchors.rightMargin: 32
anchors.bottom: parent.bottom
anchors.bottomMargin: 57
RowLayout {
id: avatarScaleRow
anchors.left: parent.left
anchors.right: parent.right
spacing: 17
// TextStyle9
RalewaySemiBold {
size: 17;
text: "Avatar Scale"
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
}
RowLayout {
anchors.verticalCenter: parent.verticalCenter
Layout.fillWidth: true
spacing: 0
HiFiGlyphs {
size: 30
text: 'T'
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.verticalCenter: parent.verticalCenter
}
HifiControlsUit.Slider {
id: scaleSlider
property bool notify: false;
from: 1
to: 40
onValueChanged: {
console.debug('value changed: ', value);
if(notify) {
console.debug('notifying.. ');
root.scaleChanged(value / 10);
}
}
anchors.verticalCenter: parent.verticalCenter
Layout.fillWidth: true
// TextStyle9
RalewaySemiBold {
size: 17;
anchors.left: scaleSlider.left
anchors.leftMargin: 5
anchors.top: scaleSlider.bottom
anchors.topMargin: 2
text: String(scaleSlider.from / 10) + 'x'
}
// TextStyle9
RalewaySemiBold {
size: 17;
anchors.right: scaleSlider.right
anchors.rightMargin: 5
anchors.top: scaleSlider.bottom
anchors.topMargin: 2
text: String(scaleSlider.to / 10) + 'x'
}
}
HiFiGlyphs {
size: 40
text: 'T'
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.verticalCenter: parent.verticalCenter
}
}
ShadowRectangle {
width: 37
height: 28
AvatarAppStyle {
id: style
}
gradient: Gradient {
GradientStop { position: 0.0; color: style.colors.blueHighlight }
GradientStop { position: 1.0; color: style.colors.blueAccent }
}
radius: 3
RalewaySemiBold {
color: 'white'
anchors.centerIn: parent
text: "1x"
size: 18
}
MouseArea {
anchors.fill: parent
onClicked: {
scaleSlider.value = 10
}
}
}
}
GridLayout {
id: handAndCollisions
anchors.top: avatarScaleRow.bottom
anchors.topMargin: 39
anchors.left: parent.left
anchors.right: parent.right
rows: 2
rowSpacing: 25
columns: 3
// TextStyle9
RalewaySemiBold {
size: 17;
Layout.row: 0
Layout.column: 0
text: "Dominant Hand"
}
ButtonGroup {
id: leftRight
}
HifiControlsUit.RadioButton {
id: leftHandRadioButton
Layout.row: 0
Layout.column: 1
Layout.leftMargin: -40
ButtonGroup.group: leftRight
checked: true
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
text: "Left"
boxSize: 20
}
HifiControlsUit.RadioButton {
id: rightHandRadioButton
Layout.row: 0
Layout.column: 2
Layout.rightMargin: 20
ButtonGroup.group: leftRight
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
text: "Right"
boxSize: 20
}
// TextStyle9
RalewaySemiBold {
size: 17;
Layout.row: 1
Layout.column: 0
text: "Avatar Collisions"
}
ButtonGroup {
id: onOff
}
HifiControlsUit.RadioButton {
id: collisionsEnabledRadiobutton
Layout.row: 1
Layout.column: 1
Layout.leftMargin: -40
ButtonGroup.group: onOff
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
checked: true
text: "ON"
boxSize: 20
}
HifiConstants {
id: hifi
}
HifiControlsUit.RadioButton {
id: collisionsDisabledRadioButton
Layout.row: 1
Layout.column: 2
Layout.rightMargin: 20
ButtonGroup.group: onOff
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
text: "OFF"
boxSize: 20
}
}
ColumnLayout {
id: avatarAnimationLayout
anchors.top: handAndCollisions.bottom
anchors.topMargin: 25
anchors.left: parent.left
anchors.right: parent.right
spacing: 4
// TextStyle9
RalewaySemiBold {
size: 17;
text: "Avatar Animation JSON"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
}
InputTextStyle4 {
id: avatarAnimationUrlInputText
font.pixelSize: 17
anchors.left: parent.left
anchors.right: parent.right
placeholderText: 'user\\file\\dir'
}
}
ColumnLayout {
id: avatarCollisionLayout
anchors.top: avatarAnimationLayout.bottom
anchors.topMargin: 25
anchors.left: parent.left
anchors.right: parent.right
spacing: 4
// TextStyle9
RalewaySemiBold {
size: 17;
text: "Avatar collision sound URL (optional)"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
}
InputTextStyle4 {
id: avatarCollisionSoundUrlInputText
font.pixelSize: 17
anchors.left: parent.left
anchors.right: parent.right
placeholderText: 'https://hifi-public.s3.amazonaws.com/sounds/Collisions-'
}
}
DialogButtons {
id: dialogButtons
anchors.right: parent.right
anchors.bottom: parent.bottom
yesText: "SAVE"
noText: "CANCEL"
}
}
}

View file

@ -0,0 +1,29 @@
import "../../styles-uit"
import QtQuick 2.9
import QtGraphicalEffects 1.0
Item {
property alias text: glyph.text
property alias font: glyph.font
property alias color: glyph.color
property alias horizontalAlignment: glyph.horizontalAlignment
property alias dropShadowRadius: shadow.radius
property alias dropShadowHorizontalOffset: shadow.horizontalOffset
property alias dropShadowVerticalOffset: shadow.verticalOffset
HiFiGlyphs {
id: glyph
width: parent.width
height: parent.height
}
DropShadow {
id: shadow
anchors.fill: glyph
radius: 4
horizontalOffset: 0
verticalOffset: 4
color: Qt.rgba(0, 0, 0, 0.25)
source: glyph
}
}

View file

@ -0,0 +1,32 @@
import "../../styles-uit"
import QtQuick 2.9
import QtGraphicalEffects 1.0
Item {
property alias source: image.source
property alias dropShadowRadius: shadow.radius
property alias dropShadowHorizontalOffset: shadow.horizontalOffset
property alias dropShadowVerticalOffset: shadow.verticalOffset
property alias radius: image.radius
property alias border: image.border
property alias status: image.status
property alias progress: image.progress
property alias fillMode: image.fillMode
RoundImage {
id: image
width: parent.width
height: parent.height
radius: 6
}
DropShadow {
id: shadow
anchors.fill: image
radius: 6
horizontalOffset: 0
verticalOffset: 3
color: Qt.rgba(0, 0, 0, 0.25)
source: image
}
}

View file

@ -0,0 +1,30 @@
import "../../styles-uit"
import QtQuick 2.9
import QtGraphicalEffects 1.0
Item {
property alias color: rectangle.color
property alias gradient: rectangle.gradient
property alias border: rectangle.border
property alias radius: rectangle.radius
property alias dropShadowRadius: shadow.radius
property alias dropShadowHorizontalOffset: shadow.horizontalOffset
property alias dropShadowVerticalOffset: shadow.verticalOffset
property alias dropShadowOpacity: shadow.opacity
Rectangle {
id: rectangle
width: parent.width
height: parent.height
}
DropShadow {
id: shadow
anchors.fill: rectangle
radius: 6
horizontalOffset: 0
verticalOffset: 3
color: Qt.rgba(0, 0, 0, 0.25)
source: rectangle
}
}

View file

@ -0,0 +1,7 @@
import QtQuick 2.5
import QtWebEngine 1.5
AnimatedImage {
source: "../../../icons/loader-snake-64-w.gif"
playing: visible
}

View file

@ -0,0 +1,29 @@
import "../../styles-uit"
import QtQuick 2.9
import QtGraphicalEffects 1.0
ShadowRectangle {
width: 44
height: 28
AvatarAppStyle {
id: style
}
gradient: Gradient {
GradientStop { position: 0.0; color: style.colors.blueHighlight }
GradientStop { position: 1.0; color: style.colors.blueAccent }
}
property alias glyphText: glyph.text
property alias glyphRotation: glyph.rotation
property alias glyphSize: glyph.size
radius: 3
HiFiGlyphs {
id: glyph
color: 'white'
anchors.centerIn: parent
size: 30
}
}

View file

@ -0,0 +1,43 @@
import QtQuick 2.0
Item {
property alias source: sourceImage.sourceItem
property alias maskSource: sourceMask.sourceItem
anchors.fill: parent
ShaderEffectSource {
id: sourceMask
smooth: true
hideSource: true
}
ShaderEffectSource {
id: sourceImage
hideSource: true
}
ShaderEffect {
id: maskEffect
anchors.fill: parent
property variant source: sourceImage
property variant mask: sourceMask
fragmentShader: {
"
varying highp vec2 qt_TexCoord0;
uniform lowp sampler2D source;
uniform lowp sampler2D mask;
void main() {
highp vec4 maskColor = texture2D(mask, vec2(qt_TexCoord0.x, qt_TexCoord0.y));
highp vec4 sourceColor = texture2D(source, vec2(qt_TexCoord0.x, qt_TexCoord0.y));
if(maskColor.a > 0.0)
gl_FragColor = sourceColor;
else
gl_FragColor = maskColor;
}
"
}
}
}

View file

@ -0,0 +1,65 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
Row {
id: root
width: parent.width
height: xspinner.controlHeight
property int spinboxSpace: 10
property int spinboxWidth: (parent.width - 2 * spinboxSpace) / 3
property color backgroundColor: "darkgray"
property int decimals: 4
property real realFrom: 0
property real realTo: 100
property real realStepSize: 0.0001
spacing: spinboxSpace
property alias xvalue: xspinner.realValue
property alias yvalue: yspinner.realValue
property alias zvalue: zspinner.realValue
HifiControlsUit.SpinBox {
id: xspinner
width: parent.spinboxWidth
labelInside: "X:"
backgroundColor: parent.backgroundColor
colorLabelInside: hifi.colors.redHighlight
colorScheme: hifi.colorSchemes.light
decimals: root.decimals;
realFrom: root.realFrom
realTo: root.realTo
realStepSize: root.realStepSize
}
HifiControlsUit.SpinBox {
id: yspinner
width: parent.spinboxWidth
labelInside: "Y:"
backgroundColor: parent.backgroundColor
colorLabelInside: hifi.colors.greenHighlight
colorScheme: hifi.colorSchemes.light
decimals: root.decimals;
realFrom: root.realFrom
realTo: root.realTo
realStepSize: root.realStepSize
}
HifiControlsUit.SpinBox {
id: zspinner
width: parent.spinboxWidth
labelInside: "Z:"
backgroundColor: parent.backgroundColor
colorLabelInside: hifi.colors.primaryHighlight
colorScheme: hifi.colorSchemes.light
decimals: root.decimals;
realFrom: root.realFrom
realTo: root.realTo
realStepSize: root.realStepSize
}
}

View file

@ -0,0 +1,15 @@
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
HifiControlsUit.Button {
HifiConstants {
id: hifi
}
width: Math.max(hifi.dimensions.buttonWidth, implicitTextWidth + 20)
fontSize: 18
color: hifi.buttons.noneBorderlessGray;
colorScheme: hifi.colorSchemes.light;
height: 40
}

View file

@ -51,6 +51,8 @@ ListModel {
if (!delayedClear) { root.clear(); }
currentPageToRetrieve = 1;
retrievedAtLeastOnePage = false;
totalPages = 0;
totalEntries = 0;
}
// Page processing.
@ -59,6 +61,8 @@ ListModel {
property var processPage: function (data) { return data; }
property var listView; // Optional. For debugging.
property int totalPages: 0;
property int totalEntries: 0;
// Check consistency and call processPage.
function handlePage(error, response) {
var processed;
@ -79,8 +83,10 @@ ListModel {
if (response.current_page && response.current_page !== currentPageToRetrieve) { // Not all endpoints specify this property.
return fail("Mismatched page, expected:" + currentPageToRetrieve);
}
totalPages = response.total_pages || 0;
totalEntries = response.total_entries || 0;
processed = processPage(response.data || response);
if (response.total_pages && (response.total_pages === currentPageToRetrieve)) {
if (totalPages && (totalPages === currentPageToRetrieve)) {
currentPageToRetrieve = -1;
}

View file

@ -300,6 +300,12 @@ Item {
id: controllerPrefereneces
objectName: "TabletControllerPreferences"
showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"]
categoryProperties: {
"VR Movement" : {
"User real-world height (meters)" : { "anchors.right" : "undefined" },
"RESET SENSORS" : { "width" : "180", "anchors.left" : "undefined" }
}
}
}
}
}

View file

@ -0,0 +1,15 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtWebChannel 1.0
import "../../controls"
import "../toolbars"
import QtGraphicalEffects 1.0
import "../../controls-uit" as HifiControls
import "../../styles-uit"
WebView {
id: entityListToolWebView
url: Paths.defaultScripts + "/system/html/entityList.html"
enabled: true
}

View file

@ -9,7 +9,6 @@ import "../../styles-uit"
TabBar {
id: editTabView
// anchors.fill: parent
width: parent.width
contentWidth: parent.width
padding: 0
@ -34,7 +33,7 @@ TabBar {
width: parent.width
clip: true
contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height +
contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height +
header.anchors.topMargin + createEntitiesFlow.anchors.topMargin +
assetServerButton.anchors.topMargin + importButton.anchors.topMargin +
header.paintedHeight
@ -77,8 +76,9 @@ TabBar {
text: "MODEL"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newModelButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newModelButton" }
});
editTabView.currentIndex = 2
}
}
@ -88,8 +88,9 @@ TabBar {
text: "CUBE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newCubeButton" }
});
editTabView.currentIndex = 2
}
}
@ -99,8 +100,9 @@ TabBar {
text: "SPHERE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newSphereButton" }
});
editTabView.currentIndex = 2
}
}
@ -110,8 +112,9 @@ TabBar {
text: "LIGHT"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newLightButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newLightButton" }
});
editTabView.currentIndex = 2
}
}
@ -121,8 +124,9 @@ TabBar {
text: "TEXT"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newTextButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newTextButton" }
});
editTabView.currentIndex = 2
}
}
@ -132,8 +136,9 @@ TabBar {
text: "IMAGE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newImageButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newImageButton" }
});
editTabView.currentIndex = 2
}
}
@ -143,8 +148,9 @@ TabBar {
text: "WEB"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newWebButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newWebButton" }
});
editTabView.currentIndex = 2
}
}
@ -154,8 +160,9 @@ TabBar {
text: "ZONE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newZoneButton" }
});
editTabView.currentIndex = 2
}
}
@ -165,8 +172,9 @@ TabBar {
text: "PARTICLE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newParticleButton" }
});
editTabView.currentIndex = 4
}
}
@ -176,8 +184,9 @@ TabBar {
text: "MATERIAL"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newMaterialButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newMaterialButton" }
});
editTabView.currentIndex = 2
}
}
@ -196,8 +205,9 @@ TabBar {
anchors.topMargin: 35
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "openAssetBrowserButton" }
});
}
}
@ -214,8 +224,9 @@ TabBar {
anchors.topMargin: 20
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "importEntitiesButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "importEntitiesButton" }
});
}
}
}

View file

@ -0,0 +1,58 @@
import QtQuick 2.7
import QtQuick.Controls 2.3
// FIXME pretty non-DRY code, should figure out a way to optionally hide one tab from the tab view, keep in sync with Edit.qml
StackView {
id: editRoot
objectName: "stack"
signal sendToScript(var message);
topPadding: 40
leftPadding: 0
rightPadding: 0
bottomPadding: 0
anchors.fill: parent
property var itemProperties: {"y": editRoot.topPadding,
"width": editRoot.availableWidth,
"height": editRoot.availableHeight }
Component.onCompleted: {
tab.currentIndex = 0
}
background: Rectangle {
color: "#404040" //default background color
EditToolsTabView {
id: tab
anchors.fill: parent
currentIndex: -1
onCurrentIndexChanged: {
editRoot.replace(null, tab.itemAt(currentIndex).visualItem,
itemProperties,
StackView.Immediate)
}
}
}
function pushSource(path) {
editRoot.push(Qt.resolvedUrl("../../" + path), itemProperties,
StackView.Immediate);
editRoot.currentItem.sendToScript.connect(editRoot.sendToScript);
}
function popSource() {
editRoot.pop(StackView.Immediate);
}
// Passes script messages to the item on the top of the stack
function fromScript(message) {
var currentItem = editRoot.currentItem;
if (currentItem && currentItem.fromScript) {
currentItem.fromScript(message);
} else if (tab.fromScript) {
tab.fromScript(message);
}
}
}

View file

@ -0,0 +1,328 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtWebChannel 1.0
import "../../controls"
import "../toolbars"
import QtGraphicalEffects 1.0
import "../../controls-uit" as HifiControls
import "../../styles-uit"
TabBar {
id: editTabView
width: parent.width
contentWidth: parent.width
padding: 0
spacing: 0
readonly property QtObject tabIndex: QtObject {
readonly property int create: 0
readonly property int properties: 1
readonly property int grid: 2
readonly property int particle: 3
}
readonly property HifiConstants hifi: HifiConstants {}
EditTabButton {
title: "CREATE"
active: true
enabled: true
property string originalUrl: ""
property Component visualItem: Component {
Rectangle {
color: "#404040"
id: container
Flickable {
height: parent.height
width: parent.width
clip: true
contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height +
header.anchors.topMargin + createEntitiesFlow.anchors.topMargin +
assetServerButton.anchors.topMargin + importButton.anchors.topMargin +
header.paintedHeight
contentWidth: width
ScrollBar.vertical : ScrollBar {
visible: parent.contentHeight > parent.height
width: 20
background: Rectangle {
color: hifi.colors.tableScrollBackgroundDark
}
}
Text {
id: header
color: "#ffffff"
text: "Choose an Entity Type to Create:"
font.pixelSize: 14
font.bold: true
anchors.top: parent.top
anchors.topMargin: 28
anchors.left: parent.left
anchors.leftMargin: 28
}
Flow {
id: createEntitiesFlow
spacing: 35
anchors.right: parent.right
anchors.rightMargin: 55
anchors.left: parent.left
anchors.leftMargin: 55
anchors.top: parent.top
anchors.topMargin: 70
NewEntityButton {
icon: "icons/create-icons/94-model-01.svg"
text: "MODEL"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newModelButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/21-cube-01.svg"
text: "CUBE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newCubeButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/22-sphere-01.svg"
text: "SPHERE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newSphereButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/24-light-01.svg"
text: "LIGHT"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newLightButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/20-text-01.svg"
text: "TEXT"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newTextButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/image.svg"
text: "IMAGE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newImageButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/25-web-1-01.svg"
text: "WEB"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newWebButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/23-zone-01.svg"
text: "ZONE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newZoneButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/90-particles-01.svg"
text: "PARTICLE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newParticleButton" }
});
editTabView.currentIndex = tabIndex.particle
}
}
NewEntityButton {
icon: "icons/create-icons/126-material-01.svg"
text: "MATERIAL"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newMaterialButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
}
HifiControls.Button {
id: assetServerButton
text: "Open This Domain's Asset Server"
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
anchors.right: parent.right
anchors.rightMargin: 55
anchors.left: parent.left
anchors.leftMargin: 55
anchors.top: createEntitiesFlow.bottom
anchors.topMargin: 35
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "openAssetBrowserButton" }
});
}
}
HifiControls.Button {
id: importButton
text: "Import Entities (.json)"
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
anchors.right: parent.right
anchors.rightMargin: 55
anchors.left: parent.left
anchors.leftMargin: 55
anchors.top: assetServerButton.bottom
anchors.topMargin: 20
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "importEntitiesButton" }
});
}
}
}
} // Flickable
}
}
EditTabButton {
title: "PROPERTIES"
active: true
enabled: true
property string originalUrl: ""
property Component visualItem: Component {
WebView {
id: entityPropertiesWebView
url: Paths.defaultScripts + "/system/html/entityProperties.html"
enabled: true
}
}
}
EditTabButton {
title: "GRID"
active: true
enabled: true
property string originalUrl: ""
property Component visualItem: Component {
WebView {
id: gridControlsWebView
url: Paths.defaultScripts + "/system/html/gridControls.html"
enabled: true
}
}
}
EditTabButton {
title: "P"
active: true
enabled: true
property string originalUrl: ""
property Component visualItem: Component {
WebView {
id: particleExplorerWebView
url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html"
enabled: true
}
}
}
function fromScript(message) {
switch (message.method) {
case 'selectTab':
selectTab(message.params.id);
break;
default:
console.warn('Unrecognized message:', JSON.stringify(message));
}
}
// Changes the current tab based on tab index or title as input
function selectTab(id) {
if (typeof id === 'number') {
if (id >= tabIndex.create && id <= tabIndex.particle) {
editTabView.currentIndex = id;
} else {
console.warn('Attempt to switch to invalid tab:', id);
}
} else if (typeof id === 'string'){
switch (id.toLowerCase()) {
case 'create':
editTabView.currentIndex = tabIndex.create;
break;
case 'properties':
editTabView.currentIndex = tabIndex.properties;
break;
case 'grid':
editTabView.currentIndex = tabIndex.grid;
break;
case 'particle':
editTabView.currentIndex = tabIndex.particle;
break;
default:
console.warn('Attempt to switch to invalid tab:', id);
}
} else {
console.warn('Attempt to switch tabs with invalid input:', JSON.stringify(id));
}
}
}

View file

@ -0,0 +1,5 @@
WebView {
id: entityListToolWebView
url: Paths.defaultScripts + "/system/html/entityList.html"
enabled: true
}

View file

@ -29,12 +29,16 @@ Rectangle {
property bool keyboardRasied: false
function errorMessageBox(message) {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
try {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
} catch(e) {
Window.alert(message);
}
}
Item {

View file

@ -0,0 +1,20 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
StackView {
id: stackView
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.topMargin: 40
signal sendToScript(var message);
NewMaterialDialog {
id: dialog
anchors.fill: parent
Component.onCompleted:{
dialog.sendToScript.connect(stackView.sendToScript);
}
}
}

View file

@ -29,12 +29,16 @@ Rectangle {
property bool keyboardRasied: false
function errorMessageBox(message) {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
try {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
} catch(e) {
Window.alert(message);
}
}
Item {

View file

@ -0,0 +1,20 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
StackView {
id: stackView
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.topMargin: 40
signal sendToScript(var message);
NewModelDialog {
id: dialog
anchors.fill: parent
Component.onCompleted:{
dialog.sendToScript.connect(stackView.sendToScript);
}
}
}

View file

@ -24,6 +24,7 @@ Item {
HifiConstants { id: hifi }
property var sections: []
property var showCategories: []
property var categoryProperties: ({})
property bool keyboardEnabled: false
property bool keyboardRaised: false
@ -100,7 +101,8 @@ Item {
// NOTE: the sort order of items in the showCategories array is the same order in the dialog.
for (i = 0; i < showCategories.length; i++) {
if (categoryMap[showCategories[i]]) {
sections.push(sectionBuilder.createObject(prefControls, {name: showCategories[i]}));
var properties = categoryProperties.hasOwnProperty(showCategories[i]) ? categoryProperties[showCategories[i]] : {};
sections.push(sectionBuilder.createObject(prefControls, {name: showCategories[i], sectionProperties: properties}));
}
}

View file

@ -24,6 +24,7 @@ Preference {
property bool isLast: false
property string name: "Header"
property real spacing: 8
property var sectionProperties: ({})
default property alias preferences: contentContainer.children
HifiConstants { id: hifi }
@ -163,6 +164,28 @@ Preference {
if (builder) {
preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) , z: zpos}));
var preferenceObject = preferences[preferences.length - 1];
var props = sectionProperties.hasOwnProperty(preference.name) ? sectionProperties[preference.name] : {};
for(var prop in props) {
var value = props[prop];
if(value.indexOf('.') !== -1) {
var splittedValues = value.split('.');
if(splittedValues[0] === 'parent') {
value = preferenceObject.parent[splittedValues[1]];
}
} else if(value === 'undefined') {
value = undefined;
}
if(prop.indexOf('.') !== -1) {
var splittedProps = prop.split('.');
preferenceObject[splittedProps[0]][splittedProps[1]] = value;
} else {
preferenceObject[prop] = value;
}
}
}
}
}

View file

@ -7640,7 +7640,6 @@ void Application::toggleEntityScriptServerLogDialog() {
void Application::loadAddAvatarBookmarkDialog() const {
auto avatarBookmarks = DependencyManager::get<AvatarBookmarks>();
avatarBookmarks->addBookmark();
}
void Application::loadAvatarBrowser() const {

View file

@ -17,7 +17,9 @@
#include <QStandardPaths>
#include <QQmlContext>
#include <QList>
#include <QtCore/QThread>
#include <shared/QtHelpers.h>
#include <Application.h>
#include <OffscreenUi.h>
#include <avatar/AvatarManager.h>
@ -28,7 +30,6 @@
#include <VariantMapToScriptValue.h>
#include "MainWindow.h"
#include "Menu.h"
#include "InterfaceLogging.h"
#include "QVariantGLM.h"
@ -92,10 +93,96 @@ void addAvatarEntities(const QVariantList& avatarEntities) {
}
AvatarBookmarks::AvatarBookmarks() {
QDir directory(PathUtils::getAppDataPath());
if (!directory.exists()) {
directory.mkpath(".");
}
_bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME;
if(!QFile::exists(_bookmarksFilename)) {
auto defaultBookmarksFilename = PathUtils::resourcesPath() + QString("avatar/bookmarks") + "/" + AVATARBOOKMARKS_FILENAME;
if (!QFile::exists(defaultBookmarksFilename)) {
qDebug() << defaultBookmarksFilename << "doesn't exist!";
}
if (!QFile::copy(defaultBookmarksFilename, _bookmarksFilename)) {
qDebug() << "failed to copy" << defaultBookmarksFilename << "to" << _bookmarksFilename;
}
}
readFromFile();
}
void AvatarBookmarks::addBookmark(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "addBookmark", Q_ARG(QString, bookmarkName));
return;
}
QVariantMap bookmark = getAvatarDataToBookmark();
insert(bookmarkName, bookmark);
emit bookmarkAdded(bookmarkName);
}
void AvatarBookmarks::saveBookmark(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "saveBookmark", Q_ARG(QString, bookmarkName));
return;
}
if (contains(bookmarkName)) {
QVariantMap bookmark = getAvatarDataToBookmark();
insert(bookmarkName, bookmark);
}
}
void AvatarBookmarks::removeBookmark(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "removeBookmark", Q_ARG(QString, bookmarkName));
return;
}
remove(bookmarkName);
emit bookmarkDeleted(bookmarkName);
}
void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
myAvatar->removeAvatarEntities();
addAvatarEntities(avatarEntities);
}
void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "loadBookmark", Q_ARG(QString, bookmarkName));
return;
}
auto bookmarkEntry = _bookmarks.find(bookmarkName);
if (bookmarkEntry != _bookmarks.end()) {
QVariantMap bookmark = bookmarkEntry.value().toMap();
if (!bookmark.empty()) {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
myAvatar->removeAvatarEntities();
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
myAvatar->useFullAvatarURL(avatarUrl);
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
const QList<QVariant>& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList<QVariant>()).toList();
qCDebug(interfaceapp) << "Attach " << attachments;
myAvatar->setAttachmentsVariant(attachments);
const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
myAvatar->setAvatarScale(qScale);
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
addAvatarEntities(avatarEntities);
emit bookmarkLoaded(bookmarkName);
}
}
}
void AvatarBookmarks::readFromFile() {
// migrate old avatarbookmarks.json, used to be in 'local' folder on windows
QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
@ -115,99 +202,37 @@ void AvatarBookmarks::readFromFile() {
Bookmarks::readFromFile();
}
void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
// Add menus/actions
auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar);
QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection);
_bookmarksMenu = menu->addMenu(MenuOption::AvatarBookmarks);
_deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarBookmark);
QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection);
for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) {
addBookmarkToMenu(menubar, it.key(), it.value());
QVariantMap AvatarBookmarks::getBookmark(const QString &bookmarkName)
{
if (QThread::currentThread() != thread()) {
QVariantMap result;
BLOCKING_INVOKE_METHOD(this, "getBookmark", Q_RETURN_ARG(QVariantMap, result), Q_ARG(QString, bookmarkName));
return result;
}
Bookmarks::sortActions(menubar, _bookmarksMenu);
QVariantMap bookmark;
auto bookmarkEntry = _bookmarks.find(bookmarkName);
if (bookmarkEntry != _bookmarks.end()) {
bookmark = bookmarkEntry.value().toMap();
}
return bookmark;
}
void AvatarBookmarks::changeToBookmarkedAvatar() {
QAction* action = qobject_cast<QAction*>(sender());
QVariantMap AvatarBookmarks::getAvatarDataToBookmark() {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
const QVariant& avatarScale = myAvatar->getAvatarScale();
if (action->data().type() == QVariant::String) {
// TODO: Phase this out eventually.
// Legacy avatar bookmark.
myAvatar->useFullAvatarURL(action->data().toString());
qCDebug(interfaceapp) << " Using Legacy V1 Avatar Bookmark ";
} else {
const QMap<QString, QVariant> bookmark = action->data().toMap();
// Not magic value. This is the current made version, and if it changes this interpreter should be updated to
// handle the new one separately.
// This is where the avatar bookmark entry is parsed. If adding new Value, make sure to have backward compatability with previous
if (bookmark.value(ENTRY_VERSION) == 3) {
myAvatar->removeAvatarEntities();
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
myAvatar->useFullAvatarURL(avatarUrl);
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
const QList<QVariant>& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList<QVariant>()).toList();
qCDebug(interfaceapp) << "Attach " << attachments;
myAvatar->setAttachmentsVariant(attachments);
const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
myAvatar->setAvatarScale(qScale);
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
addAvatarEntities(avatarEntities);
} else {
qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarBookmark";
}
}
}
void AvatarBookmarks::addBookmark() {
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar", "Name", QString());
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
auto bookmarkName = response.toString();
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
if (bookmarkName.length() == 0) {
return;
}
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
const QVariant& avatarScale = myAvatar->getAvatarScale();
// If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION
QVariantMap bookmark;
bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
Bookmarks::addBookmarkToFile(bookmarkName, bookmark);
});
}
void AvatarBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) {
QAction* changeAction = _bookmarksMenu->newAction();
changeAction->setData(bookmark);
connect(changeAction, SIGNAL(triggered()), this, SLOT(changeToBookmarkedAvatar()));
if (!_isMenuSorted) {
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
} else {
// TODO: this is aggressive but other alternatives have proved less fruitful so far.
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
Bookmarks::sortActions(menubar, _bookmarksMenu);
}
// If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION
QVariantMap bookmark;
bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
return bookmark;
}

View file

@ -30,19 +30,50 @@ class AvatarBookmarks: public Bookmarks, public Dependency {
public:
AvatarBookmarks();
void setupMenus(Menu* menubar, MenuWrapper* menu) override;
void setupMenus(Menu* menubar, MenuWrapper* menu) override {};
Q_INVOKABLE QVariantMap getBookmark(const QString& bookmarkName);
public slots:
/**jsdoc
* Add the current Avatar to your avatar bookmarks.
* @function AvatarBookmarks.addBookMark
*/
void addBookmark();
void addBookmark(const QString& bookmarkName);
void saveBookmark(const QString& bookmarkName);
void loadBookmark(const QString& bookmarkName);
void removeBookmark(const QString& bookmarkName);
void updateAvatarEntities(const QVariantList& avatarEntities);
QVariantMap getBookmarks() { return _bookmarks; }
signals:
/**jsdoc
* This function gets triggered after avatar loaded from bookmark
* @function AvatarBookmarks.bookmarkLoaded
* @param {string} bookmarkName
* @returns {Signal}
*/
void bookmarkLoaded(const QString& bookmarkName);
/**jsdoc
* This function gets triggered after avatar bookmark deleted
* @function AvatarBookmarks.bookmarkDeleted
* @param {string} bookmarkName
* @returns {Signal}
*/
void bookmarkDeleted(const QString& bookmarkName);
/**jsdoc
* This function gets triggered after avatar bookmark added
* @function AvatarBookmarks.bookmarkAdded
* @param {string} bookmarkName
* @returns {Signal}
*/
void bookmarkAdded(const QString& bookmarkName);
protected:
void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override;
void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override {};
void readFromFile() override;
QVariantMap getAvatarDataToBookmark();
private:
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";
@ -53,9 +84,6 @@ private:
const QString ENTRY_VERSION = "version";
const int AVATAR_BOOKMARK_VERSION = 3;
private slots:
void changeToBookmarkedAvatar();
};
#endif // hifi_AvatarBookmarks_h

View file

@ -46,6 +46,10 @@ void Bookmarks::deleteBookmark() {
return;
}
deleteBookmark(bookmarkName);
}
void Bookmarks::deleteBookmark(const QString& bookmarkName) {
removeBookmarkFromMenu(Menu::getInstance(), bookmarkName);
remove(bookmarkName);

View file

@ -31,6 +31,8 @@ public:
QString addressForBookmark(const QString& name) const;
protected:
void deleteBookmark(const QString& bookmarkName);
void addBookmarkToFile(const QString& bookmarkName, const QVariant& bookmark);
virtual void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) = 0;
void enableMenuItems(bool enabled);
@ -38,9 +40,10 @@ protected:
void insert(const QString& name, const QVariant& address); // Overwrites any existing entry with same name.
void sortActions(Menu* menubar, MenuWrapper* menu);
int getMenuItemLocation(QList<QAction*> actions, const QString& name) const;
void removeBookmarkFromMenu(Menu* menubar, const QString& name);
bool contains(const QString& name) const;
void remove(const QString& name);
QVariantMap _bookmarks; // { name: url, ... }
QPointer<MenuWrapper> _bookmarksMenu;
QPointer<QAction> _deleteBookmarksAction;
@ -57,12 +60,9 @@ protected slots:
void deleteBookmark();
private:
void remove(const QString& name);
static bool sortOrder(QAction* a, QAction* b);
void persistToFile();
void removeBookmarkFromMenu(Menu* menubar, const QString& name);
};
#endif // hifi_Bookmarks_h

View file

@ -158,46 +158,6 @@ Menu::Menu() {
// Edit > Reload All Content
addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
MenuWrapper* avatarMenu = addMenu("Avatar");
auto avatarManager = DependencyManager::get<AvatarManager>();
auto avatar = avatarManager->getMyAvatar();
// Avatar > Size
MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size");
// Avatar > Size > Increase
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::IncreaseAvatarSize,
0, // QML Qt::Key_Plus,
avatar.get(), SLOT(increaseSize()));
// Avatar > Size > Decrease
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::DecreaseAvatarSize,
0, // QML Qt::Key_Minus,
avatar.get(), SLOT(decreaseSize()));
// Avatar > Size > Reset
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::ResetAvatarSize,
0, // QML Qt::Key_Equal,
avatar.get(), SLOT(resetSize()));
// Avatar > Reset Sensors
addActionToQMenuAndActionHash(avatarMenu,
MenuOption::ResetSensors,
0, // QML Qt::Key_Apostrophe,
qApp, SLOT(resetSensors()));
// Avatar > Attachments...
action = addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments);
connect(action, &QAction::triggered, [] {
qApp->showDialog(QString("hifi/dialogs/AttachmentsDialog.qml"),
QString("hifi/tablet/TabletAttachmentsDialog.qml"), "AttachmentsDialog");
});
auto avatarBookmarks = DependencyManager::get<AvatarBookmarks>();
avatarBookmarks->setupMenus(this, avatarMenu);
// Display menu ----------------------------------
// FIXME - this is not yet matching Alan's spec because it doesn't have
// menus for "2D"/"3D" - we need to add support for detecting the appropriate
@ -315,13 +275,6 @@ Menu::Menu() {
QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog");
});
// Settings > Avatar...
action = addActionToQMenuAndActionHash(settingsMenu, "Avatar...");
connect(action, &QAction::triggered, [] {
qApp->showDialog(QString("hifi/dialogs/AvatarPreferencesDialog.qml"),
QString("hifi/tablet/TabletAvatarPreferences.qml"), "AvatarPreferencesDialog");
});
// Settings > Developer Menu
addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menu", 0, false, this, SLOT(toggleDeveloperMenus()));
@ -580,6 +533,9 @@ Menu::Menu() {
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false);
connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtVectors(isOptionChecked(MenuOption::ShowOtherLookAtVectors)); });
auto avatarManager = DependencyManager::get<AvatarManager>();
auto avatar = avatarManager->getMyAvatar();
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableLookAtSnapping, 0, true);
connect(action, &QAction::triggered, [this, avatar]{
avatar->setProperty("lookAtSnappingEnabled", isOptionChecked(MenuOption::EnableLookAtSnapping));

View file

@ -88,6 +88,10 @@ const float MyAvatar::ZOOM_MIN = 0.5f;
const float MyAvatar::ZOOM_MAX = 25.0f;
const float MyAvatar::ZOOM_DEFAULT = 1.5f;
const float MIN_SCALE_CHANGED_DELTA = 0.001f;
const int MODE_READINGS_RING_BUFFER_SIZE = 500;
const float CENTIMETERS_PER_METER = 100.0f;
//#define DEBUG_DRAW_HMD_MOVING_AVERAGE
MyAvatar::MyAvatar(QThread* thread) :
Avatar(thread),
@ -108,6 +112,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_hmdSensorMatrix(),
_hmdSensorOrientation(),
_hmdSensorPosition(),
_recentModeReadings(MODE_READINGS_RING_BUFFER_SIZE),
_bodySensorMatrix(),
_goToPending(false),
_goToPosition(),
@ -414,7 +419,8 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) {
void MyAvatar::update(float deltaTime) {
// update moving average of HMD facing in xz plane.
const float HMD_FACING_TIMESCALE = 4.0f; // very slow average
const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength();
float tau = deltaTime / HMD_FACING_TIMESCALE;
_headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau);
@ -423,6 +429,12 @@ void MyAvatar::update(float deltaTime) {
_smoothOrientationTimer += deltaTime;
}
float newHeightReading = getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y;
int newHeightReadingInCentimeters = glm::floor(newHeightReading * CENTIMETERS_PER_METER);
_recentModeReadings.insert(newHeightReadingInCentimeters);
setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
#ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE
auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation());
@ -1591,18 +1603,26 @@ void MyAvatar::removeAvatarEntities() {
QVariantList MyAvatar::getAvatarEntitiesVariant() {
QVariantList avatarEntitiesData;
QScriptEngine scriptEngine;
forEachChild([&](SpatiallyNestablePointer child) {
if (child->getNestableType() == NestableType::Entity) {
auto modelEntity = std::dynamic_pointer_cast<ModelEntityItem>(child);
if (modelEntity) {
QVariantMap avatarEntityData;
EntityItemProperties entityProperties = modelEntity->getProperties();
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
avatarEntityData["properties"] = scriptProperties.toVariant();
avatarEntitiesData.append(QVariant(avatarEntityData));
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) {
AvatarEntityMap avatarEntities = getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
auto entity = entityTree->findEntityByID(entityID);
if (!entity) {
continue;
}
QVariantMap avatarEntityData;
EncodeBitstreamParams params;
auto desiredProperties = entity->getEntityProperties(params);
desiredProperties += PROP_LOCAL_POSITION;
desiredProperties += PROP_LOCAL_ROTATION;
EntityItemProperties entityProperties = entity->getProperties(desiredProperties);
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
avatarEntityData["properties"] = scriptProperties.toVariant();
avatarEntitiesData.append(QVariant(avatarEntityData));
}
});
}
return avatarEntitiesData;
}
@ -1979,7 +1999,6 @@ QUrl MyAvatar::getAnimGraphUrl() const {
}
void MyAvatar::setAnimGraphUrl(const QUrl& url) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setAnimGraphUrl", Q_ARG(QUrl, url));
return;
@ -1988,6 +2007,9 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) {
if (_currentAnimGraphUrl.get() == url) {
return;
}
emit animGraphUrlChanged(url);
destroyAnimGraph();
_skeletonModel->reset(); // Why is this necessary? Without this, we crash in the next render.
@ -2172,6 +2194,15 @@ void MyAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement)
_headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement);
}
void MyAvatar::setRotationRecenterFilterLength(float length) {
const float MINIMUM_ROTATION_RECENTER_FILTER_LENGTH = 0.01f;
_rotationRecenterFilterLength = std::max(MINIMUM_ROTATION_RECENTER_FILTER_LENGTH, length);
}
void MyAvatar::setRotationThreshold(float angleRadians) {
_rotationThreshold = angleRadians;
}
void MyAvatar::updateOrientation(float deltaTime) {
// Smoothly rotate body with arrow keys
@ -2812,6 +2843,7 @@ void MyAvatar::setCollisionsEnabled(bool enabled) {
}
_characterController.setCollisionless(!enabled);
emit collisionsEnabledChanged(enabled);
}
bool MyAvatar::getCollisionsEnabled() {
@ -3135,6 +3167,148 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() const {
return worldToSensorMat * avatarToWorldMat * avatarHipsMat;
}
static bool isInsideLine(const glm::vec3& a, const glm::vec3& b, const glm::vec3& c) {
return (((b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x)) > 0);
}
static bool withinBaseOfSupport(const controller::Pose& head) {
float userScale = 1.0f;
glm::vec3 frontLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD);
glm::vec3 frontRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD);
glm::vec3 backLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD);
glm::vec3 backRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD);
bool isWithinSupport = false;
if (head.isValid()) {
bool withinFrontBase = isInsideLine(userScale * frontLeft, userScale * frontRight, head.getTranslation());
bool withinBackBase = isInsideLine(userScale * backRight, userScale * backLeft, head.getTranslation());
bool withinLateralBase = (isInsideLine(userScale * frontRight, userScale * backRight, head.getTranslation()) &&
isInsideLine(userScale * backLeft, userScale * frontLeft, head.getTranslation()));
isWithinSupport = (withinFrontBase && withinBackBase && withinLateralBase);
}
return isWithinSupport;
}
static bool headAngularVelocityBelowThreshold(const controller::Pose& head) {
glm::vec3 xzPlaneAngularVelocity(0.0f, 0.0f, 0.0f);
if (head.isValid()) {
xzPlaneAngularVelocity.x = head.getAngularVelocity().x;
xzPlaneAngularVelocity.z = head.getAngularVelocity().z;
}
float magnitudeAngularVelocity = glm::length(xzPlaneAngularVelocity);
bool isBelowThreshold = (magnitudeAngularVelocity < DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD);
return isBelowThreshold;
}
static bool isWithinThresholdHeightMode(const controller::Pose& head,const float& newMode) {
bool isWithinThreshold = true;
if (head.isValid()) {
isWithinThreshold = (head.getTranslation().y - newMode) > DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD;
}
return isWithinThreshold;
}
float MyAvatar::computeStandingHeightMode(const controller::Pose& head) {
const float MODE_CORRECTION_FACTOR = 0.02f;
int greatestFrequency = 0;
int mode = 0;
// init mode in meters to the current mode
float modeInMeters = getCurrentStandingHeight();
if (head.isValid()) {
std::map<int, int> freq;
for(auto recentModeReadingsIterator = _recentModeReadings.begin(); recentModeReadingsIterator != _recentModeReadings.end(); ++recentModeReadingsIterator) {
freq[*recentModeReadingsIterator] += 1;
if (freq[*recentModeReadingsIterator] > greatestFrequency) {
greatestFrequency = freq[*recentModeReadingsIterator];
mode = *recentModeReadingsIterator;
}
}
modeInMeters = ((float)mode) / CENTIMETERS_PER_METER;
if (!(modeInMeters > getCurrentStandingHeight())) {
// if not greater check for a reset
if (getResetMode() && qApp->isHMDMode()) {
setResetMode(false);
float resetModeInCentimeters = glm::floor((head.getTranslation().y - MODE_CORRECTION_FACTOR)*CENTIMETERS_PER_METER);
modeInMeters = (resetModeInCentimeters / CENTIMETERS_PER_METER);
_recentModeReadings.clear();
} else {
// if not greater and no reset, keep the mode as it is
modeInMeters = getCurrentStandingHeight();
}
}
}
return modeInMeters;
}
static bool handDirectionMatchesHeadDirection(const controller::Pose& leftHand, const controller::Pose& rightHand, const controller::Pose& head) {
const float VELOCITY_EPSILON = 0.02f;
bool leftHandDirectionMatchesHead = true;
bool rightHandDirectionMatchesHead = true;
glm::vec3 xzHeadVelocity(head.velocity.x, 0.0f, head.velocity.z);
if (leftHand.isValid() && head.isValid()) {
glm::vec3 xzLeftHandVelocity(leftHand.velocity.x, 0.0f, leftHand.velocity.z);
if ((glm::length(xzLeftHandVelocity) > VELOCITY_EPSILON) && (glm::length(xzHeadVelocity) > VELOCITY_EPSILON)) {
float handDotHeadLeft = glm::dot(glm::normalize(xzLeftHandVelocity), glm::normalize(xzHeadVelocity));
leftHandDirectionMatchesHead = ((handDotHeadLeft > DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD));
} else {
leftHandDirectionMatchesHead = false;
}
}
if (rightHand.isValid() && head.isValid()) {
glm::vec3 xzRightHandVelocity(rightHand.velocity.x, 0.0f, rightHand.velocity.z);
if ((glm::length(xzRightHandVelocity) > VELOCITY_EPSILON) && (glm::length(xzHeadVelocity) > VELOCITY_EPSILON)) {
float handDotHeadRight = glm::dot(glm::normalize(xzRightHandVelocity), glm::normalize(xzHeadVelocity));
rightHandDirectionMatchesHead = (handDotHeadRight > DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD);
} else {
rightHandDirectionMatchesHead = false;
}
}
return leftHandDirectionMatchesHead && rightHandDirectionMatchesHead;
}
static bool handAngularVelocityBelowThreshold(const controller::Pose& leftHand, const controller::Pose& rightHand) {
float leftHandXZAngularVelocity = 0.0f;
float rightHandXZAngularVelocity = 0.0f;
if (leftHand.isValid()) {
glm::vec3 xzLeftHandAngularVelocity(leftHand.angularVelocity.x, 0.0f, leftHand.angularVelocity.z);
leftHandXZAngularVelocity = glm::length(xzLeftHandAngularVelocity);
}
if (rightHand.isValid()) {
glm::vec3 xzRightHandAngularVelocity(rightHand.angularVelocity.x, 0.0f, rightHand.angularVelocity.z);
rightHandXZAngularVelocity = glm::length(xzRightHandAngularVelocity);
}
return ((leftHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD) &&
(rightHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD));
}
static bool headVelocityGreaterThanThreshold(const controller::Pose& head) {
float headVelocityMagnitude = 0.0f;
if (head.isValid()) {
headVelocityMagnitude = glm::length(head.getVelocity());
}
return headVelocityMagnitude > DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD;
}
glm::quat MyAvatar::computeAverageHeadRotation(const controller::Pose& head) {
const float AVERAGING_RATE = 0.03f;
return safeLerp(_averageHeadRotation, head.getRotation(), AVERAGING_RATE);
}
static bool isHeadLevel(const controller::Pose& head, const glm::quat& averageHeadRotation) {
glm::vec3 diffFromAverageEulers(0.0f, 0.0f, 0.0f);
if (head.isValid()) {
glm::vec3 averageHeadEulers = glm::degrees(safeEulerAngles(averageHeadRotation));
glm::vec3 currentHeadEulers = glm::degrees(safeEulerAngles(head.getRotation()));
diffFromAverageEulers = averageHeadEulers - currentHeadEulers;
}
return ((fabs(diffFromAverageEulers.x) < DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE) && (fabs(diffFromAverageEulers.z) < DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE));
}
float MyAvatar::getUserHeight() const {
return _userHeight.get();
}
@ -3301,7 +3475,7 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) {
}
bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees
const float FOLLOW_ROTATION_THRESHOLD = cosf(myAvatar.getRotationThreshold());
glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
}
@ -3328,7 +3502,37 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar,
}
return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN;
}
bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const {
// get the current readings
controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD);
controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND);
controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND);
bool stepDetected = false;
if (!withinBaseOfSupport(currentHeadPose) &&
headAngularVelocityBelowThreshold(currentHeadPose) &&
isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight()) &&
handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) &&
handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) &&
headVelocityGreaterThanThreshold(currentHeadPose) &&
isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) {
// a step is detected
stepDetected = true;
} else {
glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips"));
glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head"));
glm::vec3 currentHeadPosition = currentHeadPose.getTranslation();
float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition);
if (!isActive(Horizontal) &&
(glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + DEFAULT_AVATAR_SPINE_STRETCH_LIMIT))) {
myAvatar.setResetMode(true);
stepDetected = true;
}
}
return stepDetected;
}
bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
@ -3348,9 +3552,16 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Rotation);
}
if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Horizontal);
if (myAvatar.getCenterOfGravityModelEnabled()) {
if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) {
activate(Horizontal);
}
} else {
if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Horizontal);
}
}
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Vertical);
}

View file

@ -31,6 +31,7 @@
#include "AtRestDetector.h"
#include "MyCharacterController.h"
#include "RingBufferHistory.h"
#include <ThreadSafeValueCache.h>
class AvatarActionHold;
@ -195,6 +196,8 @@ class MyAvatar : public Avatar {
Q_PROPERTY(bool hasProceduralBlinkFaceMovement READ getHasProceduralBlinkFaceMovement WRITE setHasProceduralBlinkFaceMovement)
Q_PROPERTY(bool hasProceduralEyeFaceMovement READ getHasProceduralEyeFaceMovement WRITE setHasProceduralEyeFaceMovement)
Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement)
Q_PROPERTY(float rotationRecenterFilterLength READ getRotationRecenterFilterLength WRITE setRotationRecenterFilterLength)
Q_PROPERTY(float rotationThreshold READ getRotationThreshold WRITE setRotationThreshold)
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition)
@ -880,6 +883,12 @@ public:
virtual void rebuildCollisionShape() override;
const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; }
float getCurrentStandingHeight() const { return _currentStandingHeight; }
void setCurrentStandingHeight(float newMode) { _currentStandingHeight = newMode; }
const glm::quat getAverageHeadRotation() const { return _averageHeadRotation; }
void setAverageHeadRotation(glm::quat rotation) { _averageHeadRotation = rotation; }
bool getResetMode() const { return _resetMode; }
void setResetMode(bool hasBeenReset) { _resetMode = hasBeenReset; }
void setControllerPoseInSensorFrame(controller::Action action, const controller::Pose& pose);
controller::Pose getControllerPoseInSensorFrame(controller::Action action) const;
@ -888,7 +897,12 @@ public:
bool hasDriveInput() const;
QVariantList getAvatarEntitiesVariant();
/**jsdoc
* Function returns list of avatar entities
* @function MyAvatar.getAvatarEntitiesVariant()
* @returns {object[]}
*/
Q_INVOKABLE QVariantList getAvatarEntitiesVariant();
void removeAvatarEntities();
/**jsdoc
@ -1015,6 +1029,9 @@ public:
bool isReadyForPhysics() const;
float computeStandingHeightMode(const controller::Pose& head);
glm::quat computeAverageHeadRotation(const controller::Pose& head);
public slots:
/**jsdoc
@ -1294,6 +1311,22 @@ signals:
*/
void collisionWithEntity(const Collision& collision);
/**jsdoc
* Triggered when collisions with avatar enabled or disabled
* @function MyAvatar.collisionsEnabledChanged
* @param {boolean} enabled
* @returns {Signal}
*/
void collisionsEnabledChanged(bool enabled);
/**jsdoc
* Triggered when avatar's animation url changes
* @function MyAvatar.animGraphUrlChanged
* @param {url} url
* @returns {Signal}
*/
void animGraphUrlChanged(const QUrl& url);
/**jsdoc
* @function MyAvatar.energyChanged
* @param {number} energy
@ -1387,6 +1420,10 @@ private:
bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); }
void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement);
bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); }
void setRotationRecenterFilterLength(float length);
float getRotationRecenterFilterLength() const { return _rotationRecenterFilterLength; }
void setRotationThreshold(float angleRadians);
float getRotationThreshold() const { return _rotationThreshold; }
bool isMyAvatar() const override { return true; }
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
virtual glm::vec3 getSkeletonPosition() const override;
@ -1495,6 +1532,8 @@ private:
float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT };
float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT };
std::atomic<bool> _hasScriptedBlendShapes { false };
std::atomic<float> _rotationRecenterFilterLength { 4.0f };
std::atomic<float> _rotationThreshold { 0.5235f }; // 30 degrees in radians
// working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
glm::mat4 _sensorToWorldMatrix { glm::mat4() };
@ -1506,6 +1545,11 @@ private:
// cache head controller pose in sensor space
glm::vec2 _headControllerFacing; // facing vector in xz plane (sensor space)
glm::vec2 _headControllerFacingMovingAverage { 0.0f, 0.0f }; // facing vector in xz plane (sensor space)
glm::quat _averageHeadRotation { 0.0f, 0.0f, 0.0f, 1.0f };
float _currentStandingHeight { 0.0f };
bool _resetMode { true };
RingBufferHistory<int> _recentModeReadings;
// cache of the current body position and orientation of the avatar's body,
// in sensor space.
@ -1533,6 +1577,7 @@ private:
bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const;
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput);
glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
bool getForceActivateRotation() const;
@ -1621,7 +1666,7 @@ private:
// load avatar scripts once when rig is ready
bool _shouldLoadScripts { false };
bool _haveReceivedHeightLimitsFromDomain = { false };
bool _haveReceivedHeightLimitsFromDomain { false };
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -300,6 +300,12 @@ void setupPreferences() {
preference->setStep(0.001f);
preferences->addPreference(preference);
}
{
auto preference = new ButtonPreference(MOVEMENT, "RESET SENSORS", [] {
qApp->resetSensors();
});
preferences->addPreference(preference);
}
static const QString AVATAR_CAMERA{ "Mouse Sensitivity" };
{

View file

@ -42,6 +42,7 @@
#include "scripting/MenuScriptingInterface.h"
#include "scripting/SettingsScriptingInterface.h"
#include <Preferences.h>
#include <AvatarBookmarks.h>
#include <ScriptEngines.h>
#include "FileDialogHelper.h"
#include "avatar/AvatarManager.h"
@ -253,6 +254,7 @@ void Web3DOverlay::setupQmlSurface() {
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
_webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
_webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get());
_webSurface->getSurfaceContext()->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get());
_webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());

View file

@ -40,7 +40,6 @@ void GameSpaceToRender::run(const workload::WorkloadContextPointer& runContext,
auto visible = _showAllProxies || _showAllViews;
auto showProxies = _showAllProxies;
auto showViews = _showAllViews;
auto freezeViews = _freezeViews;
render::Transaction transaction;
auto scene = gameWorkloadContext->_scene;
@ -71,7 +70,7 @@ void GameSpaceToRender::run(const workload::WorkloadContextPointer& runContext,
transaction.resetItem(_spaceRenderItemID, std::make_shared<GameWorkloadRenderItem::Payload>(renderItem));
}
transaction.updateItem<GameWorkloadRenderItem>(_spaceRenderItemID, [visible, showProxies, proxies, freezeViews, showViews, views](GameWorkloadRenderItem& item) {
transaction.updateItem<GameWorkloadRenderItem>(_spaceRenderItemID, [visible, showProxies, proxies, showViews, views](GameWorkloadRenderItem& item) {
item.setVisible(visible);
item.showProxies(showProxies);
item.setAllProxies(proxies);

View file

@ -212,6 +212,8 @@ void Avatar::setTargetScale(float targetScale) {
_targetScale = newValue;
_scaleChanged = usecTimestampNow();
_isAnimatingScale = true;
emit targetScaleChanged(targetScale);
}
}

View file

@ -361,6 +361,9 @@ public:
virtual scriptable::ScriptableModelBase getScriptableModel() override;
signals:
void targetScaleChanged(float targetScale);
public slots:
// FIXME - these should be migrated to use Pose data instead

View file

@ -1575,13 +1575,12 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string
for (auto shapeID : shapeIDs) {
if (shapeID < _modelMeshRenderItemIDs.size()) {
auto itemID = _modelMeshRenderItemIDs[shapeID];
bool visible = isVisible();
auto renderItemsKey = _renderItemKeyGlobalFlags;
bool wireframe = isWireframe();
auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex;
bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex);
bool useDualQuaternionSkinning = _useDualQuaternionSkinning;
transaction.updateItem<ModelMeshPartPayload>(itemID, [material, visible, renderItemsKey,
transaction.updateItem<ModelMeshPartPayload>(itemID, [material, renderItemsKey,
invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) {
data.removeMaterial(material);
// if the material changed, we might need to update our item key or shape key

View file

@ -24,6 +24,17 @@ const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f;
const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f;
const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f;
const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.10f;
const float DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD = 0.10f;
const float DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD = 0.04f;
const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.07f;
const float DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 0.3f;
const float DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD = -0.02f;
const float DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD = 0.4f;
const float DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 3.3f;
const float DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD = 0.18f;
const float DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE = 7.0f;
const float DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE = 7.0f;
const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.07f;
const float DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR = 0.5f;
const float DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR = 2.0f;
const float DEFAULT_AVATAR_HIPS_MASS = 40.0f;

View file

@ -15,6 +15,7 @@
#include <QtCore/QVariant>
#include <QtCore/QList>
#include <QtCore/QString>
#include <QtCore/QVariantMap>
#include "DependencyManager.h"
@ -80,7 +81,6 @@ public:
}
void setEnabler(BoolPreference* enabler, bool inverse = false);
virtual Type getType() { return Invalid; };
Q_INVOKABLE virtual void load() {};

View file

@ -48,7 +48,6 @@ const quint64 CALIBRATION_TIMELAPSE = 1 * USECS_PER_SECOND;
static const char* MENU_PARENT = "Avatar";
static const char* MENU_NAME = "Vive Controllers";
static const char* MENU_PATH = "Avatar" ">" "Vive Controllers";
static const char* RENDER_CONTROLLERS = "Render Hand Controllers";
static const int MIN_HEAD = 1;
static const int MIN_PUCK_COUNT = 2;
@ -205,11 +204,6 @@ bool ViveControllerManager::activate() {
return false;
}
_container->addMenu(MENU_PATH);
_container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS,
[this](bool clicked) { this->setRenderControllers(clicked); },
true, true);
enableOpenVrKeyboard(_container);
// register with UserInputMapper
@ -224,9 +218,6 @@ void ViveControllerManager::deactivate() {
disableOpenVrKeyboard();
_container->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS);
_container->removeMenu(MENU_PATH);
if (_system) {
_container->makeRenderingContextCurrent();
releaseOpenVrSystem();

View file

@ -57,8 +57,6 @@ public:
void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); }
void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
void setRenderControllers(bool renderControllers) { _renderControllers = renderControllers; }
virtual void saveSettings() const override;
virtual void loadSettings() override;
@ -219,7 +217,6 @@ private:
int _leftHandRenderID { 0 };
int _rightHandRenderID { 0 };
bool _renderControllers { false };
vr::IVRSystem* _system { nullptr };
std::shared_ptr<InputDevice> _inputDevice { std::make_shared<InputDevice>(_system) };

View file

@ -21,6 +21,7 @@ var DEFAULT_SCRIPTS_COMBINED = [
"system/bubble.js",
"system/snapshot.js",
"system/pal.js", // "system/mod.js", // older UX, if you prefer
"system/avatarapp.js",
"system/makeUserConnection.js",
"system/tablet-goto.js",
"system/marketplaces/marketplaces.js",

550
scripts/system/avatarapp.js Normal file
View file

@ -0,0 +1,550 @@
"use strict";
/*jslint vars:true, plusplus:true, forin:true*/
/*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation*/
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
//
// avatarapp.js
//
// Created by Alexander Ivash on April 30, 2018
// Copyright 2016 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() { // BEGIN LOCAL_SCOPE
var request = Script.require('request').request;
var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml";
Script.include("/~/system/libraries/controllers.js");
// constants from AvatarBookmarks.h
var ENTRY_AVATAR_URL = "avatarUrl";
var ENTRY_AVATAR_ATTACHMENTS = "attachments";
var ENTRY_AVATAR_ENTITIES = "avatarEntites";
var ENTRY_AVATAR_SCALE = "avatarScale";
var ENTRY_VERSION = "version";
function executeLater(callback) {
Script.setTimeout(callback, 300);
}
function getMyAvatarWearables() {
var wearablesArray = MyAvatar.getAvatarEntitiesVariant();
for(var i = 0; i < wearablesArray.length; ++i) {
var wearable = wearablesArray[i];
var localRotation = wearable.properties.localRotation;
wearable.properties.localRotationAngles = Quat.safeEulerAngles(localRotation)
}
return wearablesArray;
}
function getMyAvatar() {
var avatar = {}
avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL;
avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale();
avatar[ENTRY_AVATAR_ATTACHMENTS] = MyAvatar.getAttachmentsVariant();
avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables();
return avatar;
}
function getMyAvatarSettings() {
return {
dominantHand: MyAvatar.getDominantHand(),
collisionsEnabled : MyAvatar.getCollisionsEnabled(),
collisionSoundUrl : MyAvatar.collisionSoundURL,
animGraphUrl : MyAvatar.getAnimGraphUrl(),
}
}
function updateAvatarWearables(avatar, bookmarkAvatarName) {
executeLater(function() {
var wearables = getMyAvatarWearables();
avatar[ENTRY_AVATAR_ENTITIES] = wearables;
sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables, 'avatarName' : bookmarkAvatarName})
});
}
var adjustWearables = {
opened : false,
cameraMode : '',
setOpened : function(value) {
if(this.opened !== value) {
if(value) {
this.cameraMode = Camera.mode;
if(!HMD.active) {
Camera.mode = 'mirror';
}
} else {
Camera.mode = this.cameraMode;
}
this.opened = value;
}
}
}
var currentAvatarWearablesBackup = null;
var currentAvatar = null;
var currentAvatarSettings = getMyAvatarSettings();
var notifyScaleChanged = true;
function onTargetScaleChanged() {
if(currentAvatar.scale !== MyAvatar.getAvatarScale()) {
currentAvatar.scale = MyAvatar.getAvatarScale();
if(notifyScaleChanged) {
sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale})
}
}
}
function onSkeletonModelURLChanged() {
if(currentAvatar || (currentAvatar.skeletonModelURL !== MyAvatar.skeletonModelURL)) {
fromQml({'method' : 'getAvatars'});
}
}
function onDominantHandChanged(dominantHand) {
if(currentAvatarSettings.dominantHand !== dominantHand) {
currentAvatarSettings.dominantHand = dominantHand;
sendToQml({'method' : 'settingChanged', 'name' : 'dominantHand', 'value' : dominantHand})
}
}
function onCollisionsEnabledChanged(enabled) {
if(currentAvatarSettings.collisionsEnabled !== enabled) {
currentAvatarSettings.collisionsEnabled = enabled;
sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled})
}
}
function onNewCollisionSoundUrl(url) {
if(currentAvatarSettings.collisionSoundUrl !== url) {
currentAvatarSettings.collisionSoundUrl = url;
sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url})
}
}
function onAnimGraphUrlChanged(url) {
if(currentAvatarSettings.animGraphUrl !== url) {
currentAvatarSettings.animGraphUrl = url;
sendToQml({'method' : 'settingChanged', 'name' : 'animGraphUrl', 'value' : url})
}
}
var selectedAvatarEntityGrabbable = false;
var selectedAvatarEntityID = null;
var grabbedAvatarEntityChangeNotifier = null;
var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace";
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("html/js/marketplacesInject.js");
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
switch (message.method) {
case 'getAvatars':
currentAvatar = getMyAvatar();
currentAvatarSettings = getMyAvatarSettings();
message.data = {
'bookmarks' : AvatarBookmarks.getBookmarks(),
'displayName' : MyAvatar.displayName,
'currentAvatar' : currentAvatar,
'currentAvatarSettings' : currentAvatarSettings
};
for(var bookmarkName in message.data.bookmarks) {
var bookmark = message.data.bookmarks[bookmarkName];
bookmark.avatarEntites.forEach(function(avatarEntity) {
avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation)
})
}
sendToQml(message)
break;
case 'selectAvatar':
AvatarBookmarks.loadBookmark(message.name);
break;
case 'deleteAvatar':
AvatarBookmarks.removeBookmark(message.name);
break;
case 'addAvatar':
AvatarBookmarks.addBookmark(message.name);
break;
case 'adjustWearable':
if(message.properties.localRotationAngles) {
message.properties.localRotation = Quat.fromVec3Degrees(message.properties.localRotationAngles)
}
Entities.editEntity(message.entityID, message.properties);
message.properties = Entities.getEntityProperties(message.entityID, Object.keys(message.properties));
if(message.properties.localRotation) {
message.properties.localRotationAngles = Quat.safeEulerAngles(message.properties.localRotation);
}
sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false})
break;
case 'adjustWearablesOpened':
currentAvatarWearablesBackup = getMyAvatarWearables();
adjustWearables.setOpened(true);
Entities.mousePressOnEntity.connect(onSelectedEntity);
Messages.subscribe('Hifi-Object-Manipulation');
Messages.messageReceived.connect(handleWearableMessages);
break;
case 'adjustWearablesClosed':
if(!message.save) {
// revert changes using snapshot of wearables
if(currentAvatarWearablesBackup !== null) {
AvatarBookmarks.updateAvatarEntities(currentAvatarWearablesBackup);
updateAvatarWearables(currentAvatar, message.avatarName);
}
} else {
sendToQml({'method' : 'updateAvatarInBookmarks'});
}
adjustWearables.setOpened(false);
ensureWearableSelected(null);
Entities.mousePressOnEntity.disconnect(onSelectedEntity);
Messages.messageReceived.disconnect(handleWearableMessages);
Messages.unsubscribe('Hifi-Object-Manipulation');
break;
case 'selectWearable':
ensureWearableSelected(message.entityID);
break;
case 'deleteWearable':
Entities.deleteEntity(message.entityID);
updateAvatarWearables(currentAvatar, message.avatarName);
break;
case 'changeDisplayName':
if (MyAvatar.displayName !== message.displayName) {
MyAvatar.displayName = message.displayName;
UserActivityLogger.palAction("display_name_change", message.displayName);
}
break;
case 'applyExternalAvatar':
var currentAvatarURL = MyAvatar.getFullAvatarURLFromPreferences();
if(currentAvatarURL !== message.avatarURL) {
MyAvatar.useFullAvatarURL(message.avatarURL);
sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL})
}
break;
case 'navigate':
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system")
if(message.url.indexOf('app://') === 0) {
if(message.url === 'app://marketplace') {
tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
} else if(message.url === 'app://purchases') {
tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
}
} else if(message.url.indexOf('hifi://') === 0) {
AddressManager.handleLookupString(message.url, false);
} else if(message.url.indexOf('https://') === 0 || message.url.indexOf('http://') === 0) {
tablet.gotoWebScreen(message.url, MARKETPLACES_INJECT_SCRIPT_URL);
}
break;
case 'setScale':
notifyScaleChanged = false;
MyAvatar.setAvatarScale(message.avatarScale);
currentAvatar.avatarScale = message.avatarScale;
notifyScaleChanged = true;
break;
case 'revertScale':
MyAvatar.setAvatarScale(message.avatarScale);
currentAvatar.avatarScale = message.avatarScale;
break;
case 'saveSettings':
MyAvatar.setAvatarScale(message.avatarScale);
currentAvatar.avatarScale = message.avatarScale;
MyAvatar.setDominantHand(message.settings.dominantHand);
MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled);
MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl;
MyAvatar.setAnimGraphUrl(message.settings.animGraphUrl);
settings = getMyAvatarSettings();
break;
default:
print('Unrecognized message from AvatarApp.qml:', JSON.stringify(message));
}
}
function isGrabbable(entityID) {
if(entityID === null) {
return false;
}
var properties = Entities.getEntityProperties(entityID, ['clientOnly', 'userData']);
if (properties.clientOnly) {
var userData;
try {
userData = JSON.parse(properties.userData);
} catch (e) {
userData = {};
}
return userData.grabbableKey && userData.grabbableKey.grabbable;
}
return false;
}
function setGrabbable(entityID, grabbable) {
var properties = Entities.getEntityProperties(entityID, ['clientOnly', 'userData']);
if (properties.clientOnly) {
var userData;
try {
userData = JSON.parse(properties.userData);
} catch (e) {
userData = {};
}
if (userData.grabbableKey === undefined) {
userData.grabbableKey = {};
}
userData.grabbableKey.grabbable = grabbable;
Entities.editEntity(entityID, {userData: JSON.stringify(userData)});
}
}
function ensureWearableSelected(entityID) {
if(selectedAvatarEntityID !== entityID) {
if(grabbedAvatarEntityChangeNotifier !== null) {
Script.clearInterval(grabbedAvatarEntityChangeNotifier);
grabbedAvatarEntityChangeNotifier = null;
}
if(selectedAvatarEntityID !== null) {
setGrabbable(selectedAvatarEntityID, selectedAvatarEntityGrabbable);
}
selectedAvatarEntityID = entityID;
selectedAvatarEntityGrabbable = isGrabbable(entityID);
if(selectedAvatarEntityID !== null) {
setGrabbable(selectedAvatarEntityID, true);
}
return true;
}
return false;
}
function isEntityBeingWorn(entityID) {
return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID;
};
function onSelectedEntity(entityID, pointerEvent) {
if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID))
{
if(ensureWearableSelected(entityID)) {
sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID});
}
}
}
function handleWearableMessages(channel, message, sender) {
if (channel !== 'Hifi-Object-Manipulation') {
return;
}
var parsedMessage = null;
try {
parsedMessage = JSON.parse(message);
} catch (e) {
return;
}
var entityID = parsedMessage.grabbedEntity;
if(parsedMessage.action === 'grab') {
if(selectedAvatarEntityID !== entityID) {
ensureWearableSelected(entityID);
sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID});
}
grabbedAvatarEntityChangeNotifier = Script.setInterval(function() {
// for some reasons Entities.getEntityProperties returns more than was asked..
var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions'];
var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames);
var properties = {}
propertyNames.forEach(function(propertyName) {
properties[propertyName] = entityProperties[propertyName];
})
properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation);
sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, 'wearableIndex' : -1, 'properties' : properties, updateUI : true})
}, 1000);
} else if(parsedMessage.action === 'release') {
if(grabbedAvatarEntityChangeNotifier !== null) {
Script.clearInterval(grabbedAvatarEntityChangeNotifier);
grabbedAvatarEntityChangeNotifier = null;
}
}
}
function sendToQml(message) {
tablet.sendToQml(message);
}
function onBookmarkLoaded(bookmarkName) {
executeLater(function() {
currentAvatar = getMyAvatar();
sendToQml({'method' : 'bookmarkLoaded', 'data' : {'name' : bookmarkName, 'currentAvatar' : currentAvatar} });
});
}
function onBookmarkDeleted(bookmarkName) {
sendToQml({'method' : 'bookmarkDeleted', 'name' : bookmarkName});
}
function onBookmarkAdded(bookmarkName) {
var bookmark = AvatarBookmarks.getBookmark(bookmarkName);
bookmark.avatarEntites.forEach(function(avatarEntity) {
avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation)
})
sendToQml({ 'method': 'bookmarkAdded', 'bookmarkName': bookmarkName, 'bookmark': bookmark });
}
//
// Manage the connection between the button and the window.
//
var button;
var buttonName = "AVATAR";
var tablet = null;
function startup() {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
text: buttonName,
icon: "icons/tablet-icons/avatar-i.svg",
activeIcon: "icons/tablet-icons/avatar-a.svg",
sortOrder: 7
});
button.clicked.connect(onTabletButtonClicked);
tablet.screenChanged.connect(onTabletScreenChanged);
}
startup();
var isWired = false;
function off() {
if (isWired) { // It is not ok to disconnect these twice, hence guard.
isWired = false;
}
if(adjustWearables.opened) {
adjustWearables.setOpened(false);
ensureWearableSelected(null);
Entities.mousePressOnEntity.disconnect(onSelectedEntity);
Messages.messageReceived.disconnect(handleWearableMessages);
Messages.unsubscribe('Hifi-Object-Manipulation');
}
AvatarBookmarks.bookmarkLoaded.disconnect(onBookmarkLoaded);
AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted);
AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded);
MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged);
MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged);
MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged);
MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl);
MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged);
MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged);
}
function on() {
AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded);
AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted);
AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded);
MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged);
MyAvatar.dominantHandChanged.connect(onDominantHandChanged);
MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged);
MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl);
MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged);
MyAvatar.targetScaleChanged.connect(onTargetScaleChanged);
}
function onTabletButtonClicked() {
if (onAvatarAppScreen) {
// for toolbar-mode: go back to home screen, this will close the window.
tablet.gotoHomeScreen();
} else {
ContextOverlay.enabled = false;
tablet.loadQMLSource(AVATARAPP_QML_SOURCE);
isWired = true;
}
}
var hasEventBridge = false;
function wireEventBridge(on) {
if (on) {
if (!hasEventBridge) {
tablet.fromQml.connect(fromQml);
hasEventBridge = true;
}
} else {
if (hasEventBridge) {
tablet.fromQml.disconnect(fromQml);
hasEventBridge = false;
}
}
}
var onAvatarAppScreen = false;
function onTabletScreenChanged(type, url) {
var onAvatarAppScreenNow = (type === "QML" && url === AVATARAPP_QML_SOURCE);
wireEventBridge(onAvatarAppScreenNow);
// for toolbar mode: change button to active when window is first openend, false otherwise.
button.editProperties({isActive: onAvatarAppScreenNow});
if (!onAvatarAppScreen && onAvatarAppScreenNow) {
on();
} else if(onAvatarAppScreen && !onAvatarAppScreenNow) {
off();
}
onAvatarAppScreen = onAvatarAppScreenNow;
if(onAvatarAppScreenNow) {
var message = {
'method' : 'initialize',
'data' : {
'jointNames' : MyAvatar.getJointNames()
}
};
sendToQml(message)
}
}
function shutdown() {
if (onAvatarAppScreen) {
tablet.gotoHomeScreen();
}
button.clicked.disconnect(onTabletButtonClicked);
tablet.removeButton(button);
tablet.screenChanged.disconnect(onTabletScreenChanged);
off();
}
//
// Cleanup.
//
Script.scriptEnding.connect(shutdown);
}()); // END LOCAL_SCOPE

View file

@ -29,7 +29,6 @@ var CONTOLLER_SCRIPTS = [
"controllerModules/disableOtherModule.js",
"controllerModules/farTrigger.js",
"controllerModules/teleport.js",
"controllerModules/scaleAvatar.js",
"controllerModules/hudOverlayPointer.js",
"controllerModules/mouseHMD.js",
"controllerModules/scaleEntity.js",

View file

@ -10,17 +10,15 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger,
Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */
/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager,
Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera,
progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool, OverlaySystemWindow */
(function() { // BEGIN LOCAL_SCOPE
"use strict";
var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton";
var SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system";
var EDIT_TOOLBAR = "com.highfidelity.interface.toolbar.edit";
Script.include([
"libraries/stringHelpers.js",
@ -36,13 +34,43 @@ Script.include([
"libraries/entityIconOverlayManager.js"
]);
var CreateWindow = Script.require('./modules/createWindow.js');
var TITLE_OFFSET = 60;
var CREATE_TOOLS_WIDTH = 490;
var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942;
var createToolsWindow = new CreateWindow(
Script.resourcesPath() + "qml/hifi/tablet/EditTools.qml",
'Create Tools',
'com.highfidelity.create.createToolsWindow',
function () {
var windowHeight = Window.innerHeight - TITLE_OFFSET;
if (windowHeight > MAX_DEFAULT_ENTITY_LIST_HEIGHT) {
windowHeight = MAX_DEFAULT_ENTITY_LIST_HEIGHT;
}
return {
size: {
x: CREATE_TOOLS_WIDTH,
y: windowHeight
},
position: {
x: Window.x + Window.innerWidth - CREATE_TOOLS_WIDTH,
y: Window.y + TITLE_OFFSET
}
}
},
false
);
var selectionDisplay = SelectionDisplay;
var selectionManager = SelectionManager;
var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg");
var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg");
var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg");
entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect'], function(entityID) {
var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect'], function(entityID) {
var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']);
if (properties.type === 'Light') {
return {
@ -59,7 +87,8 @@ var cameraManager = new CameraManager();
var grid = new Grid();
var gridTool = new GridTool({
horizontalGrid: grid
horizontalGrid: grid,
createToolsWindow: createToolsWindow
});
gridTool.setVisible(false);
@ -207,7 +236,7 @@ function hideMarketplace() {
// }
function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) {
// Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original
// Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original
// position in the given direction.
var CORNERS = [
{ x: 0, y: 0, z: 0 },
@ -232,7 +261,6 @@ function adjustPositionPerBoundingBox(position, direction, registration, dimensi
return position;
}
var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit";
// Handles any edit mode updates required when domains have switched
@ -260,6 +288,7 @@ var toolBar = (function () {
toolBar,
activeButton = null,
systemToolbar = null,
dialogWindow = null,
tablet = null;
function createNewEntity(properties) {
@ -356,6 +385,13 @@ var toolBar = (function () {
return entityID;
}
function closeExistingDialogWindow() {
if (dialogWindow) {
dialogWindow.close();
dialogWindow = null;
}
}
function cleanup() {
that.setActive(false);
if (tablet) {
@ -438,7 +474,7 @@ var toolBar = (function () {
if (materialURL.startsWith("materialData")) {
materialData = JSON.stringify({
"materials": {}
})
});
}
var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1;
@ -458,15 +494,23 @@ var toolBar = (function () {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.popFromStack();
switch (message.method) {
case "newModelDialogAdd":
handleNewModelDialogResult(message.params);
break;
case "newEntityButtonClicked":
buttonHandlers[message.params.buttonName]();
break;
case "newMaterialDialogAdd":
handleNewMaterialDialogResult(message.params);
break;
case "newModelDialogAdd":
handleNewModelDialogResult(message.params);
closeExistingDialogWindow();
break;
case "newModelDialogCancel":
closeExistingDialogWindow();
break;
case "newEntityButtonClicked":
buttonHandlers[message.params.buttonName]();
break;
case "newMaterialDialogAdd":
handleNewMaterialDialogResult(message.params);
closeExistingDialogWindow();
break;
case "newMaterialDialogCancel":
closeExistingDialogWindow();
break;
}
}
@ -501,6 +545,13 @@ var toolBar = (function () {
checkEditPermissionsAndUpdate();
});
HMD.displayModeChanged.connect(function() {
if (isActive) {
tablet.gotoHomeScreen();
}
that.setActive(false);
});
Entities.canAdjustLocksChanged.connect(function (canAdjustLocks) {
if (isActive && !canAdjustLocks) {
that.setActive(false);
@ -527,11 +578,13 @@ var toolBar = (function () {
});
createButton = activeButton;
tablet.screenChanged.connect(function (type, url) {
if (isActive && (type !== "QML" || url !== "hifi/tablet/Edit.qml")) {
that.setActive(false)
var isGoingToHomescreenOnDesktop = (!HMD.active && (url === 'hifi/tablet/TabletHome.qml' || url === ''));
if (isActive && (type !== "QML" || url !== "hifi/tablet/Edit.qml") && !isGoingToHomescreenOnDesktop) {
that.setActive(false);
}
});
tablet.fromQml.connect(fromQml);
createToolsWindow.fromQml.addListener(fromQml);
createButton.clicked.connect(function() {
if ( ! (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()) ) {
@ -550,12 +603,29 @@ var toolBar = (function () {
addButton("openAssetBrowserButton", function() {
Window.showAssetServer();
});
function createNewEntityDialogButtonCallback(entityType) {
return function() {
if (HMD.active) {
// tablet version of new-model dialog
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.pushOntoStack("hifi/tablet/New" + entityType + "Dialog.qml");
} else {
closeExistingDialogWindow();
var qmlPath = Script.resourcesPath() + "qml/hifi/tablet/New" + entityType + "Window.qml";
var DIALOG_WINDOW_SIZE = { x: 500, y: 300 };
dialogWindow = Desktop.createWindow(qmlPath, {
title: "New " + entityType + " Entity",
flags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES,
presentationMode: Desktop.PresentationMode.NATIVE,
size: DIALOG_WINDOW_SIZE,
visible: true
});
dialogWindow.fromQml.connect(fromQml);
}
};
};
addButton("newModelButton", function () {
// tablet version of new-model dialog
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.pushOntoStack("hifi/tablet/NewModelDialog.qml");
});
addButton("newModelButton", createNewEntityDialogButtonCallback("Model"));
addButton("newCubeButton", function () {
createNewEntity({
@ -716,11 +786,7 @@ var toolBar = (function () {
});
});
addButton("newMaterialButton", function () {
// tablet version of new material dialog
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.pushOntoStack("hifi/tablet/NewMaterialDialog.qml");
});
addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material"));
that.setActive(false);
}
@ -743,6 +809,8 @@ var toolBar = (function () {
Controller.captureEntityClickEvents();
} else {
Controller.releaseEntityClickEvents();
closeExistingDialogWindow();
}
if (active === isActive) {
return;
@ -769,7 +837,12 @@ var toolBar = (function () {
selectionDisplay.triggerMapping.disable();
tablet.landscape = false;
} else {
tablet.loadQMLSource("hifi/tablet/Edit.qml", true);
if (HMD.active) {
tablet.loadQMLSource("hifi/tablet/Edit.qml", true);
} else {
// make other apps inactive while in desktop mode
tablet.gotoHomeScreen();
}
UserActivityLogger.enabledEdit();
entityListTool.setVisible(true);
gridTool.setVisible(true);
@ -790,17 +863,6 @@ var toolBar = (function () {
return that;
})();
function isLocked(properties) {
// special case to lock the ground plane model in hq.
if (location.hostname === "hq.highfidelity.io" &&
properties.modelURL === HIFI_PUBLIC_BUCKET + "ozan/Terrain_Reduce_forAlpha.fbx") {
return true;
}
return false;
}
var selectedEntityID;
var orientation;
var intersection;
@ -1047,68 +1109,62 @@ function mouseClickEvent(event) {
return;
}
properties = Entities.getEntityProperties(foundEntity);
if (isLocked(properties)) {
if (wantDebug) {
print("Model locked " + properties.id);
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
if (wantDebug) {
print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal);
}
// P P - Model
// /| A - Palm
// / | d B - unit vector toward tip
// / | X - base of the perpendicular line
// A---X----->B d - distance fom axis
// x x - distance from A
//
// |X-A| = (P-A).B
// X === A + ((P-A).B)B
// d = |P-X|
var A = pickRay.origin;
var B = Vec3.normalize(pickRay.direction);
var P = properties.position;
var x = Vec3.dot(Vec3.subtract(P, A), B);
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) *
180 / Math.PI;
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) &&
(allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
if (0 < x && sizeOK) {
selectedEntityID = foundEntity;
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation));
if (event.isShifted) {
particleExplorerTool.destroyWebView();
}
if (properties.type !== "ParticleEffect") {
particleExplorerTool.destroyWebView();
}
if (!event.isShifted) {
selectionManager.setSelections([foundEntity]);
} else {
selectionManager.addEntity(foundEntity, true);
}
} else {
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
if (wantDebug) {
print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal);
print("Model selected: " + foundEntity);
}
// P P - Model
// /| A - Palm
// / | d B - unit vector toward tip
// / | X - base of the perpendicular line
// A---X----->B d - distance fom axis
// x x - distance from A
//
// |X-A| = (P-A).B
// X === A + ((P-A).B)B
// d = |P-X|
selectionDisplay.select(selectedEntityID, event);
var A = pickRay.origin;
var B = Vec3.normalize(pickRay.direction);
var P = properties.position;
var x = Vec3.dot(Vec3.subtract(P, A), B);
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) *
180 / Math.PI;
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) &&
(allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
if (0 < x && sizeOK) {
selectedEntityID = foundEntity;
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation));
if (event.isShifted) {
particleExplorerTool.destroyWebView();
}
if (properties.type !== "ParticleEffect") {
particleExplorerTool.destroyWebView();
}
if (!event.isShifted) {
selectionManager.setSelections([foundEntity]);
} else {
selectionManager.addEntity(foundEntity, true);
}
if (wantDebug) {
print("Model selected: " + foundEntity);
}
selectionDisplay.select(selectedEntityID, event);
if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) {
cameraManager.enable();
cameraManager.focus(selectionManager.worldPosition,
selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
}
if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) {
cameraManager.enable();
cameraManager.focus(selectionManager.worldPosition,
selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
}
}
} else if (event.isRightButton) {
@ -1368,11 +1424,7 @@ function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) {
var localPosition = Vec3.multiplyQbyV(Quat.inverse(selectionManager.localRotation),
Vec3.subtract(position,
selectionManager.localPosition));
return insideBox({
x: 0,
y: 0,
z: 0
}, selectionManager.localDimensions, localPosition);
return insideBox(Vec3.ZERO, selectionManager.localDimensions, localPosition);
};
}
for (var i = 0; i < entities.length; ++i) {
@ -1476,7 +1528,7 @@ function parentSelectedEntities() {
return;
}
var parentCheck = false;
var lastEntityId = selectedEntities[selectedEntities.length-1];
var lastEntityId = selectedEntities[selectedEntities.length - 1];
selectedEntities.forEach(function (id, index) {
if (lastEntityId !== id) {
var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID;
@ -1489,7 +1541,7 @@ function parentSelectedEntities() {
if (parentCheck) {
Window.notify("Entities parented");
}else {
} else {
Window.notify("Entities are already parented to last");
}
} else {
@ -1902,8 +1954,6 @@ function pushCommandForSelections(createdEntityData, deletedEntityData) {
UndoStack.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData);
}
var ENTITY_PROPERTIES_URL = Script.resolvePath('html/entityProperties.html');
var ServerScriptStatusMonitor = function(entityID, statusCallback) {
var self = this;
@ -1947,13 +1997,14 @@ var PropertiesTool = function (opts) {
var currentSelectedEntityID = null;
var statusMonitor = null;
webView.setVisible(visible);
that.setVisible = function (newVisible) {
visible = newVisible;
webView.setVisible(visible);
webView.setVisible(HMD.active && visible);
createToolsWindow.setVisible(!HMD.active && visible);
};
that.setVisible(false);
function updateScriptStatus(info) {
info.type = "server_script_status";
webView.emitScriptEvent(JSON.stringify(info));
@ -1982,7 +2033,7 @@ var PropertiesTool = function (opts) {
statusMonitor = null;
}
currentSelectedEntityID = null;
} else if (currentSelectedEntityID != selectionManager.selections[0]) {
} else if (currentSelectedEntityID !== selectionManager.selections[0]) {
if (statusMonitor !== null) {
statusMonitor.stop();
}
@ -2008,11 +2059,14 @@ var PropertiesTool = function (opts) {
selections.push(entity);
}
data.selections = selections;
webView.emitScriptEvent(JSON.stringify(data));
createToolsWindow.emitScriptEvent(JSON.stringify(data));
}
selectionManager.addEventListener(updateSelections);
webView.webEventReceived.connect(function (data) {
var onWebEventReceived = function(data) {
try {
data = JSON.parse(data);
}
@ -2034,16 +2088,8 @@ var PropertiesTool = function (opts) {
} else if (data.properties) {
if (data.properties.dynamic === false) {
// this object is leaving dynamic, so we zero its velocities
data.properties.velocity = {
x: 0,
y: 0,
z: 0
};
data.properties.angularVelocity = {
x: 0,
y: 0,
z: 0
};
data.properties.velocity = Vec3.ZERO;
data.properties.angularVelocity = Vec3.ZERO;
}
if (data.properties.rotation !== undefined) {
var rotation = data.properties.rotation;
@ -2171,7 +2217,11 @@ var PropertiesTool = function (opts) {
} else if (data.type === "propertiesPageReady") {
updateSelections(true);
}
});
};
createToolsWindow.webEventReceived.addListener(this, onWebEventReceived);
webView.webEventReceived.connect(onWebEventReceived);
return that;
};
@ -2186,6 +2236,8 @@ var PopupMenu = function () {
var overlays = [];
var overlayInfo = {};
var visible = false;
var upColor = {
red: 0,
green: 0,
@ -2303,8 +2355,6 @@ var PopupMenu = function () {
}
};
var visible = false;
self.setVisible = function (newVisible) {
if (newVisible !== visible) {
visible = newVisible;
@ -2358,7 +2408,7 @@ propertyMenu.onSelectMenuItem = function (name) {
var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace");
var propertiesTool = new PropertiesTool();
var particleExplorerTool = new ParticleExplorerTool();
var particleExplorerTool = new ParticleExplorerTool(createToolsWindow);
var selectedParticleEntityID = null;
function selectParticleEntity(entityID) {
@ -2375,11 +2425,16 @@ function selectParticleEntity(entityID) {
particleExplorerTool.setActiveParticleEntity(entityID);
// Switch to particle explorer
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.sendToQml({method: 'selectTab', params: {id: 'particle'}});
var selectTabMethod = { method: 'selectTab', params: { id: 'particle' } };
if (HMD.active) {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.sendToQml(selectTabMethod);
} else {
createToolsWindow.sendToQml(selectTabMethod);
}
}
entityListTool.webView.webEventReceived.connect(function (data) {
entityListTool.webView.webEventReceived.connect(function(data) {
try {
data = JSON.parse(data);
} catch(e) {

View file

@ -11,27 +11,64 @@
/* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages,
cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible */
EntityListTool = function(opts) {
EntityListTool = function() {
var that = {};
var CreateWindow = Script.require('../modules/createWindow.js');
var TITLE_OFFSET = 60;
var ENTITY_LIST_WIDTH = 495;
var MAX_DEFAULT_CREATE_TOOLS_HEIGHT = 778;
var entityListWindow = new CreateWindow(
Script.resourcesPath() + "qml/hifi/tablet/EditEntityList.qml",
'Entity List',
'com.highfidelity.create.entityListWindow',
function () {
var windowHeight = Window.innerHeight - TITLE_OFFSET;
if (windowHeight > MAX_DEFAULT_CREATE_TOOLS_HEIGHT) {
windowHeight = MAX_DEFAULT_CREATE_TOOLS_HEIGHT;
}
return {
size: {
x: ENTITY_LIST_WIDTH,
y: windowHeight
},
position: {
x: Window.x,
y: Window.y + TITLE_OFFSET
}
};
},
false
);
var webView = null;
webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
webView.setVisible = function(value) {};
webView.setVisible = function(value){ };
var filterInView = false;
var searchRadius = 100;
var visible = false;
webView.setVisible(visible);
that.webView = webView;
that.setVisible = function(newVisible) {
visible = newVisible;
webView.setVisible(visible);
webView.setVisible(HMD.active && visible);
entityListWindow.setVisible(!HMD.active && visible);
};
that.setVisible(false);
function emitJSONScriptEvent(data) {
var dataString = JSON.stringify(data);
webView.emitScriptEvent(dataString);
if (entityListWindow.window) {
entityListWindow.window.emitScriptEvent(dataString);
}
}
that.toggleVisible = function() {
that.setVisible(!visible);
};
@ -43,18 +80,16 @@ EntityListTool = function(opts) {
selectedIDs.push(selectionManager.selections[i]);
}
var data = {
emitJSONScriptEvent({
type: 'selectionUpdate',
selectedIDs: selectedIDs,
};
webView.emitScriptEvent(JSON.stringify(data));
selectedIDs: selectedIDs
});
});
that.clearEntityList = function () {
var data = {
that.clearEntityList = function() {
emitJSONScriptEvent({
type: 'clearEntityList'
};
webView.emitScriptEvent(JSON.stringify(data));
});
};
that.removeEntities = function (deletedIDs, selectedIDs) {
@ -87,9 +122,9 @@ EntityListTool = function(opts) {
if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) {
var url = "";
if (properties.type == "Model") {
if (properties.type === "Model") {
url = properties.modelURL;
} else if (properties.type == "Material") {
} else if (properties.type === "Material") {
url = properties.materialURL;
}
entities.push({
@ -107,7 +142,7 @@ EntityListTool = function(opts) {
valueIfDefined(properties.renderInfo.texturesSize) : ""),
hasTransparent: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.hasTransparent) : ""),
isBaked: properties.type == "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false,
isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false,
drawCalls: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.drawCalls) : ""),
hasScript: properties.script !== ""
@ -120,12 +155,11 @@ EntityListTool = function(opts) {
selectedIDs.push(selectionManager.selections[j]);
}
var data = {
emitJSONScriptEvent({
type: "update",
entities: entities,
selectedIDs: selectedIDs,
};
webView.emitScriptEvent(JSON.stringify(data));
});
};
function onFileSaveChanged(filename) {
@ -138,15 +172,15 @@ EntityListTool = function(opts) {
}
}
webView.webEventReceived.connect(function(data) {
var onWebEventReceived = function(data) {
try {
data = JSON.parse(data);
} catch(e) {
print("entityList.js: Error parsing JSON: " + e.name + " data " + data)
print("entityList.js: Error parsing JSON: " + e.name + " data " + data);
return;
}
if (data.type == "selectionUpdate") {
if (data.type === "selectionUpdate") {
var ids = data.entityIds;
var entityIDs = [];
for (var i = 0; i < ids.length; i++) {
@ -159,20 +193,20 @@ EntityListTool = function(opts) {
selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
}
} else if (data.type == "refresh") {
} else if (data.type === "refresh") {
that.sendUpdate();
} else if (data.type == "teleport") {
} else if (data.type === "teleport") {
if (selectionManager.hasSelection()) {
MyAvatar.position = selectionManager.worldPosition;
}
} else if (data.type == "export") {
} else if (data.type === "export") {
if (!selectionManager.hasSelection()) {
Window.notifyEditError("No entities have been selected.");
} else {
Window.saveFileChanged.connect(onFileSaveChanged);
Window.saveAsync("Select Where to Save", "", "*.json");
}
} else if (data.type == "pal") {
} else if (data.type === "pal") {
var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates.
selectionManager.selections.forEach(function (id) {
var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy;
@ -189,24 +223,21 @@ EntityListTool = function(opts) {
// No need to subscribe if we're just sending.
Messages.sendMessage('com.highfidelity.pal', JSON.stringify({method: 'select', params: [dedupped, true, false]}), 'local');
}
} else if (data.type == "delete") {
} else if (data.type === "delete") {
deleteSelectedEntities();
} else if (data.type == "toggleLocked") {
} else if (data.type === "toggleLocked") {
toggleSelectedEntitiesLocked();
} else if (data.type == "toggleVisible") {
} else if (data.type === "toggleVisible") {
toggleSelectedEntitiesVisible();
} else if (data.type === "filterInView") {
filterInView = data.filterInView === true;
} else if (data.type === "radius") {
searchRadius = data.radius;
}
});
};
// webView.visibleChanged.connect(function () {
// if (webView.visible) {
// that.sendUpdate();
// }
// });
webView.webEventReceived.connect(onWebEventReceived);
entityListWindow.webEventReceived.addListener(onWebEventReceived);
return that;
};

View file

@ -13,7 +13,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global SelectionManager, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections,
/* global SelectionManager, SelectionDisplay, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections,
getMainTabletIDs, getControllerWorldLocation */
var SPACE_LOCAL = "local";
@ -72,7 +72,9 @@ SelectionManager = (function() {
subscribeToUpdateMessages();
var COLOR_ORANGE_HIGHLIGHT = { red: 255, green: 99, blue: 9 }
// disabling this for now as it is causing rendering issues with the other handle overlays
/*
var COLOR_ORANGE_HIGHLIGHT = { red: 255, green: 99, blue: 9 };
var editHandleOutlineStyle = {
outlineUnoccludedColor: COLOR_ORANGE_HIGHLIGHT,
outlineOccludedColor: COLOR_ORANGE_HIGHLIGHT,
@ -85,8 +87,8 @@ SelectionManager = (function() {
outlineWidth: 3,
isOutlineSmooth: true
};
// disabling this for now as it is causing rendering issues with the other handle overlays
//Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, editHandleOutlineStyle);
Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, editHandleOutlineStyle);
*/
that.savedProperties = {};
that.selections = [];
@ -411,8 +413,6 @@ SelectionDisplay = (function() {
var spaceMode = SPACE_LOCAL;
var overlayNames = [];
var lastCameraPosition = Camera.getPosition();
var lastCameraOrientation = Camera.getOrientation();
var lastControllerPoses = [
getControllerWorldLocation(Controller.Standard.LeftHand, true),
getControllerWorldLocation(Controller.Standard.RightHand, true)
@ -432,7 +432,7 @@ SelectionDisplay = (function() {
var ctrlPressed = false;
var replaceCollisionsAfterStretch = false;
that.replaceCollisionsAfterStretch = false;
var handlePropertiesTranslateArrowCones = {
shape: "Cone",
@ -1018,7 +1018,7 @@ SelectionDisplay = (function() {
if (SelectionManager.hasSelection()) {
var controllerPose = getControllerWorldLocation(activeHand, true);
var hand = (activeHand === Controller.Standard.LeftHand) ? 0 : 1;
if (controllerPose.valid && lastControllerPoses[hand].valid) {
if (controllerPose.valid && lastControllerPoses[hand].valid && that.triggered) {
if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) ||
!Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) {
that.mouseMoveEvent({});
@ -1068,9 +1068,6 @@ SelectionDisplay = (function() {
that.select = function(entityID, event) {
var properties = Entities.getEntityProperties(SelectionManager.selections[0]);
lastCameraPosition = Camera.getPosition();
lastCameraOrientation = Camera.getOrientation();
if (event !== false) {
var wantDebug = false;
if (wantDebug) {
@ -1668,7 +1665,7 @@ SelectionDisplay = (function() {
translateXZTool.pickPlanePosition = pickResult.intersection;
translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x,
SelectionManager.worldDimensions.y),
SelectionManager.worldDimensions.y),
SelectionManager.worldDimensions.z);
translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position);
translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition);
@ -1818,23 +1815,27 @@ SelectionDisplay = (function() {
function addHandleTranslateTool(overlay, mode, direction) {
var pickNormal = null;
var lastPick = null;
var initialPosition = null;
var projectionVector = null;
var previousPickRay = null;
addHandleTool(overlay, {
mode: mode,
onBegin: function(event, pickRay, pickResult) {
var axisVector;
if (direction === TRANSLATE_DIRECTION.X) {
pickNormal = { x: 0, y: 1, z: 1 };
axisVector = { x: 1, y: 0, z: 0 };
} else if (direction === TRANSLATE_DIRECTION.Y) {
pickNormal = { x: 1, y: 0, z: 1 };
axisVector = { x: 0, y: 1, z: 0 };
} else if (direction === TRANSLATE_DIRECTION.Z) {
pickNormal = { x: 1, y: 1, z: 0 };
axisVector = { x: 0, y: 0, z: 1 };
}
var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation;
pickNormal = Vec3.multiplyQbyV(rotation, pickNormal);
axisVector = Vec3.multiplyQbyV(rotation, axisVector);
pickNormal = Vec3.cross(Vec3.cross(pickRay.direction, axisVector), axisVector);
lastPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal);
initialPosition = SelectionManager.worldPosition;
SelectionManager.saveProperties();
that.resetPreviousHandleColor();
@ -1869,7 +1870,7 @@ SelectionDisplay = (function() {
pickRay = previousPickRay;
}
var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal);
var newIntersection = rayPlaneIntersection(pickRay, initialPosition, pickNormal);
var vector = Vec3.subtract(newIntersection, lastPick);
if (direction === TRANSLATE_DIRECTION.X) {
@ -1885,7 +1886,8 @@ SelectionDisplay = (function() {
var dotVector = Vec3.dot(vector, projectionVector);
vector = Vec3.multiply(dotVector, projectionVector);
vector = grid.snapToGrid(vector);
var gridOrigin = grid.getOrigin();
vector = Vec3.subtract(grid.snapToGrid(Vec3.sum(vector, gridOrigin)), gridOrigin);
var wantDebug = false;
if (wantDebug) {
@ -2185,8 +2187,8 @@ SelectionDisplay = (function() {
newDimensions = Vec3.sum(initialDimensions, changeInDimensions);
}
var minimumDimension = directionEnum === STRETCH_DIRECTION.ALL ? STRETCH_ALL_MINIMUM_DIMENSION :
STRETCH_MINIMUM_DIMENSION;
var minimumDimension = directionEnum ===
STRETCH_DIRECTION.ALL ? STRETCH_ALL_MINIMUM_DIMENSION : STRETCH_MINIMUM_DIMENSION;
if (newDimensions.x < minimumDimension) {
newDimensions.x = minimumDimension;
changeInDimensions.x = minimumDimension - initialDimensions.x;
@ -2280,8 +2282,7 @@ SelectionDisplay = (function() {
selectedHandle = handleScaleRTFCube;
}
offset = Vec3.multiply(directionVector, NEGATE_VECTOR);
var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector,
directionVector, offset, null, selectedHandle);
var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector, directionVector, offset, null, selectedHandle);
return addHandleTool(overlay, tool);
}

View file

@ -240,6 +240,7 @@ GridTool = function(opts) {
var horizontalGrid = opts.horizontalGrid;
var verticalGrid = opts.verticalGrid;
var createToolsWindow = opts.createToolsWindow;
var listeners = [];
var webView = null;
@ -247,13 +248,15 @@ GridTool = function(opts) {
webView.setVisible = function(value) { };
horizontalGrid.addListener(function(data) {
webView.emitScriptEvent(JSON.stringify(data));
var dataString = JSON.stringify(data);
webView.emitScriptEvent(dataString);
createToolsWindow.emitScriptEvent(dataString);
if (selectionDisplay) {
selectionDisplay.updateHandles();
}
});
webView.webEventReceived.connect(function(data) {
var webEventReceived = function(data) {
try {
data = JSON.parse(data);
} catch (e) {
@ -282,14 +285,17 @@ GridTool = function(opts) {
grid.setPosition(newPosition);
}
}
});
};
webView.webEventReceived.connect(webEventReceived);
createToolsWindow.webEventReceived.addListener(webEventReceived);
that.addListener = function(callback) {
listeners.push(callback);
};
that.setVisible = function(visible) {
webView.setVisible(visible);
webView.setVisible(HMD.active && visible);
};
return that;

View file

@ -0,0 +1,151 @@
"use strict";
// createWindow.js
//
// Created by Thijs Wenker on 6/1/18
//
// 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
//
var getWindowRect = function(settingsKey, defaultRect) {
var windowRect = Settings.getValue(settingsKey, defaultRect);
return windowRect;
};
var setWindowRect = function(settingsKey, position, size) {
Settings.setValue(settingsKey, {
position: position,
size: size
});
};
var CallableEvent = (function() {
function CallableEvent() {
this.callbacks = [];
}
CallableEvent.prototype = {
callbacks: null,
call: function () {
var callArguments = arguments;
this.callbacks.forEach(function(callbackObject) {
try {
callbackObject.callback.apply(callbackObject.context ? callbackObject.context : this, callArguments);
} catch (e) {
console.error('Call to CallableEvent callback failed!');
}
});
},
addListener: function(contextOrCallback, callback) {
if (callback) {
this.callbacks.push({
context: contextOrCallback,
callback: callback
});
} else {
this.callbacks.push({
callback: contextOrCallback
});
}
},
removeListener: function(callback) {
var foundIndex = -1;
this.callbacks.forEach(function (callbackObject, index) {
if (callbackObject.callback === callback) {
foundIndex = index;
}
});
if (foundIndex !== -1) {
this.callbacks.splice(foundIndex, 1);
}
}
};
return CallableEvent;
})();
module.exports = (function() {
function CreateWindow(qmlPath, title, settingsKey, defaultRect, createOnStartup) {
this.qmlPath = qmlPath;
this.title = title;
this.settingsKey = settingsKey;
this.defaultRect = defaultRect;
this.webEventReceived = new CallableEvent();
this.fromQml = new CallableEvent();
if (createOnStartup) {
this.createWindow();
}
}
CreateWindow.prototype = {
window: null,
createWindow: function() {
var defaultRect = this.defaultRect;
if (typeof this.defaultRect === "function") {
defaultRect = this.defaultRect();
}
var windowRect = getWindowRect(this.settingsKey, defaultRect);
this.window = Desktop.createWindow(this.qmlPath, {
title: this.title,
flags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES,
presentationMode: Desktop.PresentationMode.NATIVE,
size: windowRect.size,
visible: true,
position: windowRect.position
});
var windowRectChanged = function () {
if (this.window.visible) {
setWindowRect(this.settingsKey, this.window.position, this.window.size);
}
};
this.window.sizeChanged.connect(this, windowRectChanged);
this.window.positionChanged.connect(this, windowRectChanged);
this.window.webEventReceived.connect(this, function (data) {
this.webEventReceived.call(data);
});
this.window.fromQml.connect(this, function (data) {
this.fromQml.call(data);
});
Script.scriptEnding.connect(this, function() {
this.window.close();
});
},
setVisible: function(visible) {
if (visible && !this.window) {
this.createWindow();
}
if (this.window) {
if (visible) {
this.window.show();
} else {
this.window.visible = false;
}
}
},
emitScriptEvent: function(data) {
if (this.window) {
this.window.emitScriptEvent(data);
}
},
sendToQml: function(data) {
if (this.window) {
this.window.sendToQml(data);
}
},
webEventReceived: null,
fromQml: null
};
return CreateWindow;
})();

View file

@ -9,13 +9,12 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global window, alert, ParticleExplorerTool, EventBridge, dat, listenForSettingsUpdates, createVec3Folder,
createQuatFolder, writeVec3ToInterface, writeDataToInterface */
/* global ParticleExplorerTool */
var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html');
ParticleExplorerTool = function() {
ParticleExplorerTool = function(createToolsWindow) {
var that = {};
that.activeParticleEntity = 0;
that.updatedActiveParticleProperties = {};
@ -24,8 +23,15 @@ ParticleExplorerTool = function() {
that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
that.webView.setVisible = function(value) {};
that.webView.webEventReceived.connect(that.webEventReceived);
createToolsWindow.webEventReceived.addListener(this, that.webEventReceived);
};
function emitScriptEvent(data) {
var messageData = JSON.stringify(data);
that.webView.emitScriptEvent(messageData);
createToolsWindow.emitScriptEvent(messageData);
}
that.destroyWebView = function() {
if (!that.webView) {
return;
@ -33,17 +39,16 @@ ParticleExplorerTool = function() {
that.activeParticleEntity = 0;
that.updatedActiveParticleProperties = {};
var messageData = {
emitScriptEvent({
messageType: "particle_close"
};
that.webView.emitScriptEvent(JSON.stringify(messageData));
});
};
function sendParticleProperties(properties) {
that.webView.emitScriptEvent(JSON.stringify({
emitScriptEvent({
messageType: "particle_settings",
currentProperties: properties
}));
});
}
function sendActiveParticleProperties() {