working UI
|
@ -0,0 +1,7 @@
|
|||
Image {
|
||||
width: 36
|
||||
height: 36
|
||||
anchors.centerIn: parent
|
||||
source: emojiBaseURL + EmojiList.emojiList[0].code[0] + ".png"
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
162
interface/resources/qml/hifi/simplifiedUI/emojiApp/EmojiApp.qml
Normal file
|
@ -0,0 +1,162 @@
|
|||
//
|
||||
// EmojiApp.qml
|
||||
//
|
||||
// Created by Milad Nazeri on 2019-08-03
|
||||
// Copyright 2019 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.10
|
||||
import QtQuick.Controls 2.3
|
||||
import "../simplifiedConstants" as SimplifiedConstants
|
||||
import "../simplifiedControls" as SimplifiedControls
|
||||
import stylesUit 1.0 as HifiStylesUit
|
||||
import "./emojiList.js" as EmojiList
|
||||
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
|
||||
import TabletScriptingInterface 1.0
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
color: simplifiedUI.colors.darkBackground
|
||||
// color: simplifiedUI.colors.darkBackground
|
||||
anchors.fill: parent
|
||||
|
||||
readonly property string emojiBaseURL: "./images/emojis/png1024/"
|
||||
readonly property string emojiSpriteBaseURL: "./images/emojis/"
|
||||
property string currentCode: ""
|
||||
property bool emojiRunning: false
|
||||
|
||||
onCurrentCodeChanged: {
|
||||
console.log("CURRENT CODE IS BEING CHANGED");
|
||||
mainEmojiImage.source = emojiBaseURL + currentCode + ".png";
|
||||
console.log("new main emoji image source:", mainEmojiImage.source);
|
||||
}
|
||||
|
||||
SimplifiedConstants.SimplifiedConstants {
|
||||
id: simplifiedUI
|
||||
}
|
||||
|
||||
focus: true
|
||||
|
||||
ListModel {
|
||||
id: mainModel
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.forceActiveFocus();
|
||||
EmojiList.emojiList
|
||||
.filter( emoji => {
|
||||
return emoji.mainCategory === "Smileys & Emotion" ||
|
||||
emoji.mainCategory === "People & Body" ||
|
||||
emoji.mainCategory === "Animals & Nature" ||
|
||||
emoji.mainCategory === "Food & Drink";
|
||||
})
|
||||
.forEach(function(item, index){
|
||||
item.code = { utf: item.code[0] }
|
||||
item.keywords = { keywords: item.keywords }
|
||||
mainModel.append(item);
|
||||
});
|
||||
root.currentCode = mainModel.get(0).code.utf;
|
||||
console.log("CURRENT CODE", root.currentCode);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: emojiIndicatorContainer
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 200
|
||||
clip: true
|
||||
color: simplifiedUI.colors.darkBackground
|
||||
|
||||
Image {
|
||||
id: mainEmojiImage
|
||||
width: 180
|
||||
height: 180
|
||||
anchors.centerIn: parent
|
||||
source: emojiBaseURL + root.currentCode + ".png"
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: emojiIconListContainer
|
||||
anchors.top: emojiIndicatorContainer.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 415
|
||||
clip: true
|
||||
color: simplifiedUI.colors.darkBackground
|
||||
// color: "#DDFF33"
|
||||
|
||||
// TODO: Fix the margin in the emoji, you can tell from the highlight
|
||||
Component {
|
||||
id: emojiDelegate
|
||||
Item {
|
||||
width: grid.cellWidth; height: grid.cellHeight
|
||||
Column {
|
||||
Rectangle {
|
||||
id: imageContainer
|
||||
// anchors.centerIn: emojiDelegate.centerIn
|
||||
clip: true;
|
||||
z:1
|
||||
width: 36
|
||||
height: 36
|
||||
x: 2
|
||||
y: -2
|
||||
color: Qt.rgba(1, 1, 1, 0.0)
|
||||
Image {
|
||||
source: emojiSpriteBaseURL + normal.source
|
||||
z: 2
|
||||
fillMode: Image.Pad
|
||||
x: -normal.frame.x
|
||||
y: -normal.frame.y
|
||||
MouseArea {
|
||||
hoverEnabled: enabled
|
||||
anchors.fill: parent
|
||||
onEntered: {
|
||||
// Tablet.playSound(TabletEnums.ButtonClick);
|
||||
grid.currentIndex = index
|
||||
console.log("model.get(grid.currentIndex).code.utf", mainModel.get(index).code.utf)
|
||||
root.currentCode = mainModel.get(index).code.utf;
|
||||
}
|
||||
onClicked: {
|
||||
console.log("GOT THE CLICK on emoji");
|
||||
sendToScript({
|
||||
"source": "EmoteAppBar.qml",
|
||||
"method": "selectedEmoji",
|
||||
"code": code.utf
|
||||
});
|
||||
}
|
||||
onExited: {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: grid
|
||||
anchors.fill: parent
|
||||
anchors.centerIn: parent
|
||||
anchors.leftMargin: 0
|
||||
anchors.rightMargin: 0
|
||||
width: 480
|
||||
height: 415
|
||||
cellWidth: 40
|
||||
cellHeight: 40
|
||||
model: mainModel
|
||||
delegate: emojiDelegate
|
||||
focus: true
|
||||
highlight: Rectangle { color: Qt.rgba(1, 1, 1, 0.4); radius: 0 }
|
||||
}
|
||||
}
|
||||
signal sendToScript(var message);
|
||||
|
||||
}
|
96633
interface/resources/qml/hifi/simplifiedUI/emojiApp/emojiList.js
Normal file
4
interface/resources/qml/hifi/simplifiedUI/emojiApp/images/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
emojis/png1024
|
||||
emojis/svg-original
|
||||
emojis/svg-variations
|
||||
emojis/svgFiltered
|
After Width: | Height: | Size: 343 KiB |
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" preserveAspectRatio="xMidYMid" viewBox="0 0 63 78.75" version="1.1" x="0px" y="0px"><defs><style>
|
||||
|
||||
.cls-3 {
|
||||
fill: #000000;
|
||||
}
|
||||
</style></defs><path style="" d="M 31.160156 0.01171875 C 13.985156 0.01171875 0.013671875 13.987016 0.013671875 31.166016 C 0.013671875 48.344016 13.985156 62.320312 31.160156 62.320312 C 48.334156 62.320312 62.306641 48.344016 62.306641 31.166016 C 62.306641 13.987016 48.334156 0.01171875 31.160156 0.01171875 z M 31.160156 3.0117188 C 46.681156 3.0117188 59.306641 15.642016 59.306641 31.166016 C 59.306641 46.690016 46.681156 59.320312 31.160156 59.320312 C 15.640156 59.320312 3.0136719 46.690016 3.0136719 31.166016 C 3.0136719 15.642016 15.640156 3.0117187 31.160156 3.0117188 z M 21.660156 18.306641 C 19.951281 18.286766 18.248188 18.902203 16.992188 20.158203 C 16.406188 20.744203 16.406188 21.69525 16.992188 22.28125 C 17.578187 22.86725 18.527281 22.86725 19.113281 22.28125 C 20.477281 20.91825 22.826047 20.969625 24.248047 22.390625 C 24.541047 22.683625 24.926547 22.830078 25.310547 22.830078 C 25.693547 22.830078 26.076141 22.683625 26.369141 22.390625 C 26.955141 21.805625 26.955141 20.854531 26.369141 20.269531 C 25.083641 18.983031 23.369031 18.326516 21.660156 18.306641 z M 42.0625 18.306641 C 40.353625 18.286766 38.652484 18.902203 37.396484 20.158203 C 36.811484 20.744203 36.811484 21.69525 37.396484 22.28125 C 37.982484 22.86725 38.932578 22.86725 39.517578 22.28125 C 40.881578 20.91825 43.230344 20.969625 44.652344 22.390625 C 44.945344 22.683625 45.330844 22.830078 45.714844 22.830078 C 46.098844 22.830078 46.480438 22.683625 46.773438 22.390625 C 47.359438 21.805625 47.359437 20.854531 46.773438 20.269531 C 45.487938 18.983031 43.771375 18.326516 42.0625 18.306641 z M 18.857422 34.193359 C 18.029422 34.193359 17.357422 34.865359 17.357422 35.693359 C 17.357422 42.478359 22.758344 47.792969 29.652344 47.792969 L 35.052734 47.792969 C 41.946734 47.792969 47.347656 42.566531 47.347656 35.894531 C 47.347656 35.065531 46.675656 34.394531 45.847656 34.394531 C 45.018656 34.394531 44.347656 35.065531 44.347656 35.894531 C 44.347656 40.883531 40.265734 44.791016 35.052734 44.791016 L 29.652344 44.791016 C 24.439344 44.791016 20.357422 40.795359 20.357422 35.693359 C 20.357422 34.865359 19.685422 34.193359 18.857422 34.193359 z " fill="#000000" fill-rule="evenodd"/><text x="0" y="78" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by Shital Patel</text><text x="0" y="83" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg>
|
After Width: | Height: | Size: 3 KiB |
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="Layer_1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 63 78.8"
|
||||
style="enable-background:new 0 0 63 78.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{font-family:'Arial-BoldMT';}
|
||||
.st3{font-size:5px;}
|
||||
</style>
|
||||
<path class="st0" d="M31.2,0C14,0,0,14,0,31.2c0,17.2,14,31.2,31.1,31.2c17.2,0,31.1-14,31.1-31.2C62.3,14,48.3,0,31.2,0z M31.2,3
|
||||
c15.5,0,28.1,12.6,28.1,28.2S46.7,59.3,31.2,59.3C15.6,59.3,3,46.7,3,31.2S15.6,3,31.2,3z M21.7,18.3c-1.7,0-3.4,0.6-4.7,1.9
|
||||
c-0.6,0.6-0.6,1.5,0,2.1s1.5,0.6,2.1,0c1.4-1.4,3.7-1.3,5.1,0.1c0.3,0.3,0.7,0.4,1.1,0.4c0.4,0,0.8-0.1,1.1-0.4
|
||||
c0.6-0.6,0.6-1.5,0-2.1C25.1,19,23.4,18.3,21.7,18.3z M42.1,18.3c-1.7,0-3.4,0.6-4.7,1.9c-0.6,0.6-0.6,1.5,0,2.1
|
||||
c0.6,0.6,1.5,0.6,2.1,0c1.4-1.4,3.7-1.3,5.1,0.1c0.3,0.3,0.7,0.4,1.1,0.4s0.8-0.1,1.1-0.4c0.6-0.6,0.6-1.5,0-2.1
|
||||
C45.5,19,43.8,18.3,42.1,18.3z M18.9,34.2c-0.8,0-1.5,0.7-1.5,1.5c0,6.8,5.4,12.1,12.3,12.1h5.4c6.9,0,12.3-5.2,12.3-11.9
|
||||
c0-0.8-0.7-1.5-1.5-1.5c-0.8,0-1.5,0.7-1.5,1.5c0,5-4.1,8.9-9.3,8.9h-5.4c-5.2,0-9.3-4-9.3-9.1C20.4,34.9,19.7,34.2,18.9,34.2z"/>
|
||||
<text transform="matrix(1 0 0 1 0 78)" class="st1 st2 st3">Created by Shital Patel</text>
|
||||
<text transform="matrix(1 0 0 1 0 83)" class="st1 st2 st3">from the Noun Project</text>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 5.8 KiB |
|
@ -0,0 +1 @@
|
|||
<svg width="72px" height="72px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="lds-rolling" style="background: none;"><circle cx="50" cy="50" fill="none" ng-attr-stroke="{{config.color}}" ng-attr-stroke-width="{{config.width}}" ng-attr-r="{{config.radius}}" ng-attr-stroke-dasharray="{{config.dasharray}}" stroke="#00b4ef" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138" transform="rotate(239.933 50 50)"><animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animateTransform></circle></svg>
|
After Width: | Height: | Size: 685 B |
|
@ -24,6 +24,9 @@ Rectangle {
|
|||
property int hoveredWidth: 480
|
||||
property int requestedWidth
|
||||
|
||||
property bool overEmoteButton: false
|
||||
property bool overEmojiButton: false
|
||||
|
||||
onRequestedWidthChanged: {
|
||||
root.requestNewWidth(root.requestedWidth);
|
||||
}
|
||||
|
@ -38,16 +41,24 @@ Rectangle {
|
|||
}
|
||||
|
||||
MouseArea {
|
||||
id: emoteMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: enabled
|
||||
propagateComposedEvents: true;
|
||||
onEntered: {
|
||||
console.log("in mouseArea 1");
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
root.requestedWidth = root.hoveredWidth;
|
||||
emojiMouseArea.hoverEnabled = true;
|
||||
root.overEmoteButton = true;
|
||||
}
|
||||
onExited: {
|
||||
console.log("MOUSE 1 EXIT");
|
||||
root.overEmoteButton = false;
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
root.requestedWidth = root.originalWidth;
|
||||
}
|
||||
z: 2
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -69,6 +80,7 @@ Rectangle {
|
|||
size: 26
|
||||
color: simplifiedUI.colors.text.almostWhite
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -87,6 +99,48 @@ Rectangle {
|
|||
size: 20
|
||||
color: simplifiedUI.colors.text.black
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: emojiButtonBackground
|
||||
z: 3
|
||||
width: root.originalWidth
|
||||
height: parent.height
|
||||
anchors.right: parent.right
|
||||
color: simplifiedUI.colors.darkBackground
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
id: emojiButton
|
||||
text: "😊"
|
||||
z: 3
|
||||
anchors.fill: parent
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: 1
|
||||
anchors.verticalCenterOffset: -2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
size: 26
|
||||
color: simplifiedUI.colors.text.almostWhite
|
||||
}
|
||||
MouseArea {
|
||||
id: emojiMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: false
|
||||
propagateComposedEvents: true;
|
||||
z: 4
|
||||
onClicked: {
|
||||
console.log("GOT THE CLICK");
|
||||
sendToScript({
|
||||
"source": "EmoteAppBar.qml",
|
||||
"method": "toggleEmojiApp"
|
||||
});
|
||||
}
|
||||
onEntered: {
|
||||
console.log("OVER EMOJI BUTTON");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function fromScript(message) {
|
||||
|
|
463
scripts/simplifiedUI/emojiApp/avimoji_app.js
Normal file
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
|
||||
Avimoji
|
||||
avimoji_app.js
|
||||
Created by Milad Nazeri on 2019-04-25
|
||||
Copyright 2019 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 () {
|
||||
|
||||
// *************************************
|
||||
// START dependencies
|
||||
// *************************************
|
||||
// #region dependencies
|
||||
|
||||
// Custom module for handling entities
|
||||
var EntityMaker = Script.require("./resources/modules/entityMaker.js?" + Date.now());
|
||||
// Add nice smoothing functions to the animations
|
||||
var EasingFunctions = Script.require("./resources/modules/easing.js");
|
||||
|
||||
// The information needed to properly use the sprite sheets and get the general information
|
||||
// about the emojis
|
||||
var emojiList = Script.require("./resources/node/emojiList.json?" + Date.now());
|
||||
// The location to find where the individual emoji icons are
|
||||
var CONFIG = Script.require("./resources/config.json?" + Date.now());
|
||||
// Where to load the images from taken from the Config above
|
||||
var imageURLBase = CONFIG.baseImagesURL;
|
||||
|
||||
// #endregion
|
||||
// *************************************
|
||||
// END dependencies
|
||||
// *************************************
|
||||
|
||||
// *************************************
|
||||
// START utility
|
||||
// *************************************
|
||||
// #region utility
|
||||
|
||||
|
||||
// CONSTS
|
||||
// UTF-Codes are stored on the first index of one of the keys on a returned emoji object.
|
||||
// Just makes the code a little easier to read
|
||||
var UTF_CODE = 0;
|
||||
|
||||
// Make the emoji groups
|
||||
var MAX_PER_GROUP = 250;
|
||||
var emojiChunks = [];
|
||||
function makeEmojiChunks() {
|
||||
for (var i = 0, len = emojiList.length; i < len; i += MAX_PER_GROUP) {
|
||||
emojiChunks.push(emojiList.slice(i, i + MAX_PER_GROUP));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Play sound borrowed from bingo app:
|
||||
// Plays the specified sound at the specified position, volume, and localOnly
|
||||
// Only plays a sound if it is downloaded.
|
||||
// Only plays one sound at a time.
|
||||
var soundUrl = Script.resolvePath('./resources/sounds/emojiPopSound.wav');
|
||||
var popSound = SoundCache.getSound(soundUrl);
|
||||
var injector;
|
||||
var DEFAULT_VOLUME = 0.0003;
|
||||
var local = false;
|
||||
function playSound(sound, volume, position, localOnly) {
|
||||
sound = sound || popSound;
|
||||
volume = volume || DEFAULT_VOLUME;
|
||||
position = position || MyAvatar.position;
|
||||
localOnly = localOnly || local;
|
||||
if (sound.downloaded) {
|
||||
if (injector) {
|
||||
injector.stop();
|
||||
}
|
||||
injector = Audio.playSound(sound, {
|
||||
position: position,
|
||||
volume: volume,
|
||||
localOnly: localOnly
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// prune old emojis on you
|
||||
function pruneOldAvimojis() {
|
||||
MyAvatar.getAvatarEntitiesVariant()
|
||||
.forEach(function (avatarEntity) {
|
||||
if (avatarEntity && avatarEntity.properties.name.toLowerCase().indexOf("avimoji") > -1) {
|
||||
Entities.deleteEntity(avatarEntity.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// help send an emoji chunk to load up faster in the tablet
|
||||
var INTERVAL = 200;
|
||||
var currentChunk = 0;
|
||||
function sendEmojiChunks() {
|
||||
if (currentChunk >= emojiChunks.length) {
|
||||
currentChunk = 0;
|
||||
return;
|
||||
} else {
|
||||
var chunk = emojiChunks[currentChunk];
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "sendChunk",
|
||||
chunkNumber: currentChunk,
|
||||
totalChunks: emojiChunks.length,
|
||||
chunk: chunk
|
||||
});
|
||||
currentChunk++;
|
||||
Script.setTimeout(function () {
|
||||
sendEmojiChunks();
|
||||
}, INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #endregion
|
||||
// *************************************
|
||||
// END utility
|
||||
// *************************************
|
||||
|
||||
// *************************************
|
||||
// START ui_handlers
|
||||
// *************************************
|
||||
// #region ui_handlers
|
||||
|
||||
|
||||
// what to do when someone selects an emoji in the tablet
|
||||
var selectedEmoji = null;
|
||||
var lastEmoji = null;
|
||||
function handleSelectedEmoji(data) {
|
||||
var emoji = data.emoji;
|
||||
if (selectedEmoji && selectedEmoji.code[UTF_CODE] === emoji.code[UTF_CODE]) {
|
||||
maybePlayPop("off");
|
||||
selectedEmoji = null;
|
||||
return;
|
||||
} else {
|
||||
selectedEmoji = emoji;
|
||||
lastEmoji = emoji;
|
||||
addEmoji(selectedEmoji);
|
||||
}
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateEmojiPicks",
|
||||
selectedEmoji: selectedEmoji
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// handle what happens when unselect an emoji
|
||||
function handleSelectedRemoved() {
|
||||
maybePlayPop("off");
|
||||
selectedEmoji = null;
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateEmojiPicks",
|
||||
selectedEmoji: selectedEmoji
|
||||
});
|
||||
}
|
||||
|
||||
function onDomainChanged() {
|
||||
pruneOldAvimojis();
|
||||
maybeClearPop();
|
||||
if (currentEmoji && currentEmoji.id) {
|
||||
currentEmoji.destroy();
|
||||
selectedEmoji = null;
|
||||
}
|
||||
maybeClearPop();
|
||||
if (ui.isOpen) {
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #endregion
|
||||
// *************************************
|
||||
// END ui_handlers
|
||||
// *************************************
|
||||
|
||||
|
||||
|
||||
// *************************************
|
||||
// START avimoji
|
||||
// *************************************
|
||||
// #region avimoji
|
||||
|
||||
|
||||
// what happens when we need to add an emoji over a user
|
||||
var billboardMode = "full";
|
||||
var currentSelectedDimensions = null;
|
||||
function addEmoji(emoji) {
|
||||
if (currentEmoji && currentEmoji.id) {
|
||||
maybePlayPop("offThenOn");
|
||||
} else {
|
||||
createEmoji(emoji);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// creating the actual emoji that isn't an animation
|
||||
var ABOVE_HEAD = 0.60;
|
||||
var EMOJI_CONST_SCALER = 0.27;
|
||||
var currentEmoji = new EntityMaker("avatar");
|
||||
var EMOJI_X_OFFSET = -0.01;
|
||||
var DEFAULT_EMOJI_SIZE = 0.27;
|
||||
var emojiSize = Settings.getValue("avimoji/emojiSize", DEFAULT_EMOJI_SIZE);
|
||||
function createEmoji(emoji) {
|
||||
var neckPosition, avatarScale, aboveNeck, emojiPosition;
|
||||
avatarScale = MyAvatar.scale;
|
||||
aboveNeck = ABOVE_HEAD;
|
||||
neckPosition = Vec3.subtract(MyAvatar.getNeckPosition(), MyAvatar.position);
|
||||
emojiPosition = Vec3.sum(
|
||||
neckPosition,
|
||||
[
|
||||
EMOJI_X_OFFSET,
|
||||
avatarScale * aboveNeck * (1 + emojiSize * EMOJI_CONST_SCALER),
|
||||
0
|
||||
]);
|
||||
var IMAGE_SIZE = avatarScale * emojiSize;
|
||||
var dimensions = { x: IMAGE_SIZE, y: IMAGE_SIZE, z: IMAGE_SIZE };
|
||||
currentSelectedDimensions = dimensions;
|
||||
|
||||
var parentID = MyAvatar.sessionUUID;
|
||||
var imageURL = imageURLBase + emoji.code[UTF_CODE] + ".png";
|
||||
currentEmoji
|
||||
.add('type', "Image")
|
||||
.add('name', 'AVIMOJI')
|
||||
.add('localPosition', emojiPosition)
|
||||
.add('dimensions', [0, 0, 0])
|
||||
.add('parentID', parentID)
|
||||
.add('emissive', true)
|
||||
.add('collisionless', true)
|
||||
.add('imageURL', imageURL)
|
||||
.add('billboardMode', billboardMode)
|
||||
.add('ignorePickIntersection', true)
|
||||
.add('alpha', 1)
|
||||
.add('grab', {grabbable: false})
|
||||
.create();
|
||||
maybePlayPop("on");
|
||||
}
|
||||
|
||||
|
||||
// #endregion
|
||||
// *************************************
|
||||
// END avimoji
|
||||
// *************************************
|
||||
|
||||
// *************************************
|
||||
// START animation
|
||||
// *************************************
|
||||
// #region animation
|
||||
|
||||
|
||||
// see what we need to do when an emoji gets clicked
|
||||
var DURATION = POP_ANIMATION_DURATION_MS + 100;
|
||||
function maybePlayPop(type) {
|
||||
maybeClearPop();
|
||||
popType = type;
|
||||
playPopInterval = Script.setInterval(playPopAnimation, POP_DURATION_PER_STEP);
|
||||
}
|
||||
|
||||
|
||||
// maybe clear a pop up animation not in animation mode
|
||||
function maybeClearPop() {
|
||||
if (playPopInterval) {
|
||||
Script.clearTimeout(playPopInterval);
|
||||
playPopInterval = null;
|
||||
currentPopScale = null;
|
||||
currentPopStep = 1;
|
||||
isPopPlaying = false;
|
||||
popType = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// play an animation pop in
|
||||
var currentPopStep = 1;
|
||||
var POP_ANIMATION_DURATION_MS = 170;
|
||||
var POP_ANIMATION_STEPS = 10;
|
||||
var POP_DURATION_PER_STEP = POP_ANIMATION_DURATION_MS / POP_ANIMATION_STEPS;
|
||||
var currentPopScale = null;
|
||||
var MAX_POP_SCALE = 1;
|
||||
var MIN_POP_SCALE = 0.0;
|
||||
var POP_SCALE_DISTANCE = MAX_POP_SCALE - MIN_POP_SCALE;
|
||||
var POP_PER_STEP = POP_SCALE_DISTANCE / POP_ANIMATION_STEPS;
|
||||
var isPopPlaying = false;
|
||||
var popType = null;
|
||||
var playPopInterval = null;
|
||||
function playPopAnimation() {
|
||||
var dimensions;
|
||||
|
||||
// Handle if this is the first step of the animation
|
||||
|
||||
if (currentPopStep === 1) {
|
||||
isPopPlaying = true;
|
||||
if (popType === "in") {
|
||||
currentPopScale = MIN_POP_SCALE;
|
||||
} else {
|
||||
currentPopScale = MAX_POP_SCALE;
|
||||
playSound();
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the animation step
|
||||
|
||||
if (popType === "in") {
|
||||
currentPopScale += POP_PER_STEP;
|
||||
dimensions = Vec3.multiply(currentSelectedDimensions, EasingFunctions.easeInCubic(currentPopScale));
|
||||
} else {
|
||||
currentPopScale -= POP_PER_STEP;
|
||||
dimensions = Vec3.multiply(currentSelectedDimensions, EasingFunctions.easeOutCubic(currentPopScale));
|
||||
}
|
||||
currentPopStep++;
|
||||
currentEmoji.edit("dimensions", dimensions);
|
||||
|
||||
// Handle if it's the end of the animation step
|
||||
|
||||
if (currentPopStep === POP_ANIMATION_STEPS) {
|
||||
if (popType === "in") {
|
||||
playSound();
|
||||
} else {
|
||||
if (currentEmoji && currentEmoji.id) {
|
||||
currentEmoji.destroy();
|
||||
currentEmoji = new EntityMaker("avatar");
|
||||
if (popType === "out") {
|
||||
selectedEmoji = null;
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateEmojiPicks",
|
||||
selectedEmoji: selectedEmoji
|
||||
});
|
||||
}
|
||||
if (popType === "offThenOn") {
|
||||
Script.setTimeout(function () {
|
||||
createEmoji(selectedEmoji);
|
||||
}, DURATION);
|
||||
}
|
||||
}
|
||||
}
|
||||
maybeClearPop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #endregion
|
||||
// *************************************
|
||||
// END animation
|
||||
// *************************************
|
||||
|
||||
// *************************************
|
||||
// START messages
|
||||
// *************************************
|
||||
// #region messages
|
||||
|
||||
|
||||
// handle getting a message from the tablet
|
||||
function onMessage(message) {
|
||||
if (message.app !== "avimoji") {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.method) {
|
||||
case "eventBridgeReady":
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateUI",
|
||||
selectedEmoji: selectedEmoji
|
||||
});
|
||||
sendEmojiChunks();
|
||||
break;
|
||||
|
||||
case "handleSelectedEmoji":
|
||||
handleSelectedEmoji(message.data);
|
||||
break;
|
||||
|
||||
case "handleSelectedRemoved":
|
||||
handleSelectedRemoved();
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("Unhandled message from avimoji_ui.js", message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #endregion
|
||||
// *************************************
|
||||
// END messages
|
||||
// *************************************
|
||||
|
||||
// *************************************
|
||||
// START main
|
||||
// *************************************
|
||||
// #region main
|
||||
|
||||
|
||||
// startup the app
|
||||
var BUTTON_NAME = "AVIMOJI";
|
||||
var APP_UI_URL = Script.resolvePath('./resources/avimoji_ui.html');
|
||||
var AppUI = Script.require('./resources/modules/appUi.js?' + Date.now());
|
||||
var ui;
|
||||
var emojiCodeMap;
|
||||
function startup() {
|
||||
// make a map of just the utf codes to help with accesing
|
||||
emojiCodeMap = emojiList.reduce(function (previous, current, index) {
|
||||
if (current && current.code && current.code.length > 0 && current.code[UTF_CODE]) {
|
||||
previous[current.code[UTF_CODE]] = index;
|
||||
return previous;
|
||||
}
|
||||
}, {});
|
||||
ui = new AppUI({
|
||||
buttonName: BUTTON_NAME,
|
||||
home: APP_UI_URL,
|
||||
onMessage: onMessage,
|
||||
graphicsDirectory: Script.resolvePath("./resources/images/icons/")
|
||||
});
|
||||
|
||||
pruneOldAvimojis();
|
||||
makeEmojiChunks();
|
||||
|
||||
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
}
|
||||
|
||||
|
||||
startup();
|
||||
|
||||
|
||||
// #endregion
|
||||
// *************************************
|
||||
// END main
|
||||
// *************************************
|
||||
|
||||
// *************************************
|
||||
// START cleanup
|
||||
// *************************************
|
||||
// #region cleanup
|
||||
|
||||
|
||||
function scriptEnding() {
|
||||
if (ui.isOpen) {
|
||||
ui.onClosed();
|
||||
}
|
||||
pruneOldAvimojis();
|
||||
Window.domainChanged.disconnect(onDomainChanged);
|
||||
if (currentEmoji && currentEmoji.id) {
|
||||
currentEmoji.destroy();
|
||||
}
|
||||
maybeClearPop();
|
||||
}
|
||||
|
||||
|
||||
// #endregion
|
||||
// *************************************
|
||||
// END cleanup
|
||||
// *************************************
|
||||
|
||||
})();
|
736
scripts/simplifiedUI/emojiApp/avimoji_savedCode.js
Normal file
|
@ -0,0 +1,736 @@
|
|||
/*
|
||||
- Built with same tech as settings screen. Use SettingsApp.qml as template to create the app page
|
||||
- On hover or select, each emoji icon highlights. Add function for on hover to swap inactive icon with active
|
||||
- Has scrolling field of emoji icons to choose from called emoji icon list. Add rows of emoji icons
|
||||
- On hover or select, each emoji icon highlights. Add function for on hover to swap inactive icon with active
|
||||
- Has emoji indicator at top to show current emoji icon selection
|
||||
- Emoji indicator will initially be empty
|
||||
- When user clicks emoji icon, an entity image of the selected emoji appears over their avatar's head.
|
||||
- Create a function called 'addEmoji'. The 'addEmoji' fn will call 'createEmoji' and several other functions.
|
||||
- In 'createEmoji' use entity API to add an avatar entity parented to head or fall back to neck if no head joint, hips if no neck joint.
|
||||
- A 5 second timer begins Set 5 second timeout in 'addEmoji' function
|
||||
- After 5 seconds, the emoji entity image shrinks and disappears over an 2 second interval When the timeout is up, start an interval for every 200ms to shrink the entity icon. After 10 intervals entity icon should be very small. Delete it and clear the interval by calling 'removeEmoji'.
|
||||
- The emoji indicator and emote indicator display the time remaining until the entity icon will disappear as a highlighted pie section of the circular emoji image. Use a QML pieSeries over the emoji and emote indicators. The image for the pieSeries can be a 10% opacity white overlaycircle. Update the values of the pie slices on the 200ms interval to show the time left before the emoji disappears.
|
||||
- The emote indicator is replaced with the current emoji icon (with timer display) while the emoji entity image appears over the user's head. In the 'addEmoji' function call a function that swaps the icon on the emote indicator.
|
||||
- After the 7 seconds, the emoji entity image has disappeared
|
||||
- The emote indicator returns to default. In 'removeEmoji', restore the default image for the emote indicator.
|
||||
- The emoji indicator returns to empty. In 'removeEmoji', restore the default image for the emoji indicator.
|
||||
- Emoji icon highlight is removed if not hovered. In 'removeEmoji', restore the default icon for the emoji that just ended.
|
||||
*/
|
||||
// NOTE: play Last emoji from keypress
|
||||
// Open up on ctrl + enter
|
||||
var ENTER_KEY = 16777220;
|
||||
var SEMI_COLON_KEY = 59;
|
||||
function keyPress(event) {
|
||||
if (event.key === ENTER_KEY && event.isControl) {
|
||||
if (ui.isOpen) {
|
||||
ui.close();
|
||||
} else {
|
||||
ui.open();
|
||||
}
|
||||
} if (event.key === SEMI_COLON_KEY) {
|
||||
playEmojiAgain();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// NOTE: turn the favorites object into an array of top 10 favorites
|
||||
var MAX_FAVORITES = 10;
|
||||
function makeFavoritesArray() {
|
||||
var i = 0, favoritesArray = [];
|
||||
for (var emoji in favorites ) {
|
||||
favoritesArray[i++] = favorites[emoji];
|
||||
}
|
||||
|
||||
favoritesArray.sort(function (a, b) {
|
||||
if (a.count > b.count) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.count < b.count) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
favoritesArray = favoritesArray.slice(0, MAX_FAVORITES);
|
||||
return favoritesArray;
|
||||
}
|
||||
|
||||
// NOTE: grab an emoji in a looping ring
|
||||
function findValue(index, array, offset) {
|
||||
offset = offset || 0;
|
||||
return array[(index + offset) % array.length];
|
||||
}
|
||||
// NOTE: Wear emoji like a mask
|
||||
// choose to wear a mask or above their head
|
||||
var mask = Settings.getValue("avimoji/mask", false);
|
||||
function handleMask(data) {
|
||||
mask = data.mask;
|
||||
if (currentEmoji && currentEmoji.id) {
|
||||
addEmojiToUser(selectedEmoji);
|
||||
}
|
||||
if (mask) {
|
||||
shouldTimeoutDelete = false;
|
||||
} else {
|
||||
shouldTimeoutDelete = true;
|
||||
}
|
||||
Settings.setValue("avimoji/mask", mask);
|
||||
Settings.setValue("avimoji/shouldDefaultDelete", shouldTimeoutDelete);
|
||||
}
|
||||
// NOTE: Render the emojis locally
|
||||
// don't render the emojis for anyone else
|
||||
var local = Settings.getValue("avimoji/local", false);
|
||||
var entityType = Settings.getValue("avimoji/entityType", "avatar");
|
||||
function handleLocal(data) {
|
||||
local = data.local;
|
||||
Settings.setValue("avimoji/local", local);
|
||||
|
||||
if (local) {
|
||||
entityType = "local";
|
||||
Settings.setValue("avimoji/entityType", "local");
|
||||
} else {
|
||||
entityType = "avatar";
|
||||
Settings.setValue("avimoji/entityType", "avatar");
|
||||
}
|
||||
|
||||
if (!advanced && currentEmoji && currentEmoji.id) {
|
||||
addEmojiToUser(selectedEmoji);
|
||||
}
|
||||
|
||||
maybeRedrawAnimation();
|
||||
}
|
||||
// NOTE: Favorite emojis shown as overlays
|
||||
// show the favorite emojis as overlays
|
||||
var ezFavorites = Settings.getValue("avimoji/ezFavorites", false);
|
||||
function handleEZFavorites(data) {
|
||||
ezFavorites = data.ezFavorites;
|
||||
log("EZ FAVORITES", ezFavorites, PRINT);
|
||||
Settings.setValue("avimoji/ezFavorites", ezFavorites);
|
||||
if (ezFavorites) {
|
||||
maybeClearEZFavoritesTimer();
|
||||
renderEZFavoritesOverlays();
|
||||
} else {
|
||||
deleteEZFavoritesOverlays();
|
||||
maybeClearEZFavoritesTimer();
|
||||
}
|
||||
}
|
||||
// NOTE: Handle playing emojis in sequence
|
||||
// handle moving to sequence mode
|
||||
var sequenceMode = Settings.getValue("avimoji/sequenceMode", false);
|
||||
function handleSequenceMode(data) {
|
||||
sequenceMode = data.sequenceMode;
|
||||
Settings.setValue("avimoji/sequenceMode", sequenceMode);
|
||||
if (!sequenceMode) {
|
||||
maybeClearPlayEmojiSequenceInterval();
|
||||
}
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateEmojiPicks",
|
||||
selectedEmoji: selectedEmoji,
|
||||
emojiSequence: emojiSequence
|
||||
});
|
||||
}
|
||||
// NOTE: Original Handling of all the emojis
|
||||
// what to do when someone selects an emoji in the tablet
|
||||
var MAX_EMOJI_SEQUENCE = 40;
|
||||
var emojiSequence = Settings.getValue("avimoji/emojiSequence", []);
|
||||
var selectedEmoji = null;
|
||||
function handleSelectedEmoji(data) {
|
||||
var emoji = data.emoji;
|
||||
if (advanced) {
|
||||
if (!sequenceMode) {
|
||||
selectedEmoji = emoji;
|
||||
lastEmoji = emoji;
|
||||
maybeClearTimeoutDelete();
|
||||
addEmojiToUser(selectedEmoji);
|
||||
} else {
|
||||
emojiSequence.push(emoji);
|
||||
emojiSequence = emojiSequence.slice(0, MAX_EMOJI_SEQUENCE);
|
||||
|
||||
Settings.setValue("avimoji/emojiSequence", emojiSequence);
|
||||
}
|
||||
} else {
|
||||
if (selectedEmoji && selectedEmoji.code[0] === emoji.code[0]) {
|
||||
maybePlayPop("off");
|
||||
selectedEmoji = null;
|
||||
return;
|
||||
} else {
|
||||
selectedEmoji = emoji;
|
||||
lastEmoji = emoji;
|
||||
maybeClearTimeoutDelete();
|
||||
addEmojiToUser(selectedEmoji);
|
||||
}
|
||||
}
|
||||
addToFavorites(emoji);
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateEmojiPicks",
|
||||
selectedEmoji: selectedEmoji,
|
||||
emojiSequence: emojiSequence
|
||||
});
|
||||
}
|
||||
// NOTE: Changing the size of an emoji
|
||||
// dynamically update the emoji size
|
||||
function handleEmojiSize(data) {
|
||||
emojiSize = data.emojiSize;
|
||||
if (currentEmoji && currentEmoji.id) {
|
||||
addEmojiToUser(selectedEmoji);
|
||||
}
|
||||
Settings.setValue("avimoji/emojiSize", emojiSize);
|
||||
maybeRedrawAnimation();
|
||||
}
|
||||
// NOTE: Change the distance traveled in an emoji sequence
|
||||
// handle a user changing the start and end distance of an emoji animation
|
||||
var DEFAULT_ANIMATION_DISTANCE = 0.5;
|
||||
var animationDistance = Settings.getValue("avimoji/animationDistance", DEFAULT_ANIMATION_DISTANCE);
|
||||
function handleAnimationDistance(data) {
|
||||
animationDistance = data.animationDistance;
|
||||
Settings.setValue("avimoji/animationDistance", animationDistance);
|
||||
maybeRedrawAnimation();
|
||||
}
|
||||
// NOTE: Change the speed of emoji sequence
|
||||
// handle a user changing the speed of the emoji sequence animation
|
||||
var DEFAULT_ANIMATION_SPEED = 1.2;
|
||||
var animationSpeed = Settings.getValue("avimoji/animationSpeed", DEFAULT_ANIMATION_SPEED);
|
||||
function handleAnimationSpeed(data) {
|
||||
animationSpeed = data.animationSpeed;
|
||||
Settings.setValue("avimoji/animationDistance", animationSpeed);
|
||||
maybeRedrawAnimation();
|
||||
}
|
||||
// NOTE: Update the play state
|
||||
// Update the play state
|
||||
var isPlaying = false;
|
||||
function handleUpdateIsPlaying(data) {
|
||||
isPlaying = data.isPlaying;
|
||||
if (isPlaying) {
|
||||
playEmojiSequence();
|
||||
} else {
|
||||
stopEmojiSequence();
|
||||
}
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updatePlay",
|
||||
isPlaying: isPlaying
|
||||
});
|
||||
}
|
||||
// NOTE: Delete an Emoji from a sequence
|
||||
// remove an emoji from the sequence
|
||||
function deleteSequenceEmoji(data) {
|
||||
emojiSequence.splice(data.index, 1);
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateEmojiPicks",
|
||||
selectedEmoji: selectedEmoji,
|
||||
emojiSequence: emojiSequence
|
||||
});
|
||||
if (emojiSequence.length === 0) {
|
||||
maybeClearPlayEmojiSequenceInterval();
|
||||
}
|
||||
}
|
||||
// NOTE: Handle emoji advanced selected
|
||||
// handle if you change to move to advanced mode
|
||||
var advanced = Settings.getValue("avimoji/advanced", false);
|
||||
function handleAdvanced(data) {
|
||||
advanced = data.advanced;
|
||||
Settings.setValue("avimoji/advanced", advanced);
|
||||
if (isPlaying) {
|
||||
maybeClearPlayEmojiSequenceInterval();
|
||||
isPlaying = false;
|
||||
}
|
||||
}
|
||||
// NOTE: Reset the current emoji sequence to build a new one
|
||||
function handleResetSequenceList() {
|
||||
maybeClearPlayEmojiSequenceInterval();
|
||||
emojiSequence = [];
|
||||
Settings.setValue("avimoji/emojiSequence", emojiSequence);
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateEmojiPicks",
|
||||
selectedEmoji: selectedEmoji,
|
||||
emojiSequence: emojiSequence
|
||||
});
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updatePlay",
|
||||
isPlaying: isPlaying
|
||||
});
|
||||
}
|
||||
//
|
||||
// NOTE: Empty out the favorites object to start tracking them again
|
||||
function handleResetFavoritesList() {
|
||||
favorites = {};
|
||||
|
||||
Settings.setValue("avimoji/favorites", favorites);
|
||||
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateFavorites",
|
||||
favorites: makeFavoritesArray(favorites)
|
||||
});
|
||||
deleteEZFavoritesOverlays();
|
||||
}
|
||||
//
|
||||
// NOTE: toggles the emoji deleting by default
|
||||
var shouldTimeoutDelete = Settings.getValue("avimoji/shouldTimeoutDelete", true);
|
||||
function handleShouldTimeoutDelete(data) {
|
||||
shouldTimeoutDelete = data.shouldTimeoutDelete;
|
||||
Settings.setValue("avimoji/shouldTimeoutDelete", shouldTimeoutDelete);
|
||||
if (shouldTimeoutDelete && currentEmoji) {
|
||||
startTimeoutDelete();
|
||||
} else {
|
||||
maybeClearTimeoutDelete();
|
||||
}
|
||||
}
|
||||
//
|
||||
// NOTE: play the emoji again when we hit the ";" key
|
||||
var lastEmoji = null;
|
||||
function playEmojiAgain(){
|
||||
if (currentEmoji && currentEmoji.id) {
|
||||
maybePlayPop("offThenOn");
|
||||
} else {
|
||||
createEmoji(lastEmoji);
|
||||
}
|
||||
}
|
||||
//
|
||||
// NOTE: see if we need to clear the timeout delete that is currently there
|
||||
function maybeClearTimeoutDelete() {
|
||||
if (defaultTimeout) {
|
||||
Script.clearTimeout(defaultTimeout);
|
||||
defaultTimeout = null;
|
||||
}
|
||||
}
|
||||
//
|
||||
// NOTE: add a new emoji to your favorites list
|
||||
var favorites = Settings.getValue("avimoji/favorites", {});
|
||||
function addToFavorites(emoji) {
|
||||
if (!favorites[emoji.code[UTF_CODE]]) {
|
||||
favorites[emoji.code[UTF_CODE]] = { count: 1, code: emoji.code[UTF_CODE] };
|
||||
} else {
|
||||
favorites[emoji.code[UTF_CODE]].count++;
|
||||
}
|
||||
var newFavoritesArray = makeFavoritesArray(favorites);
|
||||
Settings.setValue("avimoji/favorites", favorites);
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateFavorites",
|
||||
favorites: newFavoritesArray
|
||||
});
|
||||
if (newFavoritesArray.length === 1 && ezFavorites) {
|
||||
var data = {ezFavorites: ezFavorites};
|
||||
handleEZFavorites(data);
|
||||
// renderEZFavoritesOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// dynammically create the above the head properties in case of resize
|
||||
var ABOVE_NECK_DEFAULT = 0.2;
|
||||
var setupAboveNeck = ABOVE_NECK_DEFAULT;
|
||||
setupAvatarScale = MyAvatar.scale;
|
||||
function setupAboveHeadAnimationProperties() {
|
||||
billboardMode = "full";
|
||||
setupNeckPosition = Vec3.subtract(MyAvatar.getNeckPosition(), MyAvatar.position);
|
||||
setupEmojiPosition1 =
|
||||
Vec3.sum(setupNeckPosition, [(END_X + START_X) / 2,
|
||||
setupAvatarScale * setupAboveNeck * (1 + emojiSize * EMOJI_CONST_SCALER), 0]);
|
||||
setupEmojiPosition1 = Vec3.sum(setupEmojiPosition1, [nextPostionXOffset, nextPostionYOffset, nextPostionZOffset]);
|
||||
setupEmojiPosition2 =
|
||||
Vec3.sum(setupNeckPosition,
|
||||
[START_X, setupAvatarScale * setupAboveNeck * (1 + emojiSize * EMOJI_CONST_SCALER), 0]);
|
||||
setupEmojiPosition2 = Vec3.sum(setupEmojiPosition2, [nextPostionXOffset, nextPostionYOffset, nextPostionZOffset]);
|
||||
}
|
||||
|
||||
|
||||
// check to see if we need to redraw an animation
|
||||
function maybeRedrawAnimation() {
|
||||
if (isPlaying) {
|
||||
setupAnimationVariables();
|
||||
maybeClearPlayEmojiSequenceInterval();
|
||||
playEmojiSequence();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// stop playing the emoji sequence
|
||||
function stopEmojiSequence() {
|
||||
maybeClearPlayEmojiSequenceInterval();
|
||||
isPlaying = false;
|
||||
currentIndex = 0;
|
||||
}
|
||||
|
||||
// play the actual emoji sequence
|
||||
var animationEmoji1 = new EntityMaker(entityType);
|
||||
var animationEmoji2 = new EntityMaker(entityType);
|
||||
var animationInitialDimensions = null;
|
||||
function playEmojiSequence(){
|
||||
setupAnimationVariables();
|
||||
if (animationEmoji1 && animationEmoji1.id) {
|
||||
animationEmoji1.destroy();
|
||||
animationEmoji1 = new EntityMaker(entityType);
|
||||
}
|
||||
if (animationEmoji2 && animationEmoji2.id) {
|
||||
animationEmoji2.destroy();
|
||||
animationEmoji2 = new EntityMaker(entityType);
|
||||
}
|
||||
|
||||
maybeClearPlayEmojiSequenceInterval();
|
||||
|
||||
if (mask) {
|
||||
setupMaskAnimationProperties();
|
||||
} else {
|
||||
setupAboveHeadAnimationProperties();
|
||||
}
|
||||
animationEmoji1.add("parentJointIndex", MyAvatar.getJointIndex("Head"));
|
||||
animationEmoji2.add("parentJointIndex", MyAvatar.getJointIndex("Head"));
|
||||
var IMAGE_SIZE = setupAvatarScale * emojiSize;
|
||||
var dimensions = { x: IMAGE_SIZE, y: IMAGE_SIZE, z: IMAGE_SIZE };
|
||||
animationInitialDimensions = dimensions;
|
||||
|
||||
var parentID = MyAvatar.sessionUUID;
|
||||
var imageURL1 = "";
|
||||
var imageURL2 = "";
|
||||
animationEmoji1
|
||||
.add('type', "Image")
|
||||
.add('name', 'AVIMOJI')
|
||||
.add('localPosition', setupEmojiPosition1)
|
||||
.add('dimensions', dimensions)
|
||||
.add('parentID', parentID)
|
||||
.add('emissive', true)
|
||||
.add('imageURL', imageURL1)
|
||||
.add('ignorePickIntersection', true)
|
||||
.add('billboard', billboardMode)
|
||||
.add('alpha', 1)
|
||||
.add('userData', "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }")
|
||||
.create(true);
|
||||
animationEmoji2
|
||||
.add('type', "Image")
|
||||
.add('name', 'AVIMOJI')
|
||||
.add('localPosition', setupEmojiPosition2)
|
||||
.add('dimensions', dimensions)
|
||||
.add('parentID', parentID)
|
||||
.add('emissive', true)
|
||||
.add('imageURL', imageURL2)
|
||||
.add('billboard', billboardMode)
|
||||
.add('ignorePickIntersection', true)
|
||||
.add('alpha', 1)
|
||||
.add('userData', "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }")
|
||||
.create(true);
|
||||
playEmojiInterval = Script.setInterval(onPlayEmojiInterval, DURATION_PER_STEP);
|
||||
onPlayEmojiInterval();
|
||||
isPlaying = true;
|
||||
}
|
||||
|
||||
|
||||
// dynamicly setup the animation properties
|
||||
var DEFAULT_ANIMATION_DURATION = 1750;
|
||||
var ANIMATION_DURATION = DEFAULT_ANIMATION_DURATION * animationSpeed;
|
||||
var HOLD_STEPS = 40;
|
||||
var ANIMATION_STEPS = 60;
|
||||
var DURATION_PER_STEP = ANIMATION_DURATION / (ANIMATION_STEPS + HOLD_STEPS * 2);
|
||||
var HALF_POINT = Math.ceil(ANIMATION_STEPS * 0.5);
|
||||
var currentStep = HALF_POINT;
|
||||
var MAX_SCALE = 1;
|
||||
var MIN_SCALE = 0.0;
|
||||
var SCALE_DISTANCE = MAX_SCALE - MIN_SCALE;
|
||||
var MAX_ALPHA = 1;
|
||||
var MIN_ALPHA = 0;
|
||||
var ALPHA_DISTANCE = MAX_ALPHA - MIN_ALPHA;
|
||||
var THRESHOLD_MIN_ALPHA = 0.001;
|
||||
var THRESHHOLD_MAX_ALPHA = 0.999;
|
||||
var DISTANCE_0_THRESHHOLD = 0.015;
|
||||
var currentScale1 = MIN_SCALE;
|
||||
var currentScale2 = MAX_SCALE;
|
||||
var currentIndex = 0;
|
||||
var playEmojiInterval = null;
|
||||
var currentAlpha1 = 1;
|
||||
var currentAlpha2 = 1;
|
||||
var middleHOLD = 0;
|
||||
var lastHOLD = 0;
|
||||
var SCALE_INCREASE_PER_STEP = SCALE_DISTANCE / HALF_POINT;
|
||||
var ALPHA_PER_STEP = ALPHA_DISTANCE / HALF_POINT;
|
||||
function setupAnimationVariables() {
|
||||
ANIMATION_DURATION = DEFAULT_ANIMATION_DURATION * animationSpeed;
|
||||
DURATION_PER_STEP = ANIMATION_DURATION / (ANIMATION_STEPS + HOLD_STEPS * 2);
|
||||
START_X = -1 * animationDistance;
|
||||
END_X = 1 * animationDistance;
|
||||
POSITION_DISTANCE = END_X - START_X;
|
||||
POSITION_PER_STEP = POSITION_DISTANCE / ANIMATION_STEPS;
|
||||
currentPosition1 = START_X;
|
||||
currentPosition2 = (END_X + START_X) / 2;
|
||||
}
|
||||
|
||||
|
||||
// interval for playing an emoji animation
|
||||
var START_X = -0.25 * animationDistance;
|
||||
var END_X = 0.25 * animationDistance;
|
||||
var POSITION_DISTANCE = END_X - START_X;
|
||||
var POSITION_PER_STEP = POSITION_DISTANCE / ANIMATION_STEPS;
|
||||
var currentPosition1 = START_X;
|
||||
var currentPosition2 = (END_X + START_X) / 2;
|
||||
var lastCurrent1Before0 = 0;
|
||||
var lastCurrent2Before0 = 0;
|
||||
function onPlayEmojiInterval() {
|
||||
var emoji, imageURL;
|
||||
if (currentStep === 1) {
|
||||
middleHOLD = 0;
|
||||
lastHOLD = 0;
|
||||
currentPosition1 = 0;
|
||||
currentScale1 = MAX_SCALE;
|
||||
currentAlpha1 = MAX_ALPHA;
|
||||
}
|
||||
|
||||
if (currentStep < HALF_POINT) {
|
||||
currentScale1 += SCALE_INCREASE_PER_STEP;
|
||||
currentScale2 -= SCALE_INCREASE_PER_STEP;
|
||||
currentAlpha1 += ALPHA_PER_STEP;
|
||||
currentAlpha2 -= ALPHA_PER_STEP;
|
||||
}
|
||||
|
||||
if (currentStep === HALF_POINT) {
|
||||
if (middleHOLD <= HOLD_STEPS) {
|
||||
middleHOLD++;
|
||||
return;
|
||||
}
|
||||
currentPosition2 = START_X;
|
||||
currentScale2 = MIN_SCALE;
|
||||
currentAlpha2 = MIN_ALPHA;
|
||||
emoji = findValue(currentIndex, emojiSequence);
|
||||
imageURL = imageURLBase + emoji.code[UTF_CODE] + ".png";
|
||||
animationEmoji2.edit("imageURL", imageURL);
|
||||
currentIndex++;
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateCurrentEmoji",
|
||||
selectedEmoji: emoji
|
||||
});
|
||||
}
|
||||
|
||||
if (currentStep === ANIMATION_STEPS) {
|
||||
if (lastHOLD <= HOLD_STEPS) {
|
||||
lastHOLD++;
|
||||
return;
|
||||
}
|
||||
currentStep = 1;
|
||||
currentPosition1 = START_X;
|
||||
currentScale1 = MIN_SCALE;
|
||||
currentAlpha1 = MIN_ALPHA;
|
||||
middleHOLD = 0;
|
||||
lastHOLD = 0;
|
||||
emoji = findValue(currentIndex, emojiSequence);
|
||||
imageURL = imageURLBase + emoji.code[UTF_CODE] + ".png";
|
||||
animationEmoji1.edit("imageURL", imageURL);
|
||||
currentIndex++;
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateCurrentEmoji",
|
||||
selectedEmoji: emoji
|
||||
});
|
||||
}
|
||||
|
||||
if (currentStep > HALF_POINT) {
|
||||
currentScale1 -= SCALE_INCREASE_PER_STEP;
|
||||
currentScale2 += SCALE_INCREASE_PER_STEP;
|
||||
currentAlpha1 -= ALPHA_PER_STEP;
|
||||
currentAlpha2 += ALPHA_PER_STEP;
|
||||
}
|
||||
|
||||
var animationEmoji1Position = animationEmoji1.get("localPosition", true);
|
||||
if (currentPosition1 === 0) {
|
||||
currentPosition1 = lastCurrent1Before0;
|
||||
}
|
||||
if (currentPosition2 === 0) {
|
||||
currentPosition2 = lastCurrent2Before0;
|
||||
}
|
||||
currentPosition1 += POSITION_PER_STEP;
|
||||
currentPosition2 += POSITION_PER_STEP;
|
||||
if (Math.abs(currentPosition1) < DISTANCE_0_THRESHHOLD) {
|
||||
lastCurrent1Before0 = currentPosition1;
|
||||
currentPosition1 = 0;
|
||||
}
|
||||
if (Math.abs(currentPosition2) < DISTANCE_0_THRESHHOLD) {
|
||||
lastCurrent2Before0 = currentPosition2;
|
||||
currentPosition2 = 0;
|
||||
}
|
||||
animationEmoji1Position.x = currentPosition1;
|
||||
var animationEmoji2Position = animationEmoji2.get("localPosition", true);
|
||||
animationEmoji2Position.x = currentPosition2;
|
||||
var emojiPosition1 = animationEmoji1Position;
|
||||
var emojiPosition2 = animationEmoji2Position;
|
||||
var newDimensions1 = Vec3.multiply(animationInitialDimensions, EasingFunctions.easeInOutQuad(currentScale1));
|
||||
var newDimensions2 = Vec3.multiply(animationInitialDimensions, EasingFunctions.easeInOutQuad(currentScale2));
|
||||
var newAlpha1 = EasingFunctions.easeInOutQuint(currentAlpha1);
|
||||
var newAlpha2 = EasingFunctions.easeInOutQuint(currentAlpha2);
|
||||
|
||||
if (newAlpha1 > THRESHHOLD_MAX_ALPHA) {
|
||||
newAlpha1 = 1.0;
|
||||
}
|
||||
|
||||
if (newAlpha1 < THRESHOLD_MIN_ALPHA) {
|
||||
newAlpha1 = 0.0;
|
||||
}
|
||||
|
||||
if (newAlpha2 > THRESHHOLD_MAX_ALPHA) {
|
||||
newAlpha2 = 1.0;
|
||||
}
|
||||
|
||||
if (newAlpha2 < THRESHOLD_MIN_ALPHA) {
|
||||
newAlpha2 = 0.0;
|
||||
}
|
||||
|
||||
animationEmoji1
|
||||
.add("localPosition", emojiPosition1)
|
||||
.add("dimensions", newDimensions1)
|
||||
.add("alpha", newAlpha1)
|
||||
.edit();
|
||||
|
||||
animationEmoji2
|
||||
.add("localPosition", emojiPosition2)
|
||||
.add("dimensions", newDimensions2)
|
||||
.add("alpha", newAlpha2)
|
||||
.edit();
|
||||
currentStep++;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Star the delete process and handle the right animation path for turning off
|
||||
var DEFAULT_TIMEOUT_MS = 7000;
|
||||
var defaultTimeout = null;
|
||||
function startTimeoutDelete() {
|
||||
defaultTimeout = Script.setTimeout(function () {
|
||||
maybePlayPop("off");
|
||||
selectedEmoji = null;
|
||||
}, DEFAULT_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// check to see if we need to clear the emoji interval
|
||||
function maybeClearPlayEmojiSequenceInterval() {
|
||||
if (animationEmoji1 && animationEmoji1.id) {
|
||||
animationEmoji1.destroy();
|
||||
}
|
||||
if (animationEmoji2 && animationEmoji2.id) {
|
||||
animationEmoji2.destroy();
|
||||
}
|
||||
if (currentEmoji && currentEmoji.id) {
|
||||
currentEmoji.destroy();
|
||||
currentEmoji = new EntityMaker("avatar");
|
||||
deleteEmojiPreviewOverlay();
|
||||
}
|
||||
if (playEmojiInterval) {
|
||||
Script.clearInterval(playEmojiInterval);
|
||||
playEmojiInterval = null;
|
||||
currentIndex = 0;
|
||||
isPlaying = false;
|
||||
}
|
||||
}
|
||||
|
||||
// custom logging function
|
||||
var PREPEND = "\n##Logger:Avimoji:Web::\n";
|
||||
var DEBUG = false;
|
||||
// var OFF = "off";
|
||||
// var ON = "on";
|
||||
// var PRINT = "PRINT";
|
||||
var PRETTY_SPACES = 4;
|
||||
function log(label, data, overrideDebug) {
|
||||
if (!DEBUG) {
|
||||
if (overrideDebug !== "PRINT") {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (overrideDebug === "off") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
data = typeof data === "undefined" ? "" : data;
|
||||
data = typeof data === "string" ? data : (JSON.stringify(data, null, PRETTY_SPACES) || "");
|
||||
data = data + " " || "";
|
||||
console.log(PREPEND + label + ": " + data + "\n");
|
||||
}
|
||||
|
||||
function playPopAnimationIn() {
|
||||
var dimensions;
|
||||
if (currentPopStep === 1) {
|
||||
isPopPlaying = true;
|
||||
currentPopScale = MIN_POP_SCALE;
|
||||
}
|
||||
currentPopScale += POP_PER_STEP;
|
||||
|
||||
dimensions = Vec3.multiply(currentSelectedDimensions, EasingFunctions.easeInCubic(currentPopScale));
|
||||
currentEmoji.edit("dimensions", dimensions);
|
||||
currentPopStep++;
|
||||
|
||||
if (currentPopStep === POP_ANIMATION_STEPS) {
|
||||
playSound();
|
||||
maybeClearPop();
|
||||
}
|
||||
}
|
||||
|
||||
// play an animation pop out
|
||||
function playPopAnimationOut() {
|
||||
var dimensions;
|
||||
if (currentPopStep === 1) {
|
||||
isPopPlaying = true;
|
||||
currentPopScale = MAX_POP_SCALE;
|
||||
playSound();
|
||||
}
|
||||
currentPopScale -= POP_PER_STEP;
|
||||
dimensions = Vec3.multiply(currentSelectedDimensions, EasingFunctions.easeOutCubic(currentPopScale));
|
||||
currentEmoji.edit("dimensions", dimensions);
|
||||
currentPopStep++;
|
||||
|
||||
if (currentPopStep === POP_ANIMATION_STEPS) {
|
||||
if (currentEmoji && currentEmoji.id) {
|
||||
// currentEmoji.destroy();
|
||||
// currentEmoji = new EntityMaker("avatar");
|
||||
selectedEmoji = null;
|
||||
ui.sendMessage({
|
||||
app: "avimoji",
|
||||
method: "updateEmojiPicks",
|
||||
selectedEmoji: selectedEmoji
|
||||
});
|
||||
}
|
||||
maybeClearPop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// play an animatino coming in and then going out
|
||||
function playPopAnimationInAndOut() {
|
||||
var dimensions;
|
||||
if (currentPopStep === 1) {
|
||||
isPopPlaying = true;
|
||||
currentPopScale = MAX_POP_SCALE;
|
||||
playSound();
|
||||
}
|
||||
|
||||
currentPopScale -= POP_PER_STEP;
|
||||
dimensions = Vec3.multiply(currentSelectedDimensions, EasingFunctions.easeOutCubic(currentPopScale));
|
||||
currentEmoji.edit("dimensions", dimensions);
|
||||
currentPopStep++;
|
||||
|
||||
if (currentPopStep === POP_ANIMATION_STEPS) {
|
||||
if (currentEmoji && currentEmoji.id) {
|
||||
currentEmoji.destroy();
|
||||
currentEmoji = new EntityMaker("avatar");
|
||||
}
|
||||
maybeClearPop();
|
||||
Script.setTimeout(function () {
|
||||
createEmoji(selectedEmoji);
|
||||
}, DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// show all of the emojis instead of just the basic set
|
||||
var allEmojis = Settings.getValue("avimoji/allEmojis", false);
|
||||
function handleAllEmojis(data) {
|
||||
allEmojis = data.allEmojis;
|
||||
Settings.setValue("avimoji/allEmojis", allEmojis);
|
||||
}
|
||||
|
||||
|
3
scripts/simplifiedUI/emojiApp/resources/config.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"baseImagesURL": "https://hifi-content.s3.amazonaws.com/milad/ROLC/mnt/d/ROLC_High-Fidelity/02_Organize/O_Projects/Repos/hifi-content/Prototyping/avimoji/appResources/appData/resources/images/emojis/png1024/"
|
||||
}
|
4
scripts/simplifiedUI/emojiApp/resources/images/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
emojis/png1024
|
||||
emojis/svg-original
|
||||
emojis/svg-variations
|
||||
emojis/svgFiltered
|
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 25 KiB |