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

This commit is contained in:
samcake 2016-01-15 09:21:13 -08:00
commit 3f4f10c38c
48 changed files with 2488 additions and 2204 deletions

View file

@ -156,13 +156,6 @@ var STATE_CONTINUE_EQUIP = 14;
var STATE_WAITING_FOR_BUMPER_RELEASE = 15; var STATE_WAITING_FOR_BUMPER_RELEASE = 15;
var STATE_EQUIP_SPRING = 16; var STATE_EQUIP_SPRING = 16;
// Used by the HandAnimaitonBuddy to play hand animations
var IDLE_HAND_STATES = [STATE_OFF, STATE_RELEASE];
var OPEN_HAND_STATES = [STATE_SEARCHING, STATE_EQUIP_SEARCHING];
var POINT_HAND_STATES = [STATE_NEAR_TRIGGER, STATE_CONTINUE_NEAR_TRIGGER, STATE_FAR_TRIGGER, STATE_CONTINUE_FAR_TRIGGER];
var FAR_GRASP_HAND_STATES = [STATE_DISTANCE_HOLDING, STATE_CONTINUE_DISTANCE_HOLDING];
// otherwise grasp
// collision masks are specified by comma-separated list of group names // collision masks are specified by comma-separated list of group names
// the possible list of names is: static, dynamic, kinematic, myAvatar, otherAvatar // the possible list of names is: static, dynamic, kinematic, myAvatar, otherAvatar
var COLLISION_MASK_WHILE_GRABBED = "dynamic,otherAvatar"; var COLLISION_MASK_WHILE_GRABBED = "dynamic,otherAvatar";
@ -274,94 +267,6 @@ function getSpatialOffsetRotation(hand, spatialKey) {
return rotation; return rotation;
} }
var HAND_IDLE_RAMP_ON_RATE = 0.1;
var HAND_IDLE_RAMP_OFF_RATE = 0.02;
// ctor
function HandAnimationBuddy(handController) {
this.handController = handController;
this.hand = handController.hand;
this.handIdleAlpha = 0;
var handPrefix = (this.hand === RIGHT_HAND) ? "right" : "left";
this.animVarKeys = {
idle: handPrefix + "HandIdle",
overlayAlpha: handPrefix + "HandOverlayAlpha",
open: handPrefix + "HandOpen",
point: handPrefix + "HandPoint",
farGrasp: handPrefix + "HandFarGrasp",
grasp: handPrefix + "HandGrasp"
};
// hook up anim var handler
var self = this;
this.animHandlerId = MyAvatar.addAnimationStateHandler(function (props) {
return self.animStateHandler(props);
}, []);
}
HandAnimationBuddy.prototype.animStateHandler = function (props) {
var foundState = false;
var result = {};
var state = this.handController.state;
var keys = this.animVarKeys;
// idle check & process
if (IDLE_HAND_STATES.indexOf(state) != -1) {
// ramp down handIdleAlpha
this.handIdleAlpha = Math.max(0, this.handIdleAlpha - HAND_IDLE_RAMP_OFF_RATE);
result[keys.idle] = true;
foundState = true;
} else {
// ramp up handIdleAlpha
this.handIdleAlpha = Math.min(1, this.handIdleAlpha + HAND_IDLE_RAMP_ON_RATE);
result[keys.idle] = false;
}
result[keys.overlayAlpha] = this.handIdleAlpha;
// open check
if (OPEN_HAND_STATES.indexOf(state) != -1) {
result[keys.open] = true;
foundState = true;
} else {
result[keys.open] = false;
}
// point check
if (POINT_HAND_STATES.indexOf(state) != -1) {
result[keys.point] = true;
foundState = true;
} else {
result[keys.point] = false;
}
// far grasp check
if (FAR_GRASP_HAND_STATES.indexOf(state) != -1) {
result[keys.farGrasp] = true;
foundState = true;
} else {
result[keys.farGrasp] = false;
}
// grasp check
if (!foundState) {
result[keys.grasp] = true;
} else {
result[keys.grasp] = false;
}
return result;
};
HandAnimationBuddy.prototype.cleanup = function () {
if (this.animHandlerId) {
MyAvatar.removeAnimationStateHandler(this.animHandlerId);
this.animHandlerId = undefined;
}
};
function MyController(hand) { function MyController(hand) {
this.hand = hand; this.hand = hand;
if (this.hand === RIGHT_HAND) { if (this.hand === RIGHT_HAND) {
@ -402,8 +307,6 @@ function MyController(hand) {
this.offsetPosition = Vec3.ZERO; this.offsetPosition = Vec3.ZERO;
this.offsetRotation = Quat.IDENTITY; this.offsetRotation = Quat.IDENTITY;
this.handAnimationBuddy = new HandAnimationBuddy(this);
var _this = this; var _this = this;
this.update = function() { this.update = function() {
@ -1822,7 +1725,6 @@ function MyController(hand) {
Entities.deleteEntity(this.particleBeam); Entities.deleteEntity(this.particleBeam);
Entities.deleteEntity(this.spotLight); Entities.deleteEntity(this.spotLight);
Entities.deleteEntity(this.pointLight); Entities.deleteEntity(this.pointLight);
this.handAnimationBuddy.cleanup();
}; };
this.activateEntity = function(entityID, grabbedProperties) { this.activateEntity = function(entityID, grabbedProperties) {

View file

@ -1,76 +0,0 @@
//
// squeezeHands.js
// examples
//
// Created by Philip Rosedale on June 4, 2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var rightHandAnimation = HIFI_PUBLIC_BUCKET + "animations/RightHandAnimPhilip.fbx";
var leftHandAnimation = HIFI_PUBLIC_BUCKET + "animations/LeftHandAnimPhilip.fbx";
var LEFT = 0;
var RIGHT = 1;
var lastLeftFrame = 0;
var lastRightFrame = 0;
var leftDirection = true;
var rightDirection = true;
var LAST_FRAME = 15.0; // What is the number of the last frame we want to use in the animation?
var SMOOTH_FACTOR = 0.0;
var MAX_FRAMES = 30.0;
Script.update.connect(function(deltaTime) {
var leftTriggerValue = Controller.getTriggerValue(LEFT);
var rightTriggerValue = Controller.getTriggerValue(RIGHT);
var leftFrame, rightFrame;
// Average last few trigger frames together for a bit of smoothing
leftFrame = (leftTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastLeftFrame * SMOOTH_FACTOR;
rightFrame = (rightTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastRightFrame * SMOOTH_FACTOR;
if (!leftDirection) {
leftFrame = MAX_FRAMES - leftFrame;
}
if (!rightDirection) {
rightFrame = MAX_FRAMES - rightFrame;
}
if ((leftTriggerValue == 1.0) && (leftDirection == true)) {
leftDirection = false;
lastLeftFrame = MAX_FRAMES - leftFrame;
} else if ((leftTriggerValue == 0.0) && (leftDirection == false)) {
leftDirection = true;
lastLeftFrame = leftFrame;
}
if ((rightTriggerValue == 1.0) && (rightDirection == true)) {
rightDirection = false;
lastRightFrame = MAX_FRAMES - rightFrame;
} else if ((rightTriggerValue == 0.0) && (rightDirection == false)) {
rightDirection = true;
lastRightFrame = rightFrame;
}
if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){
MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame);
}
if ((rightFrame != lastRightFrame) && rightHandAnimation.length) {
MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame);
}
lastLeftFrame = leftFrame;
lastRightFrame = rightFrame;
});
Script.scriptEnding.connect(function() {
MyAvatar.stopAnimation(leftHandAnimation);
MyAvatar.stopAnimation(rightHandAnimation);
});

View file

@ -1,86 +1,80 @@
// //
// squeezeHands.js // controllers/squeezeHands.js
// examples
// //
// Created by Philip Rosedale on June 4, 2014 // Created by Anthony J. Thibault
// Copyright 2014 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
//
// Default script to drive the animation of the hands based on hand controllers.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var HIFI_OZAN_BUCKET = "http://hifi-public.s3.amazonaws.com/ozan/";
var rightHandAnimation = HIFI_OZAN_BUCKET + "anim/squeeze_hands/right_hand_anim.fbx";
var leftHandAnimation = HIFI_OZAN_BUCKET + "anim/squeeze_hands/left_hand_anim.fbx";
var lastLeftTrigger = 0; var lastLeftTrigger = 0;
var lastRightTrigger = 0; var lastRightTrigger = 0;
var leftHandOverlayAlpha = 0;
var leftIsClosing = true; var rightHandOverlayAlpha = 0;
var rightIsClosing = true;
var LAST_FRAME = 15.0; // What is the number of the last frame we want to use in the animation?
var SMOOTH_FACTOR = 0.75;
var MAX_FRAMES = 30.0;
var CONTROLLER_DEAD_SPOT = 0.25; var CONTROLLER_DEAD_SPOT = 0.25;
var TRIGGER_SMOOTH_TIMESCALE = 0.1;
var OVERLAY_RAMP_RATE = 8.0;
function clamp(val, min, max) { function clamp(val, min, max) {
if (val < min) { return Math.min(Math.max(val, min), max);
return min;
} else if (val > max) {
return max;
} else {
return val;
}
} }
function normalizeControllerValue(val) { function normalizeControllerValue(val) {
return clamp((val - CONTROLLER_DEAD_SPOT) / (1.0 - CONTROLLER_DEAD_SPOT), 0.0, 1.0); return clamp((val - CONTROLLER_DEAD_SPOT) / (1 - CONTROLLER_DEAD_SPOT), 0, 1);
} }
Script.update.connect(function(deltaTime) { function lerp(a, b, alpha) {
return a * (1 - alpha) + b * alpha;
}
function init() {
Script.update.connect(update);
MyAvatar.addAnimationStateHandler(animStateHandler, ["leftHandOverlayAlpha", "rightHandOverlayAlpha",
"leftHandGraspAlpha", "rightHandGraspAlpha"]);
}
function animStateHandler(props) {
return { leftHandOverlayAlpha: leftHandOverlayAlpha,
leftHandGraspAlpha: lastLeftTrigger,
rightHandOverlayAlpha: rightHandOverlayAlpha,
rightHandGraspAlpha: lastRightTrigger };
}
function update(dt) {
var leftTrigger = normalizeControllerValue(Controller.getValue(Controller.Standard.LT)); var leftTrigger = normalizeControllerValue(Controller.getValue(Controller.Standard.LT));
var rightTrigger = normalizeControllerValue(Controller.getValue(Controller.Standard.RT)); var rightTrigger = normalizeControllerValue(Controller.getValue(Controller.Standard.RT));
// Average last few trigger values together for a bit of smoothing // Average last few trigger values together for a bit of smoothing
var smoothLeftTrigger = leftTrigger * (1.0 - SMOOTH_FACTOR) + lastLeftTrigger * SMOOTH_FACTOR; var tau = clamp(dt / TRIGGER_SMOOTH_TIMESCALE, 0, 1);
var smoothRightTrigger = rightTrigger * (1.0 - SMOOTH_FACTOR) + lastRightTrigger * SMOOTH_FACTOR; lastLeftTrigger = lerp(leftTrigger, lastLeftTrigger, tau);
lastRightTrigger = lerp(rightTrigger, lastRightTrigger, tau);
if (leftTrigger == 0.0) { // ramp on/off left hand overlay
leftIsClosing = true; var leftHandPose = Controller.getPoseValue(Controller.Standard.LeftHand);
} else if (leftTrigger == 1.0) { if (leftHandPose.valid) {
leftIsClosing = false; leftHandOverlayAlpha = clamp(leftHandOverlayAlpha + OVERLAY_RAMP_RATE * dt, 0, 1);
} else {
leftHandOverlayAlpha = clamp(leftHandOverlayAlpha - OVERLAY_RAMP_RATE * dt, 0, 1);
} }
if (rightTrigger == 0.0) { // ramp on/off right hand overlay
rightIsClosing = true; var rightHandPose = Controller.getPoseValue(Controller.Standard.RightHand);
} else if (rightTrigger == 1.0) { if (rightHandPose.valid) {
rightIsClosing = false; rightHandOverlayAlpha = clamp(rightHandOverlayAlpha + OVERLAY_RAMP_RATE * dt, 0, 1);
} else {
rightHandOverlayAlpha = clamp(rightHandOverlayAlpha - OVERLAY_RAMP_RATE * dt, 0, 1);
} }
}
lastLeftTrigger = smoothLeftTrigger; function shutdown() {
lastRightTrigger = smoothRightTrigger; Script.update.disconnect(update);
MyAvatar.removeAnimationStateHandler(animStateHandler);
}
// 0..15 Script.scriptEnding.connect(shutdown);
var leftFrame = smoothLeftTrigger * LAST_FRAME;
var rightFrame = smoothRightTrigger * LAST_FRAME;
var adjustedLeftFrame = (leftIsClosing) ? leftFrame : (MAX_FRAMES - leftFrame); init();
if (leftHandAnimation.length) {
MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, adjustedLeftFrame, adjustedLeftFrame);
}
var adjustedRightFrame = (rightIsClosing) ? rightFrame : (MAX_FRAMES - rightFrame);
if (rightHandAnimation.length) {
MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, adjustedRightFrame, adjustedRightFrame);
}
});
Script.scriptEnding.connect(function() {
MyAvatar.stopAnimation(leftHandAnimation);
MyAvatar.stopAnimation(rightHandAnimation);
});

View file

@ -17,6 +17,7 @@ Script.load("inspect.js");
Script.load("notifications.js"); Script.load("notifications.js");
Script.load("users.js"); Script.load("users.js");
Script.load("controllers/handControllerGrab.js"); Script.load("controllers/handControllerGrab.js");
Script.load("controllers/squeezeHands.js");
Script.load("grab.js"); Script.load("grab.js");
Script.load("directory.js"); Script.load("directory.js");
Script.load("dialTone.js"); Script.load("dialTone.js");

View file

@ -33,11 +33,11 @@ var BUTTON_SIZE = 32;
var PADDING = 3; var PADDING = 3;
Script.include(["libraries/toolBars.js"]); Script.include(["libraries/toolBars.js"]);
var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.dice.toolbar", function (screenSize) { var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.dice.toolbar", function(screenSize) {
return { return {
x: (screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING), x: (screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING),
y: (screenSize.y - (BUTTON_SIZE + PADDING)) y: (screenSize.y - (BUTTON_SIZE + PADDING))
}; };
}); });
var offButton = toolBar.addOverlay("image", { var offButton = toolBar.addOverlay("image", {
width: BUTTON_SIZE, width: BUTTON_SIZE,
@ -65,12 +65,13 @@ var deleteButton = toolBar.addOverlay("image", {
alpha: 1 alpha: 1
}); });
var diceIconURL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/images/dice.png"
var diceButton = toolBar.addOverlay("image", { var diceButton = toolBar.addOverlay("image", {
x: screenSize.x / 2 + PADDING, x: screenSize.x / 2 + PADDING,
y: screenSize.y - (BUTTON_SIZE + PADDING), y: screenSize.y - (BUTTON_SIZE + PADDING),
width: BUTTON_SIZE, width: BUTTON_SIZE,
height: BUTTON_SIZE, height: BUTTON_SIZE,
imageURL: HIFI_PUBLIC_BUCKET + "images/die.png", imageURL: diceIconURL,
color: { color: {
red: 255, red: 255,
green: 255, green: 255,
@ -79,6 +80,7 @@ var diceButton = toolBar.addOverlay("image", {
alpha: 1 alpha: 1
}); });
var GRAVITY = -3.5; var GRAVITY = -3.5;
// NOTE: angularVelocity is in radians/sec // NOTE: angularVelocity is in radians/sec
@ -90,28 +92,27 @@ function shootDice(position, velocity) {
Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG); Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
} else { } else {
for (var i = 0; i < NUMBER_OF_DICE; i++) { for (var i = 0; i < NUMBER_OF_DICE; i++) {
dice.push(Entities.addEntity( dice.push(Entities.addEntity({
{ type: "Model",
type: "Model", modelURL: HIFI_PUBLIC_BUCKET + "models/props/Dice/goldDie.fbx",
modelURL: HIFI_PUBLIC_BUCKET + "models/props/Dice/goldDie.fbx", position: position,
position: position, velocity: velocity,
velocity: velocity, rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360),
rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360), angularVelocity: {
angularVelocity: { x: Math.random() * MAX_ANGULAR_SPEED,
x: Math.random() * MAX_ANGULAR_SPEED, y: Math.random() * MAX_ANGULAR_SPEED,
y: Math.random() * MAX_ANGULAR_SPEED, z: Math.random() * MAX_ANGULAR_SPEED
z: Math.random() * MAX_ANGULAR_SPEED },
}, gravity: {
gravity: { x: 0,
x: 0, y: GRAVITY,
y: GRAVITY, z: 0
z: 0 },
}, lifetime: LIFETIME,
lifetime: LIFETIME, shapeType: "box",
shapeType: "box", collisionsWillMove: true,
collisionsWillMove: true, collisionSoundURL: "http://s3.amazonaws.com/hifi-public/sounds/dice/diceCollide.wav"
collisionSoundURL: "http://s3.amazonaws.com/hifi-public/sounds/dice/diceCollide.wav" }));
}));
position = Vec3.sum(position, Vec3.multiply(DIE_SIZE, Vec3.normalize(Quat.getRight(Camera.getOrientation())))); position = Vec3.sum(position, Vec3.multiply(DIE_SIZE, Vec3.normalize(Quat.getRight(Camera.getOrientation()))));
} }
} }
@ -150,4 +151,4 @@ function scriptEnding() {
} }
Controller.mousePressEvent.connect(mousePressEvent); Controller.mousePressEvent.connect(mousePressEvent);
Script.scriptEnding.connect(scriptEnding); Script.scriptEnding.connect(scriptEnding);

View file

@ -1502,7 +1502,11 @@ PropertiesTool = function(opts) {
var that = {}; var that = {};
var url = Script.resolvePath('html/entityProperties.html'); var url = Script.resolvePath('html/entityProperties.html');
var webView = new WebWindow('Entity Properties', url, 200, 280, true); var webView = new OverlayWebWindow({
title: 'Entity Properties',
source: url,
toolWindow: true
});
var visible = false; var visible = false;

View file

@ -51,7 +51,7 @@ SettingsWindow = function() {
this.plankyStack = null; this.plankyStack = null;
this.webWindow = null; this.webWindow = null;
this.init = function(plankyStack) { this.init = function(plankyStack) {
_this.webWindow = new WebWindow('Planky', Script.resolvePath('../../html/plankySettings.html'), 255, 500, true); _this.webWindow = new OverlayWebWindow('Planky', Script.resolvePath('../../html/plankySettings.html'), 255, 500, true);
_this.webWindow.setVisible(false); _this.webWindow.setVisible(false);
_this.webWindow.eventBridge.webEventReceived.connect(_this.onWebEventReceived); _this.webWindow.eventBridge.webEventReceived.connect(_this.onWebEventReceived);
_this.plankyStack = plankyStack; _this.plankyStack = plankyStack;

View file

@ -2,6 +2,8 @@
<head> <head>
<link rel="stylesheet" type="text/css" href="style.css"> <link rel="stylesheet" type="text/css" href="style.css">
<script src="list.min.js"></script> <script src="list.min.js"></script>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="eventBridgeLoader.js"></script>
<script> <script>
var entities = {}; var entities = {};
var selectedEntities = []; var selectedEntities = [];
@ -15,217 +17,219 @@
const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities. const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities.
function loaded() { function loaded() {
entityList = new List('entity-list', { valueNames: ['name', 'type', 'url'], page: MAX_ITEMS}); openEventBridge(function() {
entityList.clear(); entityList = new List('entity-list', { valueNames: ['name', 'type', 'url'], page: MAX_ITEMS});
elEntityTable = document.getElementById("entity-table"); entityList.clear();
elEntityTableBody = document.getElementById("entity-table-body"); elEntityTable = document.getElementById("entity-table");
elRefresh = document.getElementById("refresh"); elEntityTableBody = document.getElementById("entity-table-body");
elDelete = document.getElementById("delete"); elRefresh = document.getElementById("refresh");
elTeleport = document.getElementById("teleport"); elDelete = document.getElementById("delete");
elRadius = document.getElementById("radius"); elTeleport = document.getElementById("teleport");
elNoEntitiesMessage = document.getElementById("no-entities"); elRadius = document.getElementById("radius");
elNoEntitiesRadius = document.getElementById("no-entities-radius"); elNoEntitiesMessage = document.getElementById("no-entities");
elNoEntitiesRadius = document.getElementById("no-entities-radius");
document.getElementById("entity-name").onclick = function() {
setSortColumn('name'); document.getElementById("entity-name").onclick = function() {
}; setSortColumn('name');
document.getElementById("entity-type").onclick = function() { };
setSortColumn('type'); document.getElementById("entity-type").onclick = function() {
}; setSortColumn('type');
document.getElementById("entity-url").onclick = function() { };
setSortColumn('url'); document.getElementById("entity-url").onclick = function() {
}; setSortColumn('url');
};
function onRowClicked(clickEvent) {
var id = this.dataset.entityId; function onRowClicked(clickEvent) {
var selection = [this.dataset.entityId]; var id = this.dataset.entityId;
if (clickEvent.ctrlKey) { var selection = [this.dataset.entityId];
selection = selection.concat(selectedEntities); if (clickEvent.ctrlKey) {
} else if (clickEvent.shiftKey && selectedEntities.length > 0) { selection = selection.concat(selectedEntities);
var previousItemFound = -1; } else if (clickEvent.shiftKey && selectedEntities.length > 0) {
var clickedItemFound = -1; var previousItemFound = -1;
for (var entity in entityList.visibleItems) { var clickedItemFound = -1;
if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) { for (var entity in entityList.visibleItems) {
clickedItemFound = entity; if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) {
} else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) { clickedItemFound = entity;
previousItemFound = entity; } else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) {
} previousItemFound = entity;
} }
if (previousItemFound !== -1 && clickedItemFound !== -1) { }
var betweenItems = []; if (previousItemFound !== -1 && clickedItemFound !== -1) {
var toItem = Math.max(previousItemFound, clickedItemFound); var betweenItems = [];
// skip first and last item in this loop, we add them to selection after the loop var toItem = Math.max(previousItemFound, clickedItemFound);
for (var i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) { // skip first and last item in this loop, we add them to selection after the loop
entityList.visibleItems[i].elm.className = 'selected'; for (var i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) {
betweenItems.push(entityList.visibleItems[i].values().id); entityList.visibleItems[i].elm.className = 'selected';
} betweenItems.push(entityList.visibleItems[i].values().id);
if (previousItemFound > clickedItemFound) { }
// always make sure that we add the items in the right order if (previousItemFound > clickedItemFound) {
betweenItems.reverse(); // always make sure that we add the items in the right order
} betweenItems.reverse();
selection = selection.concat(betweenItems, selectedEntities); }
} selection = selection.concat(betweenItems, selectedEntities);
} }
}
selectedEntities = selection;
selectedEntities = selection;
this.className = 'selected';
this.className = 'selected';
EventBridge.emitWebEvent(JSON.stringify({
type: "selectionUpdate", EventBridge.emitWebEvent(JSON.stringify({
focus: false, type: "selectionUpdate",
entityIds: selection, focus: false,
})); entityIds: selection,
} }));
}
function onRowDoubleClicked() {
EventBridge.emitWebEvent(JSON.stringify({ function onRowDoubleClicked() {
type: "selectionUpdate", EventBridge.emitWebEvent(JSON.stringify({
focus: true, type: "selectionUpdate",
entityIds: [this.dataset.entityId], focus: true,
})); entityIds: [this.dataset.entityId],
} }));
}
function addEntity(id, name, type, url) {
if (entities[id] === undefined) { function addEntity(id, name, type, url) {
var urlParts = url.split('/'); if (entities[id] === undefined) {
var filename = urlParts[urlParts.length - 1]; var urlParts = url.split('/');
var filename = urlParts[urlParts.length - 1];
entityList.add([{ id: id, name: name, type: type, url: filename }], function(items) {
var currentElement = items[0].elm; entityList.add([{ id: id, name: name, type: type, url: filename }], function(items) {
var id = items[0]._values.id; var currentElement = items[0].elm;
entities[id] = { var id = items[0]._values.id;
id: id, entities[id] = {
name: name, id: id,
el: currentElement, name: name,
item: items[0], el: currentElement,
}; item: items[0],
currentElement.setAttribute('id', 'entity_' + id); };
currentElement.setAttribute('title', url); currentElement.setAttribute('id', 'entity_' + id);
currentElement.dataset.entityId = id; currentElement.setAttribute('title', url);
currentElement.onclick = onRowClicked; currentElement.dataset.entityId = id;
currentElement.ondblclick = onRowDoubleClicked; currentElement.onclick = onRowClicked;
}); currentElement.ondblclick = onRowDoubleClicked;
});
if (refreshEntityListTimer) {
clearTimeout(refreshEntityListTimer); if (refreshEntityListTimer) {
} clearTimeout(refreshEntityListTimer);
refreshEntityListTimer = setTimeout(refreshEntityListObject, 50); }
} else { refreshEntityListTimer = setTimeout(refreshEntityListObject, 50);
var item = entities[id].item; } else {
item.values({ name: name, url: url }); var item = entities[id].item;
} item.values({ name: name, url: url });
} }
}
function clearEntities() {
entities = {}; function clearEntities() {
entityList.clear(); entities = {};
} entityList.clear();
}
var elSortOrder = {
name: document.querySelector('#entity-name .sort-order'), var elSortOrder = {
type: document.querySelector('#entity-type .sort-order'), name: document.querySelector('#entity-name .sort-order'),
url: document.querySelector('#entity-url .sort-order'), type: document.querySelector('#entity-type .sort-order'),
} url: document.querySelector('#entity-url .sort-order'),
function setSortColumn(column) { }
if (currentSortColumn == column) { function setSortColumn(column) {
currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc"; if (currentSortColumn == column) {
} else { currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc";
elSortOrder[currentSortColumn].style.display = 'none'; } else {
elSortOrder[column].style.display = 'inline'; elSortOrder[currentSortColumn].style.display = 'none';
currentSortColumn = column; elSortOrder[column].style.display = 'inline';
currentSortOrder = "asc"; currentSortColumn = column;
} currentSortOrder = "asc";
elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASCENDING_STRING : DESCENDING_STRING; }
entityList.sort(currentSortColumn, { order: currentSortOrder }); elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASCENDING_STRING : DESCENDING_STRING;
} entityList.sort(currentSortColumn, { order: currentSortOrder });
}
function refreshEntities() {
clearEntities(); function refreshEntities() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' })); clearEntities();
} EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' }));
}
function refreshEntityListObject() {
refreshEntityListTimer = null; function refreshEntityListObject() {
entityList.sort(currentSortColumn, { order: currentSortOrder }); refreshEntityListTimer = null;
entityList.search(document.getElementById("filter").value); entityList.sort(currentSortColumn, { order: currentSortOrder });
} entityList.search(document.getElementById("filter").value);
}
function updateSelectedEntities(selectedEntities) {
var notFound = false; function updateSelectedEntities(selectedEntities) {
for (var id in entities) { var notFound = false;
entities[id].el.className = ''; for (var id in entities) {
} entities[id].el.className = '';
for (var i = 0; i < selectedEntities.length; i++) { }
var id = selectedEntities[i]; for (var i = 0; i < selectedEntities.length; i++) {
if (id in entities) { var id = selectedEntities[i];
var entity = entities[id]; if (id in entities) {
entity.el.className = 'selected'; var entity = entities[id];
} else { entity.el.className = 'selected';
notFound = true; } else {
} notFound = true;
} }
return notFound; }
} return notFound;
}
elRefresh.onclick = function() {
refreshEntities(); elRefresh.onclick = function() {
} refreshEntities();
elTeleport.onclick = function() { }
EventBridge.emitWebEvent(JSON.stringify({ type: 'teleport' })); elTeleport.onclick = function() {
} EventBridge.emitWebEvent(JSON.stringify({ type: 'teleport' }));
elDelete.onclick = function() { }
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); elDelete.onclick = function() {
refreshEntities(); EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
} refreshEntities();
}
document.addEventListener("keydown", function (keyDownEvent) {
if (keyDownEvent.target.nodeName === "INPUT") { document.addEventListener("keydown", function (keyDownEvent) {
return; if (keyDownEvent.target.nodeName === "INPUT") {
} return;
var keyCode = keyDownEvent.keyCode; }
if (keyCode === DELETE) { var keyCode = keyDownEvent.keyCode;
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); if (keyCode === DELETE) {
refreshEntities(); EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
} refreshEntities();
}, false); }
}, false);
elRadius.onchange = function () {
elRadius.value = Math.max(elRadius.value, 0); elRadius.onchange = function () {
EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elRadius.value })); elRadius.value = Math.max(elRadius.value, 0);
refreshEntities(); EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elRadius.value }));
elNoEntitiesRadius.firstChild.nodeValue = elRadius.value; refreshEntities();
} elNoEntitiesRadius.firstChild.nodeValue = elRadius.value;
}
if (window.EventBridge !== undefined) {
EventBridge.scriptEventReceived.connect(function(data) { if (window.EventBridge !== undefined) {
data = JSON.parse(data); EventBridge.scriptEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.type === "clearEntityList") {
clearEntities(); if (data.type === "clearEntityList") {
} else if (data.type == "selectionUpdate") { clearEntities();
var notFound = updateSelectedEntities(data.selectedIDs); } else if (data.type == "selectionUpdate") {
if (notFound) { var notFound = updateSelectedEntities(data.selectedIDs);
refreshEntities(); if (notFound) {
} refreshEntities();
} else if (data.type == "update") { }
var newEntities = data.entities; } else if (data.type == "update") {
if (newEntities.length == 0) { var newEntities = data.entities;
elEntityTable.style.display = "none"; if (newEntities.length == 0) {
elNoEntitiesMessage.style.display = "block"; elEntityTable.style.display = "none";
} else { elNoEntitiesMessage.style.display = "block";
elEntityTable.style.display = "table"; } else {
elNoEntitiesMessage.style.display = "none"; elEntityTable.style.display = "table";
for (var i = 0; i < newEntities.length; i++) { elNoEntitiesMessage.style.display = "none";
var id = newEntities[i].id; for (var i = 0; i < newEntities.length; i++) {
addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url); var id = newEntities[i].id;
} addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url);
updateSelectedEntities(data.selectedIDs); }
} updateSelectedEntities(data.selectedIDs);
} }
}); }
setTimeout(refreshEntities, 1000); });
} setTimeout(refreshEntities, 1000);
}
});
} }
</script> </script>
</head> </head>

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,8 @@
// void scriptEventReceived(const QString& data); // void scriptEventReceived(const QString& data);
// //
var EventBridge;
EventBridgeConnectionProxy = function(parent) { EventBridgeConnectionProxy = function(parent) {
this.parent = parent; this.parent = parent;
this.realSignal = this.parent.realBridge.scriptEventReceived this.realSignal = this.parent.realBridge.scriptEventReceived
@ -46,12 +48,10 @@ openEventBridge = function(callback) {
socket.onopen = function() { socket.onopen = function() {
channel = new QWebChannel(socket, function(channel) { channel = new QWebChannel(socket, function(channel) {
console.log("Document url is " + document.URL); console.log("Document url is " + document.URL);
for(var key in channel.objects){
console.log("registered object: " + key);
}
var webWindow = channel.objects[document.URL.toLowerCase()]; var webWindow = channel.objects[document.URL.toLowerCase()];
console.log("WebWindow is " + webWindow) console.log("WebWindow is " + webWindow)
eventBridgeProxy = new EventBridgeProxy(webWindow); eventBridgeProxy = new EventBridgeProxy(webWindow);
EventBridge = eventBridgeProxy;
if (callback) { callback(eventBridgeProxy); } if (callback) { callback(eventBridgeProxy); }
}); });
} }

View file

@ -1,110 +1,114 @@
<html> <html>
<head> <head>
<link rel="stylesheet" type="text/css" href="style.css"> <link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="eventBridgeLoader.js"></script>
<script> <script>
function loaded() { function loaded() {
var gridColor = { red: 0, green: 0, blue: 0 }; openEventBridge(function() {
var gridColors = [ var gridColor = { red: 0, green: 0, blue: 0 };
{ red: 0, green: 0, blue: 0 }, var gridColors = [
{ red: 255, green: 255, blue: 255 }, { red: 0, green: 0, blue: 0 },
{ red: 255, green: 0, blue: 0 }, { red: 255, green: 255, blue: 255 },
{ red: 0, green: 255, blue: 0}, { red: 255, green: 0, blue: 0 },
{ red: 0, green: 0, blue: 255 }, { red: 0, green: 255, blue: 0},
]; { red: 0, green: 0, blue: 255 },
var gridColorIndex = 0; ];
var gridColorIndex = 0;
elPosY = document.getElementById("horiz-y");
elMinorSpacing = document.getElementById("minor-spacing"); elPosY = document.getElementById("horiz-y");
elMajorSpacing = document.getElementById("major-spacing"); elMinorSpacing = document.getElementById("minor-spacing");
elSnapToGrid = document.getElementById("snap-to-grid"); elMajorSpacing = document.getElementById("major-spacing");
elHorizontalGridVisible = document.getElementById("horiz-grid-visible"); elSnapToGrid = document.getElementById("snap-to-grid");
elMoveToSelection = document.getElementById("move-to-selection"); elHorizontalGridVisible = document.getElementById("horiz-grid-visible");
elMoveToAvatar = document.getElementById("move-to-avatar"); elMoveToSelection = document.getElementById("move-to-selection");
elMoveToAvatar = document.getElementById("move-to-avatar");
if (window.EventBridge !== undefined) {
EventBridge.scriptEventReceived.connect(function(data) { if (window.EventBridge !== undefined) {
data = JSON.parse(data); EventBridge.scriptEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.origin) {
var origin = data.origin; if (data.origin) {
elPosY.value = origin.y.toFixed(2); var origin = data.origin;
elPosY.value = origin.y.toFixed(2);
}
if (data.minorGridWidth !== undefined) {
elMinorSpacing.value = data.minorGridWidth;
}
if (data.majorGridEvery !== undefined) {
elMajorSpacing.value = data.majorGridEvery;
}
if (data.gridColor) {
gridColor = data.gridColor;
}
if (data.snapToGrid !== undefined) {
elSnapToGrid.checked = data.snapToGrid == true;
}
if (data.visible !== undefined) {
elHorizontalGridVisible.checked = data.visible == true;
}
});
function emitUpdate() {
EventBridge.emitWebEvent(JSON.stringify({
type: "update",
origin: {
y: elPosY.value,
},
minorGridWidth: elMinorSpacing.value,
majorGridEvery: elMajorSpacing.value,
gridColor: gridColor,
colorIndex: gridColorIndex,
snapToGrid: elSnapToGrid.checked,
visible: elHorizontalGridVisible.checked,
}));
} }
if (data.minorGridWidth !== undefined) {
elMinorSpacing.value = data.minorGridWidth;
}
if (data.majorGridEvery !== undefined) {
elMajorSpacing.value = data.majorGridEvery;
}
if (data.gridColor) {
gridColor = data.gridColor;
}
if (data.snapToGrid !== undefined) {
elSnapToGrid.checked = data.snapToGrid == true;
}
if (data.visible !== undefined) {
elHorizontalGridVisible.checked = data.visible == true;
}
});
function emitUpdate() {
EventBridge.emitWebEvent(JSON.stringify({
type: "update",
origin: {
y: elPosY.value,
},
minorGridWidth: elMinorSpacing.value,
majorGridEvery: elMajorSpacing.value,
gridColor: gridColor,
colorIndex: gridColorIndex,
snapToGrid: elSnapToGrid.checked,
visible: elHorizontalGridVisible.checked,
}));
} }
} elPosY.addEventListener("change", emitUpdate);
elMinorSpacing.addEventListener("change", emitUpdate);
elPosY.addEventListener("change", emitUpdate); elMajorSpacing.addEventListener("change", emitUpdate);
elMinorSpacing.addEventListener("change", emitUpdate); elSnapToGrid.addEventListener("change", emitUpdate);
elMajorSpacing.addEventListener("change", emitUpdate); elHorizontalGridVisible.addEventListener("change", emitUpdate);
elSnapToGrid.addEventListener("change", emitUpdate);
elHorizontalGridVisible.addEventListener("change", emitUpdate); elMoveToAvatar.addEventListener("click", function() {
EventBridge.emitWebEvent(JSON.stringify({
elMoveToAvatar.addEventListener("click", function() { type: "action",
EventBridge.emitWebEvent(JSON.stringify({ action: "moveToAvatar",
type: "action", }));
action: "moveToAvatar", });
})); elMoveToSelection.addEventListener("click", function() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "moveToSelection",
}));
});
var gridColorBox = document.getElementById('grid-color');
for (var i = 0; i < gridColors.length; i++) {
var colors = gridColors[i];
var box = document.createElement('div');
box.setAttribute('class', 'color-box');
box.style.background = 'rgb(' + colors.red + ', ' + colors.green + ', ' + colors.blue + ')';
document.getElementById("grid-colors").appendChild(box);
box.addEventListener("click", function(color, index) {
return function() {
gridColor = color;
gridColorIndex = index;
emitUpdate();
}
}({ red: colors.red, green: colors.green, blue: colors.blue }, i));
}
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
}); });
elMoveToSelection.addEventListener("click", function() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "moveToSelection",
}));
});
var gridColorBox = document.getElementById('grid-color');
for (var i = 0; i < gridColors.length; i++) {
var colors = gridColors[i];
var box = document.createElement('div');
box.setAttribute('class', 'color-box');
box.style.background = 'rgb(' + colors.red + ', ' + colors.green + ', ' + colors.blue + ')';
document.getElementById("grid-colors").appendChild(box);
box.addEventListener("click", function(color, index) {
return function() {
gridColor = color;
gridColorIndex = index;
emitUpdate();
}
}({ red: colors.red, green: colors.green, blue: colors.blue }, i));
}
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
} }
</script> </script>
</head> </head>

View file

@ -4,7 +4,9 @@ EntityListTool = function(opts) {
var that = {}; var that = {};
var url = ENTITY_LIST_HTML_URL; var url = ENTITY_LIST_HTML_URL;
var webView = new WebWindow('Entities', url, 200, 280, true); var webView = new OverlayWebWindow({
title: 'Entities', source: url, toolWindow: true
});
var searchRadius = 100; var searchRadius = 100;

View file

@ -231,7 +231,9 @@ GridTool = function(opts) {
var listeners = []; var listeners = [];
var url = GRID_CONTROLS_HTML_URL; var url = GRID_CONTROLS_HTML_URL;
var webView = new WebWindow('Grid', url, 200, 280, true); var webView = new OverlayWebWindow({
title: 'Grid', source: url, toolWindow: true
});
horizontalGrid.addListener(function(data) { horizontalGrid.addListener(function(data) {
webView.eventBridge.emitScriptEvent(JSON.stringify(data)); webView.eventBridge.emitScriptEvent(JSON.stringify(data));

View file

@ -113,7 +113,7 @@
"id": "rightHandOverlay", "id": "rightHandOverlay",
"type": "overlay", "type": "overlay",
"data": { "data": {
"alpha": 1.0, "alpha": 0.0,
"boneSet": "rightHand", "boneSet": "rightHand",
"alphaVar": "rightHandOverlayAlpha" "alphaVar": "rightHandOverlayAlpha"
}, },
@ -122,125 +122,50 @@
"id": "rightHandStateMachine", "id": "rightHandStateMachine",
"type": "stateMachine", "type": "stateMachine",
"data": { "data": {
"currentState": "rightHandIdle", "currentState": "rightHandGrasp",
"states": [ "states": [
{
"id": "rightHandIdle",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "rightHandOpen", "state": "rightHandOpen" },
{ "var": "rightHandGrasp", "state": "rightHandGrasp" },
{ "var": "rightHandPoint", "state": "rightHandPoint" },
{ "var": "rightHandFarGrasp", "state": "rightHandFarGrasp" }
]
},
{
"id": "rightHandOpen",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "rightHandIdle", "state": "rightHandIdle" },
{ "var": "rightHandGrasp", "state": "rightHandGrasp" },
{ "var": "rightHandPoint", "state": "rightHandPoint" },
{ "var": "rightHandFarGrasp", "state": "rightHandFarGrasp" }
]
},
{ {
"id": "rightHandGrasp", "id": "rightHandGrasp",
"interpTarget": 3, "interpTarget": 3,
"interpDuration": 3, "interpDuration": 3,
"transitions": [ "transitions": []
{ "var": "rightHandOpen", "state": "rightHandOpen" },
{ "var": "rightHandIdle", "state": "rightHandIdle" },
{ "var": "rightHandPoint", "state": "rightHandPoint" },
{ "var": "rightHandFarGrasp", "state": "rightHandFarGrasp" }
]
},
{
"id": "rightHandPoint",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "rightHandOpen", "state": "rightHandOpen" },
{ "var": "rightHandIdle", "state": "rightHandIdle" },
{ "var": "rightHandGrasp", "state": "rightHandGrasp" },
{ "var": "rightHandFarGrasp", "state": "rightHandFarGrasp" }
]
},
{
"id": "rightHandFarGrasp",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "rightHandOpen", "state": "rightHandOpen" },
{ "var": "rightHandIdle", "state": "rightHandIdle" },
{ "var": "rightHandGrasp", "state": "rightHandGrasp" },
{ "var": "rightHandPoint", "state": "rightHandPoint" }
]
} }
] ]
}, },
"children": [ "children": [
{
"id": "rightHandIdle",
"type": "clip",
"data": {
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_right_hand.fbx",
"startFrame": 0.0,
"endFrame": 0.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "rightHandOpen",
"type": "clip",
"data": {
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/hand_anims/search_right.fbx",
"startFrame": 0.0,
"endFrame": 0.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{ {
"id": "rightHandGrasp", "id": "rightHandGrasp",
"type": "clip", "type": "blendLinear",
"data": { "data": {
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/hand_anims/grasp_right.fbx", "alpha": 0.0,
"startFrame": 0.0, "alphaVar": "rightHandGraspAlpha"
"endFrame": 0.0,
"timeScale": 1.0,
"loopFlag": true
}, },
"children": [] "children": [
}, {
{ "id": "rightHandGraspOpen",
"id": "rightHandPoint", "type": "clip",
"type": "clip", "data": {
"data": { "url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/hand_anims/hydra_pose_open_right.fbx",
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_right_hand.fbx", "startFrame": 0.0,
"startFrame": 20.0, "endFrame": 0.0,
"endFrame": 20.0, "timeScale": 1.0,
"timeScale": 1.0, "loopFlag": true
"loopFlag": true },
}, "children": []
"children": [] },
}, {
{ "id": "rightHandGraspClosed",
"id": "rightHandFarGrasp", "type": "clip",
"type": "clip", "data": {
"data": { "url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/hand_anims/hydra_pose_closed_right.fbx",
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/hand_anims/far_grasp_right.fbx", "startFrame": 0.0,
"startFrame": 0.0, "endFrame": 0.0,
"endFrame": 0.0, "timeScale": 1.0,
"timeScale": 1.0, "loopFlag": true
"loopFlag": true },
}, "children": []
"children": [] }
]
} }
] ]
}, },
@ -248,7 +173,7 @@
"id": "leftHandOverlay", "id": "leftHandOverlay",
"type": "overlay", "type": "overlay",
"data": { "data": {
"alpha": 1.0, "alpha": 0.0,
"boneSet": "leftHand", "boneSet": "leftHand",
"alphaVar" : "leftHandOverlayAlpha" "alphaVar" : "leftHandOverlayAlpha"
}, },
@ -257,127 +182,50 @@
"id": "leftHandStateMachine", "id": "leftHandStateMachine",
"type": "stateMachine", "type": "stateMachine",
"data": { "data": {
"currentState": "leftHandIdle", "currentState": "leftHandGrasp",
"states": [ "states": [
{
"id": "leftHandIdle",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "leftHandOpen", "state": "leftHandOpen" },
{ "var": "leftHandGrasp", "state": "leftHandGrasp" },
{ "var": "leftHandPoint", "state": "leftHandPoint" },
{ "var": "leftHandFarGrasp", "state": "leftHandFarGrasp" }
]
},
{
"id": "leftHandOpen",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "leftHandIdle", "state": "leftHandIdle" },
{ "var": "leftHandGrasp", "state": "leftHandGrasp" },
{ "var": "leftHandPoint", "state": "leftHandPoint" },
{ "var": "leftHandFarGrasp", "state": "leftHandFarGrasp" }
]
},
{ {
"id": "leftHandGrasp", "id": "leftHandGrasp",
"interpTarget": 3, "interpTarget": 3,
"interpDuration": 3, "interpDuration": 3,
"transitions": [ "transitions": []
{ "var": "leftHandOpen", "state": "leftHandOpen" },
{ "var": "leftHandIdle", "state": "leftHandIdle" },
{ "var": "leftHandPoint", "state": "leftHandPoint" },
{ "var": "leftHandFarGrasp", "state": "leftHandFarGrasp" }
]
},
{
"id": "leftHandPoint",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "leftHandOpen", "state": "leftHandOpen" },
{ "var": "leftHandIdle", "state": "leftHandIdle" },
{ "var": "leftHandGrasp", "state": "leftHandGrasp" },
{ "var": "leftHandFarGrasp", "state": "leftHandFarGrasp" }
]
},
{
"id": "leftHandFarGrasp",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "leftHandOpen", "state": "leftHandOpen" },
{ "var": "leftHandIdle", "state": "leftHandIdle" },
{ "var": "leftHandGrasp", "state": "leftHandGrasp" },
{ "var": "leftHandPoint", "state": "leftHandPoint" }
]
} }
] ]
}, },
"children": [ "children": [
{
"id": "leftHandIdle",
"type": "clip",
"data": {
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_left_hand.fbx",
"startFrame": 0.0,
"endFrame": 0.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "leftHandOpen",
"type": "clip",
"data": {
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/hand_anims/search_left.fbx",
"startFrame": 0.0,
"endFrame": 0.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{ {
"id": "leftHandGrasp", "id": "leftHandGrasp",
"type": "clip", "type": "blendLinear",
"data": { "data": {
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/hand_anims/grasp_left.fbx", "alpha": 0.0,
"startFrame": 0.0, "alphaVar": "leftHandGraspAlpha"
"endFrame": 0.0,
"timeScale": 1.0,
"loopFlag": true
}, },
"children": [] "children": [
}, {
{ "id": "leftHandGraspOpen",
"id": "leftHandPoint", "type": "clip",
"type": "clip", "data": {
"data": { "url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/hand_anims/hydra_pose_open_left.fbx",
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_left_hand.fbx", "startFrame": 0.0,
"startFrame": 12.0, "endFrame": 0.0,
"endFrame": 12.0, "timeScale": 1.0,
"timeScale": 1.0, "loopFlag": true
"loopFlag": true },
}, "children": []
"children": [] },
}, {
{ "id": "leftHandGraspClosed",
"id": "leftHandFarGrasp", "type": "clip",
"type": "clip", "data": {
"data": { "url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/hand_anims/hydra_pose_closed_left.fbx",
"startFrame": 10.0,
"url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/hand_anims/far_grasp_left.fbx", "endFrame": 10.0,
"startFrame": 0.0, "timeScale": 1.0,
"endFrame": 0.0, "loopFlag": true
"timeScale": 1.0, },
"loopFlag": true "children": []
}, }
"children": [] ]
} }
] ]
}, },

View file

@ -86,7 +86,7 @@ Hifi.AvatarInputs {
id: controls id: controls
width: root.mirrorWidth width: root.mirrorWidth
height: 44 height: 44
visible: !root.isHMD visible: root.showAudioTools
anchors.top: mirror.bottom anchors.top: mirror.bottom
Rectangle { Rectangle {

View file

@ -10,18 +10,22 @@ function findChild(item, name) {
return null; return null;
} }
function findParent(item, name) { function findParentMatching(item, predicate) {
while (item) { while (item) {
if (item.objectName === name) { if (predicate(item)) {
return item; break;
} }
item = item.parent; item = item.parent;
} }
return null; return item;
} }
function getDesktop(item) { function findParentByName(item, name) {
return findParent(item, OFFSCREEN_ROOT_OBJECT_NAME); return findParentMatching(item, function(item) {
var testName = name;
var result = (item.name === testName);
return result;
});
} }
function findRootMenu(item) { function findRootMenu(item) {
@ -29,6 +33,13 @@ function findRootMenu(item) {
return item ? item.rootMenu : null; return item ? item.rootMenu : null;
} }
function isDesktop(item) {
return item.desktopRoot;
}
function isTopLevelWindow(item) {
return item.topLevelWindow;
}
function getTopLevelWindows(item) { function getTopLevelWindows(item) {
var desktop = getDesktop(item); var desktop = getDesktop(item);
@ -40,8 +51,7 @@ function getTopLevelWindows(item) {
for (var i = 0; i < desktop.children.length; ++i) { for (var i = 0; i < desktop.children.length; ++i) {
var child = desktop.children[i]; var child = desktop.children[i];
if ((Global.OFFSCREEN_WINDOW_OBJECT_NAME === child.objectName) || if (isTopLevelWindow(child)) {
child[Global.OFFSCREEN_WINDOW_OBJECT_NAME]) {
var windowId = child.toString(); var windowId = child.toString();
currentWindows.push(child) currentWindows.push(child)
} }
@ -50,9 +60,12 @@ function getTopLevelWindows(item) {
} }
function getDesktop(item) {
return findParentMatching(item, isDesktop);
}
function getDesktopWindow(item) { function getDesktopWindow(item) {
item = findParent(item, OFFSCREEN_WINDOW_OBJECT_NAME); return findParentMatching(item, isTopLevelWindow)
return item;
} }
function closeWindow(item) { function closeWindow(item) {
@ -142,7 +155,7 @@ function raiseWindow(item) {
var desktop = getDesktop(targetWindow); var desktop = getDesktop(targetWindow);
if (!desktop) { if (!desktop) {
//console.warn("Could not find desktop for window " + targetWindow); console.warn("Could not find desktop for window " + targetWindow);
return; return;
} }

View file

@ -2,86 +2,26 @@ import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtWebEngine 1.1 import QtWebEngine 1.1
import "controls" import "windows" as Windows
import "controls" as Controls
import "styles" import "styles"
VrDialog { Windows.Window {
id: root id: root
HifiConstants { id: hifi } HifiConstants { id: hifi }
title: "WebWindow" title: "WebWindow"
resizable: true resizable: true
enabled: false
visible: false visible: false
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false destroyOnCloseButton: false
contentImplicitWidth: clientArea.implicitWidth property alias source: webview.url
contentImplicitHeight: clientArea.implicitHeight
backgroundColor: "#7f000000"
property url source: "about:blank"
signal navigating(string url) function raiseWindow() { Desktop.raise(root) }
function stop() {
webview.stop(); Controls.WebView {
id: webview
url: "about:blank"
anchors.fill: parent
focus: true
} }
Component.onCompleted: {
// Ensure the JS from the web-engine makes it to our logging
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
});
}
Item {
id: clientArea
implicitHeight: 600
implicitWidth: 800
x: root.clientX
y: root.clientY
width: root.clientWidth
height: root.clientHeight
WebEngineView {
id: webview
url: root.source
anchors.fill: parent
focus: true
property var originalUrl
property var lastFixupTime: 0
onUrlChanged: {
var currentUrl = url.toString();
var newUrl = urlHandler.fixupUrl(currentUrl).toString();
if (newUrl != currentUrl) {
var now = new Date().valueOf();
if (url === originalUrl && (now - lastFixupTime < 100)) {
console.warn("URL fixup loop detected")
return;
}
originalUrl = url
lastFixupTime = now
url = newUrl;
}
}
onLoadingChanged: {
// Required to support clicking on "hifi://" links
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var url = loadRequest.url.toString();
if (urlHandler.canHandleUrl(url)) {
if (urlHandler.handleUrl(url)) {
webview.stop();
}
}
}
}
profile: WebEngineProfile {
id: webviewProfile
httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)"
storageName: "qmlWebEngine"
}
}
} // item
} // dialog } // dialog

View file

@ -5,52 +5,32 @@ import QtWebChannel 1.0
import QtWebSockets 1.0 import QtWebSockets 1.0
import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel
import "Global.js" as Global import "windows" as Windows
import "controls" import "controls"
import "styles" import "styles"
VrDialog { Windows.Window {
id: root id: root
HifiConstants { id: hifi } HifiConstants { id: hifi }
title: "QmlWindow" title: "QmlWindow"
resizable: true resizable: true
enabled: false
visible: false visible: false
focus: true focus: true
property var channel; property var channel;
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false destroyOnCloseButton: false
contentImplicitWidth: clientArea.implicitWidth
contentImplicitHeight: clientArea.implicitHeight
property alias source: pageLoader.source property alias source: pageLoader.source
function raiseWindow() { function raiseWindow() { Desktop.raise(root) }
Global.raiseWindow(root)
}
Item { Loader {
id: clientArea id: pageLoader
implicitHeight: 600 objectName: "Loader"
implicitWidth: 800
x: root.clientX
y: root.clientY
width: root.clientWidth
height: root.clientHeight
focus: true focus: true
clip: true property var dialog: root
Loader {
id: pageLoader
objectName: "Loader"
anchors.fill: parent
focus: true
property var dialog: root
Keys.onPressed: { Keys.onPressed: {
console.log("QmlWindow pageLoader keypress") console.log("QmlWindow pageLoader keypress")
}
} }
} // item }
} // dialog } // dialog

View file

@ -7,22 +7,25 @@ import "Global.js" as Global
// windows will be childed. // windows will be childed.
Item { Item {
id: desktop id: desktop
objectName: Global.OFFSCREEN_ROOT_OBJECT_NAME
anchors.fill: parent; anchors.fill: parent;
onParentChanged: forceActiveFocus();
// Allows QML/JS to find the desktop through the parent chain
property bool desktopRoot: true
// The VR version of the primary menu
property var rootMenu: Menu { objectName: "rootMenu" }
// List of all top level windows
property var windows: []; property var windows: [];
property var rootMenu: Menu { onChildrenChanged: windows = Global.getTopLevelWindows(desktop);
objectName: "rootMenu"
}
onChildrenChanged: { // The tool window, one instance
windows = Global.getTopLevelWindows(desktop); property alias toolWindow: toolWindow
} ToolWindow { id: toolWindow }
onParentChanged: {
forceActiveFocus();
}
function raise(item) { function raise(item) {
Global.raiseWindow(item); Global.raiseWindow(item);
} }
} }

View file

@ -0,0 +1,127 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtWebEngine 1.1
import Qt.labs.settings 1.0
import "windows" as Windows
import "controls" as Controls
Windows.Window {
id: toolWindow
resizable: true
objectName: "ToolWindow"
destroyOnCloseButton: false
destroyOnInvisible: false
closable: false
visible: false
property string newTabSource
property alias tabView: tabView
onParentChanged: {
x = desktop.width / 2 - width / 2;
y = desktop.height / 2 - height / 2;
}
Settings {
category: "ToolWindow.Position"
property alias x: toolWindow.x
property alias y: toolWindow.y
}
property var webTabCreator: Component {
Controls.WebView {
id: webView
property string originalUrl;
// Both toolWindow.newTabSource and url can change, so we need
// to store the original url here, without creating any bindings
Component.onCompleted: {
originalUrl = toolWindow.newTabSource;
url = originalUrl;
}
}
}
TabView {
id: tabView; width: 384; height: 640;
onCountChanged: {
if (0 == count) {
toolWindow.visible = false
}
}
}
function updateVisiblity() {
var newVisible = false
console.log("Checking " + tabView.count + " children")
for (var i = 0; i < tabView.count; ++i) {
if (tabView.getTab(i).enabled) {
console.log("Tab " + i + " enabled");
newVisible = true;
break;
}
}
console.log("Setting toolWindow visible: " + newVisible);
visible = newVisible
}
function findIndexForUrl(source) {
for (var i = 0; i < tabView.count; ++i) {
var tab = tabView.getTab(i);
if (tab && tab.item && tab.item.originalUrl &&
tab.item.originalUrl === source) {
return i;
}
}
return -1;
}
function removeTabForUrl(source) {
var index = findIndexForUrl(source);
if (index < 0) {
console.warn("Could not find tab for " + source);
return;
}
tabView.removeTab(index);
console.log("Updating visibility based on child tab removed");
updateVisiblity();
}
function addWebTab(properties) {
if (!properties.source) {
console.warn("Attempted to open Web Tool Pane without URl")
return;
}
var existingTabIndex = findIndexForUrl(properties.source);
if (existingTabIndex >= 0) {
console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source)
return tabView.getTab(existingTabIndex);
}
var title = properties.title || "Unknown";
newTabSource = properties.source;
console.log(typeof(properties.source))
var newTab = tabView.addTab(title, webTabCreator);
newTab.active = true;
newTab.enabled = false;
if (properties.width) {
tabView.width = Math.min(Math.max(tabView.width, properties.width),
toolWindow.maxSize.x);
}
if (properties.height) {
tabView.height = Math.min(Math.max(tabView.height, properties.height),
toolWindow.maxSize.y);
}
console.log("Updating visibility based on child tab added");
newTab.enabledChanged.connect(function() {
console.log("Updating visibility based on child tab enabled change");
updateVisiblity();
})
updateVisiblity();
return newTab
}
}

View file

@ -3,8 +3,6 @@ import QtQuick.Controls 1.2
import "." import "."
import "../styles" import "../styles"
import "../Global.js" as Global
/* /*
* FIXME Need to create a client property here so that objects can be * FIXME Need to create a client property here so that objects can be
* placed in it without having to think about positioning within the outer * placed in it without having to think about positioning within the outer
@ -48,7 +46,7 @@ DialogBase {
if (enabled) { if (enabled) {
visible = true; visible = true;
if (root.parent) { if (root.parent) {
Global.raiseWindow(root); Desktop.raise(root);
} }
} }
} }

View file

@ -0,0 +1,50 @@
import QtQuick 2.5
import QtWebEngine 1.1
WebEngineView {
id: root
property var originalUrl
property int lastFixupTime: 0
Component.onCompleted: {
console.log("Connecting JS messaging to Hifi Logging")
// Ensure the JS from the web-engine makes it to our logging
root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
});
}
onUrlChanged: {
var currentUrl = url.toString();
var newUrl = urlHandler.fixupUrl(currentUrl).toString();
if (newUrl != currentUrl) {
var now = new Date().valueOf();
if (url === originalUrl && (now - lastFixupTime < 100)) {
console.warn("URL fixup loop detected")
return;
}
originalUrl = url
lastFixupTime = now
url = newUrl;
}
}
onLoadingChanged: {
// Required to support clicking on "hifi://" links
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var url = loadRequest.url.toString();
if (urlHandler.canHandleUrl(url)) {
if (urlHandler.handleUrl(url)) {
root.stop();
}
}
}
}
profile: WebEngineProfile {
id: webviewProfile
httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)"
storageName: "qmlWebEngine"
}
}

View file

@ -0,0 +1,232 @@
import QtQuick 2.0
import QtQuick.Controls 1.4
import Qt.labs.folderlistmodel 2.1
import Qt.labs.settings 1.0
import ".."
import "."
// Work in progress....
DialogBase {
id: root
Constants { id: vr }
property string settingsName: ""
signal selectedFile(var file);
signal canceled();
function selectCurrentFile() {
var row = tableView.currentRow
console.log("Selecting row " + row)
var fileName = folderModel.get(row, "fileName");
if (fileName === "..") {
folderModel.folder = folderModel.parentFolder
} else if (folderModel.isFolder(row)) {
folderModel.folder = folderModel.get(row, "fileURL");
} else {
selectedFile(folderModel.get(row, "fileURL"));
enabled = false
}
}
property var folderModel: FolderListModel {
id: folderModel
showDotAndDotDot: true
showDirsFirst: true
folder: "file:///C:/";
onFolderChanged: tableView.currentRow = 0
}
property var filterModel: ListModel {
ListElement {
text: "All Files (*.*)"
filter: "*"
}
ListElement {
text: "Javascript Files (*.js)"
filter: "*.js"
}
ListElement {
text: "QML Files (*.qml)"
filter: "*.qml"
}
}
Settings {
// fixme, combine with a property to allow different saved locations
category: "FileOpenLastFolder." + settingsName
property alias folder: folderModel.folder
}
Rectangle {
id: currentDirectoryWrapper
anchors.left: parent.left
anchors.leftMargin: 8
anchors.right: parent.right
anchors.rightMargin: 8
anchors.top: parent.top
anchors.topMargin: 8
height: currentDirectory.implicitHeight + 8
radius: vr.styles.radius
color: vr.controls.colors.inputBackground
TextEdit {
enabled: false
id: currentDirectory
text: folderModel.folder
anchors {
fill: parent
leftMargin: 8
rightMargin: 8
topMargin: 4
bottomMargin: 4
}
}
}
TableView {
id: tableView
focus: true
model: folderModel
anchors.top: currentDirectoryWrapper.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: selectionType.top
anchors.margins: 8
itemDelegate: Item {
Text {
anchors.verticalCenter: parent.verticalCenter
font.family: vr.fonts.lightFontName
font.pointSize: 12
color: tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor
elide: styleData.elideMode
text: getText();
function getText() {
switch (styleData.column) {
//case 1: return Date.fromLocaleString(locale, styleData.value, "yyyy-MM-dd hh:mm:ss");
case 2: return folderModel.get(styleData.row, "fileIsDir") ? "" : formatSize(styleData.value);
default: return styleData.value;
}
}
function formatSize(size) {
var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
var suffixIndex = 0
while ((size / 1024.0) > 1.1) {
size /= 1024.0;
++suffixIndex;
}
size = Math.round(size*1000)/1000;
size = size.toLocaleString()
return size + " " + suffixes[suffixIndex];
}
}
}
TableViewColumn {
role: "fileName"
title: "Name"
width: 400
}
TableViewColumn {
role: "fileModified"
title: "Date Modified"
width: 200
}
TableViewColumn {
role: "fileSize"
title: "Size"
width: 200
}
function selectItem(row) {
selectCurrentFile()
}
Keys.onReturnPressed: selectCurrentFile();
onDoubleClicked: { currentRow = row; selectCurrentFile(); }
onCurrentRowChanged: currentSelection.text = model.get(currentRow, "fileName");
KeyNavigation.left: cancelButton
KeyNavigation.right: selectionType
}
Rectangle {
anchors.right: selectionType.left
anchors.rightMargin: 8
anchors.top: selectionType.top
anchors.bottom: selectionType.bottom
anchors.left: parent.left
anchors.leftMargin: 8
radius: 8
color: "#eee"
Text {
id: currentSelection
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 8
anchors.verticalCenter: parent.verticalCenter
font.family: vr.fonts.mainFontName
font.pointSize: 18
}
}
ComboBox {
id: selectionType
anchors.bottom: buttonRow.top
anchors.bottomMargin: 8
anchors.right: parent.right
anchors.rightMargin: 8
anchors.left: buttonRow.left
model: filterModel
onCurrentIndexChanged: folderModel.nameFilters = [
filterModel.get(currentIndex).filter
]
KeyNavigation.left: tableView
KeyNavigation.right: openButton
}
Row {
id: buttonRow
anchors.right: parent.right
anchors.rightMargin: 8
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
layoutDirection: Qt.RightToLeft
spacing: 8
Button {
id: cancelButton
text: "Cancel"
KeyNavigation.up: selectionType
KeyNavigation.left: openButton
KeyNavigation.right: tableView.contentItem
Keys.onReturnPressed: { canceled(); root.enabled = false }
onClicked: { canceled(); root.enabled = false }
}
Button {
id: openButton
text: "Open"
enabled: tableView.currentRow != -1 && !folderModel.get(tableView.currentRow, "fileIsDir")
KeyNavigation.up: selectionType
KeyNavigation.left: selectionType
KeyNavigation.right: cancelButton
onClicked: selectCurrentFile();
Keys.onReturnPressed: selectedFile(folderModel.get(tableView.currentRow, "fileURL"))
}
}
Keys.onPressed: {
if (event.key === Qt.Key_Backspace && folderModel.parentFolder && folderModel.parentFolder != "") {
console.log("Navigating to " + folderModel.parentFolder)
folderModel.folder = folderModel.parentFolder
event.accepted = true
}
}
}

View file

@ -0,0 +1,311 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
// Stubs for the global service objects set by Interface.cpp when creating the UI
// This is useful for testing inside Qt creator where these services don't actually exist.
Item {
Item {
objectName: "urlHandler"
function fixupUrl(url) { return url; }
function canHandleUrl(url) { return false; }
function handleUrl(url) { return true; }
}
Item {
objectName: "Account"
function isLoggedIn() { return true; }
function getUsername() { return "Jherico"; }
}
Item {
objectName: "ScriptDiscoveryService"
property var scriptsModelFilter: scriptsModel
signal scriptCountChanged()
property var _runningScripts:[
{ name: "wireFrameTest.js", url: "foo/wireframetest.js", path: "foo/wireframetest.js", local: true },
{ name: "edit.js", url: "foo/edit.js", path: "foo/edit.js", local: false },
{ name: "listAllScripts.js", url: "foo/listAllScripts.js", path: "foo/listAllScripts.js", local: false },
{ name: "users.js", url: "foo/users.js", path: "foo/users.js", local: false },
]
function getRunning() {
return _runningScripts;
}
}
Item {
id: menuHelper
objectName: "MenuHelper"
Component {
id: modelMaker
ListModel { }
}
function toModel(menu, parent) {
if (!parent) { parent = menuHelper }
var result = modelMaker.createObject(parent);
if (menu.type !== MenuItemType.Menu) {
console.warn("Not a menu: " + menu);
return result;
}
var items = menu.items;
for (var i = 0; i < items.length; ++i) {
var item = items[i];
switch (item.type) {
case 2:
result.append({"name": item.title, "item": item})
break;
case 1:
result.append({"name": item.text, "item": item})
break;
case 0:
result.append({"name": "", "item": item})
break;
}
}
return result;
}
}
Item {
objectName: "Desktop"
property string _OFFSCREEN_ROOT_OBJECT_NAME: "desktopRoot";
property string _OFFSCREEN_DIALOG_OBJECT_NAME: "topLevelWindow";
function findChild(item, name) {
for (var i = 0; i < item.children.length; ++i) {
if (item.children[i].objectName === name) {
return item.children[i];
}
}
return null;
}
function findParent(item, name) {
while (item) {
if (item.objectName === name) {
return item;
}
item = item.parent;
}
return null;
}
function findDialog(item) {
item = findParent(item, _OFFSCREEN_DIALOG_OBJECT_NAME);
return item;
}
function closeDialog(item) {
item = findDialog(item);
if (item) {
item.enabled = false
} else {
console.warn("Could not find top level dialog")
}
}
function getDesktop(item) {
while (item) {
if (item.desktopRoot) {
break;
}
item = item.parent;
}
return item
}
function raise(item) {
var desktop = getDesktop(item);
if (desktop) {
desktop.raise(item);
}
}
}
Menu {
id: root
objectName: "rootMenu"
Menu {
title: "Audio"
}
Menu {
title: "Avatar"
}
Menu {
title: "Display"
ExclusiveGroup { id: displayMode }
Menu {
title: "More Stuff"
Menu { title: "Empty" }
MenuItem {
text: "Do Nothing"
onTriggered: console.log("Nothing")
}
}
MenuItem {
text: "Oculus"
exclusiveGroup: displayMode
checkable: true
}
MenuItem {
text: "OpenVR"
exclusiveGroup: displayMode
checkable: true
}
MenuItem {
text: "OSVR"
exclusiveGroup: displayMode
checkable: true
}
MenuItem {
text: "2D Screen"
exclusiveGroup: displayMode
checkable: true
checked: true
}
MenuItem {
text: "3D Screen (Active)"
exclusiveGroup: displayMode
checkable: true
}
MenuItem {
text: "3D Screen (Passive)"
exclusiveGroup: displayMode
checkable: true
}
}
Menu {
title: "View"
Menu {
title: "Camera Mode"
ExclusiveGroup { id: cameraMode }
MenuItem {
exclusiveGroup: cameraMode
text: "First Person";
onTriggered: console.log(text + " checked " + checked)
checkable: true
checked: true
}
MenuItem {
exclusiveGroup: cameraMode
text: "Third Person";
onTriggered: console.log(text)
checkable: true
}
MenuItem {
exclusiveGroup: cameraMode
text: "Independent Mode";
onTriggered: console.log(text)
checkable: true
}
MenuItem {
exclusiveGroup: cameraMode
text: "Entity Mode";
onTriggered: console.log(text)
enabled: false
checkable: true
}
MenuItem {
exclusiveGroup: cameraMode
text: "Fullscreen Mirror";
onTriggered: console.log(text)
checkable: true
}
}
}
Menu {
title: "Edit"
MenuItem {
text: "Undo"
shortcut: "Ctrl+Z"
enabled: false
onTriggered: console.log(text)
}
MenuItem {
text: "Redo"
shortcut: "Ctrl+Shift+Z"
enabled: false
onTriggered: console.log(text)
}
MenuSeparator { }
MenuItem {
text: "Cut"
shortcut: "Ctrl+X"
onTriggered: console.log(text)
}
MenuItem {
text: "Copy"
shortcut: "Ctrl+C"
onTriggered: console.log(text)
}
MenuItem {
text: "Paste"
shortcut: "Ctrl+V"
visible: false
onTriggered: console.log("Paste")
}
}
Menu {
title: "Navigate"
}
Menu {
title: "Market"
}
Menu {
title: "Settings"
}
Menu {
title: "Developer"
}
Menu {
title: "Quit"
}
Menu {
title: "File"
Action {
id: login
text: "Login"
}
Action {
id: quit
text: "Quit"
shortcut: "Ctrl+Q"
onTriggered: Qt.quit();
}
MenuItem { action: quit }
MenuItem { action: login }
}
}
}

View file

@ -4,90 +4,134 @@ import "."
import "../controls" import "../controls"
Frame { Frame {
id: root id: frame
anchors { margins: -16; topMargin: parent.closable ? -32 : -16; } // The frame fills the parent, which should be the size of the content.
// The frame decorations use negative anchor margins to extend beyond
anchors.fill: parent
// Size of the controls
readonly property real iconSize: 24;
// Convenience accessor for the window
property alias window: frame.parent
// FIXME needed?
property alias decoration: decoration
MouseArea { Rectangle {
id: controlsArea anchors { margins: -4 }
anchors.fill: desktopControls visible: !decoration.visible
hoverEnabled: true anchors.fill: parent;
drag.target: root.parent color: "#7f7f7f7f";
propagateComposedEvents: true radius: 3;
onClicked: {
root.raise()
mouse.accepted = false;
}
MouseArea {
id: sizeDrag
enabled: root.parent.resizable
property int startX
property int startY
anchors.right: parent.right
anchors.bottom: parent.bottom
width: 16
height: 16
z: 1000
hoverEnabled: true
onPressed: {
startX = mouseX
startY = mouseY
}
onPositionChanged: {
if (pressed) {
root.deltaSize((mouseX - startX), (mouseY - startY))
startX = mouseX
startY = mouseY
}
}
}
} }
Rectangle { Rectangle {
id: desktopControls id: decoration
// FIXME this doesn't work as expected anchors { margins: -iconSize; topMargin: -iconSize * (window.closable ? 2 : 1); }
visible: root.parent.showFrame // FIXME doesn't work
// visible: window.activator.containsMouse
anchors.fill: parent; anchors.fill: parent;
color: "#7f7f7f7f"; color: "#7f7f7f7f";
radius: 3; radius: 3;
// Allow dragging of the window
MouseArea {
id: dragMouseArea
anchors.fill: parent
drag {
target: window
// minimumX: (decoration.width - window.width) * -1
// minimumY: 0
// maximumX: (window.parent.width - window.width) - 2 * (decoration.width - window.width)
// maximumY: (window.parent.height - window.height) - 2 * (decoration.height - window.height)
}
}
Row { Row {
id: controlsRow
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.rightMargin: 4 anchors.rightMargin: iconSize
anchors.topMargin: 4 anchors.topMargin: iconSize / 2
spacing: 4 spacing: iconSize / 4
FontAwesome { FontAwesome {
visible: false visible: false
text: "\uf08d" text: "\uf08d"
style: Text.Outline; styleColor: "white" style: Text.Outline; styleColor: "white"
size: 16 size: frame.iconSize
rotation: !root.parent ? 90 : root.parent.pinned ? 0 : 90 rotation: !frame.parent ? 90 : frame.parent.pinned ? 0 : 90
color: root.pinned ? "red" : "black" color: frame.pinned ? "red" : "black"
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
propagateComposedEvents: true propagateComposedEvents: true
onClicked: { root.pin(); mouse.accepted = false; } onClicked: { frame.pin(); mouse.accepted = false; }
} }
} }
FontAwesome { FontAwesome {
visible: root.parent.closable visible: window.closable
text: closeClickArea.containsMouse ? "\uf057" : "\uf05c" text: closeClickArea.containsMouse ? "\uf057" : "\uf05c"
style: Text.Outline; style: Text.Outline;
styleColor: "white" styleColor: "white"
color: closeClickArea.containsMouse ? "red" : "black" color: closeClickArea.containsMouse ? "red" : "black"
size: 16 size: frame.iconSize
MouseArea { MouseArea {
id: closeClickArea id: closeClickArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onClicked: root.close(); onClicked: frame.close();
} }
} }
} }
// Allow sizing of the window
// FIXME works in native QML, doesn't work in Interface
MouseArea {
id: sizeDrag
width: iconSize
height: iconSize
anchors {
right: decoration.right;
bottom: decoration.bottom
bottomMargin: iconSize * 2
}
property vector2d pressOrigin
property vector2d sizeOrigin
property bool hid: false
onPressed: {
console.log("Pressed on size")
pressOrigin = Qt.vector2d(mouseX, mouseY)
sizeOrigin = Qt.vector2d(window.content.width, window.content.height)
hid = false;
}
onReleased: {
if (hid) {
window.content.visible = true
hid = false;
}
}
onPositionChanged: {
if (pressed) {
if (window.content.visible) {
window.content.visible = false;
hid = true;
}
var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin);
frame.deltaSize(delta.x, delta.y)
}
}
}
FontAwesome {
visible: window.resizable
rotation: -45
anchors { centerIn: sizeDrag }
horizontalAlignment: Text.AlignHCenter
text: "\uf07d"
size: iconSize / 3 * 2
style: Text.Outline; styleColor: "white"
}
} }
} }

View file

@ -6,78 +6,75 @@ import "../styles"
FocusScope { FocusScope {
id: window id: window
objectName: "topLevelWindow"
HifiConstants { id: hifi } HifiConstants { id: hifi }
implicitHeight: frame.height // The Window size is the size of the content, while the frame
implicitWidth: frame.width // decorations can extend outside it. Windows should generally not be
// given explicit height / width, but rather be allowed to conform to
// their content
implicitHeight: content.height
implicitWidth: content.width
property bool topLevelWindow: true property bool topLevelWindow: true
property string title property string title
property bool showFrame: true // Should the window include a close control?
property bool closable: true property bool closable: true
property bool destroyOnInvisible: false // Should hitting the close button hide or destroy the window?
property bool destroyOnCloseButton: true property bool destroyOnCloseButton: true
property bool pinned: false // Should hiding the window destroy it or just hide it?
property bool destroyOnInvisible: false
// FIXME support for pinned / unpinned pending full design
// property bool pinnable: false
// property bool pinned: false
property bool resizable: false property bool resizable: false
property real minX: 320 property vector2d minSize: Qt.vector2d(100, 100)
property real minY: 240; property vector2d maxSize: Qt.vector2d(1280, 720)
// The content to place inside the window, determined by the client
default property var content default property var content
property var frame: DefaultFrame { anchors.fill: content }
property var blur: FastBlur { anchors.fill: content; source: content; visible: false; radius: 0}
//property var hoverDetector: MouseArea { anchors.fill: frame; hoverEnabled: true; propagateComposedEvents: true; }
//property bool mouseInWindow: hoverDetector.containsMouse
children: [ frame, content, blur ]
signal windowDestroyed();
QtObject {
id: d
property vector2d minPosition: Qt.vector2d(0, 0);
property vector2d maxPosition: Qt.vector2d(100, 100);
function clamp(value, min, max) { onContentChanged: {
return Math.min(Math.max(value, min), max); if (content) {
content.anchors.fill = window
} }
function updateParentRect() {
// if (!frame) { return; }
// console.log(window.parent.width);
// console.log(frame.width);
// minPosition = Qt.vector2d(-frame.anchors.leftMargin, -frame.anchors.topMargin);
// maxPosition = Qt.vector2d(
// Math.max(minPosition.x, Desktop.width - frame.width + minPosition.x),
// Math.max(minPosition.y, Desktop.height - frame.height + minPosition.y))
// console.log(maxPosition);
}
function keepOnScreen() {
//window.x = clamp(x, minPosition.x, maxPosition.x);
//window.y = clamp(y, minPosition.y, maxPosition.y);
}
onMinPositionChanged: keepOnScreen();
onMaxPositionChanged: keepOnScreen();
} }
// Default to a standard frame. Can be overriden to provide custom
// frame styles, like a full desktop frame to simulate a modal window
property var frame: DefaultFrame {
z: -1
anchors.fill: parent
}
// This mouse area serves to raise the window. To function, it must live
// in the window and have a higher Z-order than the content, but follow
// the position and size of frame decoration
property var activator: MouseArea {
width: frame.decoration.width
height: frame.decoration.height
x: frame.decoration.anchors.margins
y: frame.decoration.anchors.topMargin
propagateComposedEvents: true
hoverEnabled: true
onPressed: { window.raise(); mouse.accepted = false; }
// Debugging visualization
// Rectangle { anchors.fill:parent; color: "#7f00ff00" }
}
children: [ frame, content, activator ]
signal windowDestroyed();
Component.onCompleted: { Component.onCompleted: {
d.updateParentRect(); fadeTargetProperty = visible ? 1.0 : 0.0
raise(); raise();
} }
Component.onDestruction: { Component.onDestruction: {
content.destroy();
console.log("Destroyed " + window); console.log("Destroyed " + window);
windowDestroyed(); windowDestroyed();
} }
onParentChanged: raise();
onParentChanged: {
d.updateParentRect();
raise();
}
onFrameChanged: d.updateParentRect();
onWidthChanged: d.updateParentRect();
onHeightChanged: d.updateParentRect();
onXChanged: d.keepOnScreen();
onYChanged: d.keepOnScreen();
Connections { Connections {
target: frame target: frame
@ -85,15 +82,15 @@ FocusScope {
onClose: window.close(); onClose: window.close();
onPin: window.pin(); onPin: window.pin();
onDeltaSize: { onDeltaSize: {
console.log("deltaSize") var newSize = Qt.vector2d(content.width + dx, content.height + dy);
content.width = Math.max(content.width + dx, minX) newSize = clampVector(newSize, minSize, maxSize);
content.height = Math.max(content.height + dy, minY) window.width = newSize.x
window.height = newSize.y
} }
} }
function raise() { function raise() {
if (enabled && parent) { if (visible && parent) {
Desktop.raise(window) Desktop.raise(window)
if (!focus) { if (!focus) {
focus = true; focus = true;
@ -102,48 +99,58 @@ FocusScope {
} }
function pin() { function pin() {
pinned = ! pinned // pinned = ! pinned
} }
// our close function performs the same way as the OffscreenUI class: // our close function performs the same way as the OffscreenUI class:
// don't do anything but manipulate the enabled flag and let the other // don't do anything but manipulate the targetVisible flag and let the other
// mechanisms decide if the window should be destroyed after the close // mechanisms decide if the window should be destroyed after the close
// animation completes // animation completes
function close() { function close() {
console.log("Closing " + window)
if (destroyOnCloseButton) { if (destroyOnCloseButton) {
destroyOnInvisible = true destroyOnInvisible = true
} }
enabled = false; visible = false;
} }
onEnabledChanged: { //
if (!enabled) { // Enable window visibility transitions
if (blur) { //
blur.visible = true;
}
if (content) {
content.visible = false;
}
}
opacity = enabled ? 1.0 : 0.0 // The target property to animate, usually scale or opacity
// If the dialog is initially invisible, setting opacity doesn't property alias fadeTargetProperty: window.opacity
// trigger making it visible. // always start the property at 0 to enable fade in on creation
if (enabled) { opacity: 0
visible = true;
raise(); // Some dialogs should be destroyed when they become
// invisible, so handle that
onVisibleChanged: {
// If someone directly set the visibility to false
// toggle it back on and use the targetVisible flag to transition
// via fading.
if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) {
var target = visible;
visible = !visible;
fadeTargetProperty = target ? 1.0 : 0.0;
return;
}
if (!visible && destroyOnInvisible) {
console.log("Destroying " + window);
destroy();
return;
} }
} }
// The offscreen UI will enable an object, rather than manipulating it's // The offscreen UI will enable an object, rather than manipulating it's
// visibility, so that we can do animations in both directions. Because // visibility, so that we can do animations in both directions. Because
// visibility and enabled are boolean flags, they cannot be animated. So when // visibility is a boolean flags, it cannot be animated. So when
// enabled is change, we modify a property that can be animated, like scale or // targetVisible is changed, we modify a property that can be animated,
// opacity, and then when the target animation value is reached, we can // like scale or opacity, and then when the target animation value is reached,
// modify the visibility // we can modify the visibility
// The actual animator // The actual animator
Behavior on opacity { Behavior on fadeTargetProperty {
NumberAnimation { NumberAnimation {
duration: hifi.effects.fadeInDuration duration: hifi.effects.fadeInDuration
easing.type: Easing.OutCubic easing.type: Easing.OutCubic
@ -151,31 +158,31 @@ FocusScope {
} }
// Once we're transparent, disable the dialog's visibility // Once we're transparent, disable the dialog's visibility
onOpacityChanged: { onFadeTargetPropertyChanged: {
visible = (opacity != 0.0); visible = (fadeTargetProperty != 0.0);
if (opacity == 1.0) {
content.visible = true;
blur.visible = false;
}
} }
// Some dialogs should be destroyed when they become
// invisible, so handle that
onVisibleChanged: {
if (!visible && destroyOnInvisible) {
console.log("Destroying " + window);
destroy();
}
}
Keys.onPressed: { Keys.onPressed: {
switch(event.key) { switch(event.key) {
case Qt.Key_W: case Qt.Key_W:
if (event.modifiers == Qt.ControlModifier) { if (event.modifiers === Qt.ControlModifier) {
event.accepted = true event.accepted = true
enabled = false visible = false
} }
break; break;
} }
} }
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function clampVector(value, min, max) {
return Qt.vector2d(
clamp(value.x, min.x, max.x),
clamp(value.y, min.y, max.y))
}
} }

View file

@ -395,7 +395,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
QApplication(argc, argv), QApplication(argc, argv),
_dependencyManagerIsSetup(setupEssentials(argc, argv)), _dependencyManagerIsSetup(setupEssentials(argc, argv)),
_window(new MainWindow(desktop())), _window(new MainWindow(desktop())),
_toolWindow(NULL),
_undoStackScriptingInterface(&_undoStack), _undoStackScriptingInterface(&_undoStack),
_frameCount(0), _frameCount(0),
_fps(60.0f), _fps(60.0f),
@ -678,10 +677,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_renderEngine->addTask(make_shared<RenderDeferredTask>()); _renderEngine->addTask(make_shared<RenderDeferredTask>());
_renderEngine->registerScene(_main3DScene); _renderEngine->registerScene(_main3DScene);
_toolWindow = new ToolWindow();
_toolWindow->setWindowFlags((_toolWindow->windowFlags() | Qt::WindowStaysOnTopHint) & ~Qt::WindowMinimizeButtonHint);
_toolWindow->setWindowTitle("Tools");
_offscreenContext->makeCurrent(); _offscreenContext->makeCurrent();
// Tell our entity edit sender about our known jurisdictions // Tell our entity edit sender about our known jurisdictions
@ -4766,7 +4761,7 @@ void Application::showFriendsWindow() {
auto webWindowClass = _window->findChildren<WebWindowClass>(FRIENDS_WINDOW_OBJECT_NAME); auto webWindowClass = _window->findChildren<WebWindowClass>(FRIENDS_WINDOW_OBJECT_NAME);
if (webWindowClass.empty()) { if (webWindowClass.empty()) {
auto friendsWindow = new WebWindowClass(FRIENDS_WINDOW_TITLE, FRIENDS_WINDOW_URL, FRIENDS_WINDOW_WIDTH, auto friendsWindow = new WebWindowClass(FRIENDS_WINDOW_TITLE, FRIENDS_WINDOW_URL, FRIENDS_WINDOW_WIDTH,
FRIENDS_WINDOW_HEIGHT, false); FRIENDS_WINDOW_HEIGHT);
friendsWindow->setParent(_window); friendsWindow->setParent(_window);
friendsWindow->setObjectName(FRIENDS_WINDOW_OBJECT_NAME); friendsWindow->setObjectName(FRIENDS_WINDOW_OBJECT_NAME);
connect(friendsWindow, &WebWindowClass::closed, &WebWindowClass::deleteLater); connect(friendsWindow, &WebWindowClass::closed, &WebWindowClass::deleteLater);

View file

@ -62,7 +62,6 @@
#include "ui/OverlayConductor.h" #include "ui/OverlayConductor.h"
#include "ui/overlays/Overlays.h" #include "ui/overlays/Overlays.h"
#include "ui/SnapshotShareDialog.h" #include "ui/SnapshotShareDialog.h"
#include "ui/ToolWindow.h"
#include "UndoStackScriptingInterface.h" #include "UndoStackScriptingInterface.h"
class OffscreenGLCanvas; class OffscreenGLCanvas;
@ -164,8 +163,6 @@ public:
NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; } NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
ToolWindow* getToolWindow() { return _toolWindow ; }
virtual controller::ScriptingInterface* getControllerScriptingInterface() { return _controllerScriptingInterface; } virtual controller::ScriptingInterface* getControllerScriptingInterface() { return _controllerScriptingInterface; }
virtual void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine); virtual void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine);
@ -400,8 +397,6 @@ private:
MainWindow* _window; MainWindow* _window;
ToolWindow* _toolWindow;
QUndoStack _undoStack; QUndoStack _undoStack;
UndoStackScriptingInterface _undoStackScriptingInterface; UndoStackScriptingInterface _undoStackScriptingInterface;

View file

@ -156,9 +156,8 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false, addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false,
audioIO.data(), SLOT(toggleMute())); audioIO.data(), SLOT(toggleMute()));
// Audio > Level Meter [advanced] -- FIXME: needs implementation // Audio > Show Level Meter
auto levelMeterAction = addCheckableActionToQMenuAndActionHash(audioMenu, "Level Meter", 0, false, NULL, NULL, UNSPECIFIED_POSITION, "Advanced"); addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::AudioTools, 0, true);
levelMeterAction->setDisabled(true);
// Avatar menu ---------------------------------- // Avatar menu ----------------------------------
@ -325,8 +324,10 @@ Menu::Menu() {
// Developer > Render >>> // Developer > Render >>>
MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render"); MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render");
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::WorldAxes);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugAmbientOcclusion); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugAmbientOcclusion);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Antialiasing); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Antialiasing);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, 0, true);
// Developer > Render > Ambient Light // Developer > Render > Ambient Light
MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight); MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight);
@ -357,15 +358,8 @@ Menu::Menu() {
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false));
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false));
// Developer > Render > Stars
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars,
0, // QML Qt::Key_Asterisk,
true);
// Developer > Render > LOD Tools // Developer > Render > LOD Tools
addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, dialogsManager.data(), SLOT(lodTools()));
0, // QML Qt::SHIFT | Qt::Key_L,
dialogsManager.data(), SLOT(lodTools()));
// Developer > Assets >>> // Developer > Assets >>>
MenuWrapper* assetDeveloperMenu = developerMenu->addMenu("Assets"); MenuWrapper* assetDeveloperMenu = developerMenu->addMenu("Assets");
@ -498,6 +492,12 @@ Menu::Menu() {
MenuWrapper* leapOptionsMenu = handOptionsMenu->addMenu("Leap Motion"); MenuWrapper* leapOptionsMenu = handOptionsMenu->addMenu("Leap Motion");
addCheckableActionToQMenuAndActionHash(leapOptionsMenu, MenuOption::LeapMotionOnHMD, 0, false); addCheckableActionToQMenuAndActionHash(leapOptionsMenu, MenuOption::LeapMotionOnHMD, 0, false);
// Developer > Entities >>>
MenuWrapper* entitiesOptionsMenu = developerMenu->addMenu("Entities");
addActionToQMenuAndActionHash(entitiesOptionsMenu, MenuOption::OctreeStats, 0,
dialogsManager.data(), SLOT(octreeStatsDetails()));
addCheckableActionToQMenuAndActionHash(entitiesOptionsMenu, MenuOption::ShowRealtimeEntityStats);
// Developer > Network >>> // Developer > Network >>>
MenuWrapper* networkMenu = developerMenu->addMenu("Network"); MenuWrapper* networkMenu = developerMenu->addMenu("Network");
addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
@ -514,12 +514,14 @@ Menu::Menu() {
dialogsManager.data(), SLOT(cachesSizeDialog())); dialogsManager.data(), SLOT(cachesSizeDialog()));
addActionToQMenuAndActionHash(networkMenu, MenuOption::DiskCacheEditor, 0, addActionToQMenuAndActionHash(networkMenu, MenuOption::DiskCacheEditor, 0,
dialogsManager.data(), SLOT(toggleDiskCacheEditor())); dialogsManager.data(), SLOT(toggleDiskCacheEditor()));
addActionToQMenuAndActionHash(networkMenu, MenuOption::ShowDSConnectTable, 0, addActionToQMenuAndActionHash(networkMenu, MenuOption::ShowDSConnectTable, 0,
dialogsManager.data(), SLOT(showDomainConnectionDialog())); dialogsManager.data(), SLOT(showDomainConnectionDialog()));
addActionToQMenuAndActionHash(networkMenu, MenuOption::BandwidthDetails, 0,
dialogsManager.data(), SLOT(bandwidthDetails()));
// Developer > Timing and Stats >>>
MenuWrapper* timingMenu = developerMenu->addMenu("Timing and Stats"); // Developer > Timing >>>
MenuWrapper* timingMenu = developerMenu->addMenu("Timing");
MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer"); MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer");
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true);
@ -534,7 +536,6 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::PipelineWarnings); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::PipelineWarnings);
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::LogExtraTimings); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::LogExtraTimings);
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SuppressShortTimings); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SuppressShortTimings);
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::ShowRealtimeEntityStats);
// Developer > Audio >>> // Developer > Audio >>>
MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio"); MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio");
@ -571,6 +572,10 @@ Menu::Menu() {
audioScopeFramesGroup->addAction(fiftyFrames); audioScopeFramesGroup->addAction(fiftyFrames);
} }
// Developer > Audio > Audio Network Stats...
addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNetworkStats, 0,
dialogsManager.data(), SLOT(audioStatsDetails()));
// Developer > Physics >>> // Developer > Physics >>>
MenuWrapper* physicsOptionsMenu = developerMenu->addMenu("Physics"); MenuWrapper* physicsOptionsMenu = developerMenu->addMenu("Physics");
addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned);
@ -588,22 +593,6 @@ Menu::Menu() {
// Developer > Stats // Developer > Stats
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats);
// Developer > Audio Stats...
addActionToQMenuAndActionHash(developerMenu, MenuOption::AudioNetworkStats, 0,
dialogsManager.data(), SLOT(audioStatsDetails()));
// Developer > Bandwidth Stats...
addActionToQMenuAndActionHash(developerMenu, MenuOption::BandwidthDetails, 0,
dialogsManager.data(), SLOT(bandwidthDetails()));
// Developer > Entity Stats...
addActionToQMenuAndActionHash(developerMenu, MenuOption::OctreeStats, 0,
dialogsManager.data(), SLOT(octreeStatsDetails()));
// Developer > World Axes
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::WorldAxes);
#if 0 /// -------------- REMOVED FOR NOW -------------- #if 0 /// -------------- REMOVED FOR NOW --------------
addDisabledActionAndSeparator(navigateMenu, "History"); addDisabledActionAndSeparator(navigateMenu, "History");

View file

@ -156,6 +156,7 @@ namespace MenuOption {
const QString AssetMigration = "ATP Asset Migration"; const QString AssetMigration = "ATP Asset Migration";
const QString Atmosphere = "Atmosphere"; const QString Atmosphere = "Atmosphere";
const QString Attachments = "Attachments..."; const QString Attachments = "Attachments...";
const QString AudioNetworkStats = "Audio Network Stats";
const QString AudioNoiseReduction = "Audio Noise Reduction"; const QString AudioNoiseReduction = "Audio Noise Reduction";
const QString AudioScope = "Show Scope"; const QString AudioScope = "Show Scope";
const QString AudioScopeFiftyFrames = "Fifty"; const QString AudioScopeFiftyFrames = "Fifty";
@ -163,8 +164,8 @@ namespace MenuOption {
const QString AudioScopeFrames = "Display Frames"; const QString AudioScopeFrames = "Display Frames";
const QString AudioScopePause = "Pause Scope"; const QString AudioScopePause = "Pause Scope";
const QString AudioScopeTwentyFrames = "Twenty"; const QString AudioScopeTwentyFrames = "Twenty";
const QString AudioNetworkStats = "Audio Network Stats";
const QString AudioStatsShowInjectedStreams = "Audio Stats Show Injected Streams"; const QString AudioStatsShowInjectedStreams = "Audio Stats Show Injected Streams";
const QString AudioTools = "Show Level Meter";
const QString AutoMuteAudio = "Auto Mute Microphone"; const QString AutoMuteAudio = "Auto Mute Microphone";
const QString AvatarReceiveStats = "Show Receive Stats"; const QString AvatarReceiveStats = "Show Receive Stats";
const QString Back = "Back"; const QString Back = "Back";

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QApplication> #include <QApplication>
#include <QMainWindow> #include <QMainWindow>
@ -35,48 +36,25 @@ void ScriptEventBridge::emitScriptEvent(const QString& data) {
emit scriptEventReceived(data); emit scriptEventReceived(data);
} }
WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow) WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height)
: QObject(NULL), : QObject(NULL), _eventBridge(new ScriptEventBridge(this)) {
_eventBridge(new ScriptEventBridge(this)), auto dialogWidget = new QDialog(qApp->getWindow(), Qt::Window);
_isToolWindow(isToolWindow) { dialogWidget->setWindowTitle(title);
dialogWidget->resize(width, height);
dialogWidget->installEventFilter(this);
connect(dialogWidget, &QDialog::finished, this, &WebWindowClass::hasClosed);
if (_isToolWindow) { auto layout = new QVBoxLayout(dialogWidget);
ToolWindow* toolWindow = qApp->getToolWindow(); layout->setContentsMargins(0, 0, 0, 0);
dialogWidget->setLayout(layout);
auto dockWidget = new QDockWidget(title, toolWindow); _webView = new QWebView(dialogWidget);
dockWidget->setFeatures(QDockWidget::DockWidgetMovable);
connect(dockWidget, &QDockWidget::visibilityChanged, this, &WebWindowClass::visibilityChanged);
_webView = new QWebView(dockWidget); layout->addWidget(_webView);
addEventBridgeToWindowObject();
dockWidget->setWidget(_webView); addEventBridgeToWindowObject();
auto titleWidget = new QWidget(dockWidget); _windowWidget = dialogWidget;
dockWidget->setTitleBarWidget(titleWidget);
toolWindow->addDockWidget(Qt::TopDockWidgetArea, dockWidget, Qt::Horizontal);
_windowWidget = dockWidget;
} else {
auto dialogWidget = new QDialog(qApp->getWindow(), Qt::Window);
dialogWidget->setWindowTitle(title);
dialogWidget->resize(width, height);
dialogWidget->installEventFilter(this);
connect(dialogWidget, &QDialog::finished, this, &WebWindowClass::hasClosed);
auto layout = new QVBoxLayout(dialogWidget);
layout->setContentsMargins(0, 0, 0, 0);
dialogWidget->setLayout(layout);
_webView = new QWebView(dialogWidget);
layout->addWidget(_webView);
addEventBridgeToWindowObject();
_windowWidget = dialogWidget;
}
auto style = QStyleFactory::create("fusion"); auto style = QStyleFactory::create("fusion");
if (style) { if (style) {
@ -121,13 +99,8 @@ void WebWindowClass::addEventBridgeToWindowObject() {
void WebWindowClass::setVisible(bool visible) { void WebWindowClass::setVisible(bool visible) {
if (visible) { if (visible) {
if (_isToolWindow) { QMetaObject::invokeMethod(_windowWidget, "showNormal", Qt::AutoConnection);
QMetaObject::invokeMethod( QMetaObject::invokeMethod(_windowWidget, "raise", Qt::AutoConnection);
qApp->getToolWindow(), "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
} else {
QMetaObject::invokeMethod(_windowWidget, "showNormal", Qt::AutoConnection);
QMetaObject::invokeMethod(_windowWidget, "raise", Qt::AutoConnection);
}
} }
QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible)); QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
} }
@ -182,13 +155,18 @@ void WebWindowClass::raise() {
QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
WebWindowClass* retVal; WebWindowClass* retVal;
QString file = context->argument(0).toString(); QString file = context->argument(0).toString();
if (context->argument(4).toBool()) {
qWarning() << "ToolWindow views with WebWindow are no longer supported. Use OverlayWebWindow instead";
return QScriptValue();
} else {
qWarning() << "WebWindow views are deprecated. Use OverlayWebWindow instead";
}
QMetaObject::invokeMethod(DependencyManager::get<WindowScriptingInterface>().data(), "doCreateWebWindow", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(DependencyManager::get<WindowScriptingInterface>().data(), "doCreateWebWindow", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(WebWindowClass*, retVal), Q_RETURN_ARG(WebWindowClass*, retVal),
Q_ARG(const QString&, file), Q_ARG(const QString&, file),
Q_ARG(QString, context->argument(1).toString()), Q_ARG(QString, context->argument(1).toString()),
Q_ARG(int, context->argument(2).toInteger()), Q_ARG(int, context->argument(2).toInteger()),
Q_ARG(int, context->argument(3).toInteger()), Q_ARG(int, context->argument(3).toInteger()));
Q_ARG(bool, context->argument(4).toBool()));
connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater); connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater);

View file

@ -39,7 +39,7 @@ class WebWindowClass : public QObject {
Q_PROPERTY(QSizeF size READ getSize WRITE setSize); Q_PROPERTY(QSizeF size READ getSize WRITE setSize);
public: public:
WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow = false); WebWindowClass(const QString& title, const QString& url, int width, int height);
~WebWindowClass(); ~WebWindowClass();
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
@ -75,7 +75,6 @@ private:
QWidget* _windowWidget; QWidget* _windowWidget;
QWebView* _webView; QWebView* _webView;
ScriptEventBridge* _eventBridge; ScriptEventBridge* _eventBridge;
bool _isToolWindow;
}; };
#endif #endif

View file

@ -23,6 +23,7 @@
#include "Menu.h" #include "Menu.h"
#include "OffscreenUi.h" #include "OffscreenUi.h"
#include "ui/ModelsBrowser.h" #include "ui/ModelsBrowser.h"
#include "WebWindowClass.h"
#include "WindowScriptingInterface.h" #include "WindowScriptingInterface.h"
@ -37,8 +38,8 @@ WindowScriptingInterface::WindowScriptingInterface() :
connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused); connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
} }
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow) { WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) {
return new WebWindowClass(title, url, width, height, isToolWindow); return new WebWindowClass(title, url, width, height);
} }
QScriptValue WindowScriptingInterface::hasFocus() { QScriptValue WindowScriptingInterface::hasFocus() {

View file

@ -19,7 +19,7 @@
#include <QComboBox> #include <QComboBox>
#include <QLineEdit> #include <QLineEdit>
#include "WebWindowClass.h" class WebWindowClass;
class WindowScriptingInterface : public QObject, public Dependency { class WindowScriptingInterface : public QObject, public Dependency {
Q_OBJECT Q_OBJECT
@ -82,8 +82,8 @@ private slots:
void nonBlockingFormAccepted() { _nonBlockingFormActive = false; _formResult = QDialog::Accepted; emit nonBlockingFormClosed(); } void nonBlockingFormAccepted() { _nonBlockingFormActive = false; _formResult = QDialog::Accepted; emit nonBlockingFormClosed(); }
void nonBlockingFormRejected() { _nonBlockingFormActive = false; _formResult = QDialog::Rejected; emit nonBlockingFormClosed(); } void nonBlockingFormRejected() { _nonBlockingFormActive = false; _formResult = QDialog::Rejected; emit nonBlockingFormClosed(); }
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow); WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height);
private: private:
QString jsRegExp2QtRegExp(QString string); QString jsRegExp2QtRegExp(QString string);
QDialog* createForm(const QString& title, QScriptValue form); QDialog* createForm(const QString& title, QScriptValue form);

View file

@ -67,6 +67,7 @@ void AvatarInputs::update() {
AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking)); AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking));
AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking)); AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking));
AI_UPDATE(isHMD, qApp->isHMDMode()); AI_UPDATE(isHMD, qApp->isHMDMode());
AI_UPDATE(showAudioTools, Menu::getInstance()->isOptionChecked(MenuOption::AudioTools));
auto audioIO = DependencyManager::get<AudioClient>(); auto audioIO = DependencyManager::get<AudioClient>();
const float AUDIO_METER_AVERAGING = 0.5; const float AUDIO_METER_AVERAGING = 0.5;

View file

@ -17,7 +17,7 @@
public: \ public: \
type name() { return _##name; }; \ type name() { return _##name; }; \
private: \ private: \
type _##name{ initialValue }; type _##name{ initialValue };
class AvatarInputs : public QQuickItem { class AvatarInputs : public QQuickItem {
Q_OBJECT Q_OBJECT
@ -31,6 +31,7 @@ class AvatarInputs : public QQuickItem {
AI_PROPERTY(bool, mirrorVisible, false) AI_PROPERTY(bool, mirrorVisible, false)
AI_PROPERTY(bool, mirrorZoomed, true) AI_PROPERTY(bool, mirrorZoomed, true)
AI_PROPERTY(bool, isHMD, false) AI_PROPERTY(bool, isHMD, false)
AI_PROPERTY(bool, showAudioTools, true)
public: public:
static AvatarInputs* getInstance(); static AvatarInputs* getInstance();
@ -46,6 +47,7 @@ signals:
void mirrorVisibleChanged(); void mirrorVisibleChanged();
void mirrorZoomedChanged(); void mirrorZoomedChanged();
void isHMDChanged(); void isHMDChanged();
void showAudioToolsChanged();
protected: protected:
Q_INVOKABLE void resetSensors(); Q_INVOKABLE void resetSensors();

View file

@ -148,8 +148,7 @@ void DialogsManager::lodTools() {
} }
void DialogsManager::toggleToolWindow() { void DialogsManager::toggleToolWindow() {
QMainWindow* toolWindow = qApp->getToolWindow(); DependencyManager::get<OffscreenUi>()->toggleToolWindow();
toolWindow->setVisible(!toolWindow->isVisible());
} }
void DialogsManager::hmdTools(bool showTools) { void DialogsManager::hmdTools(bool showTools) {

View file

@ -79,9 +79,6 @@ HMDToolsDialog::HMDToolsDialog(QWidget* parent) :
// what screens we're allowed on // what screens we're allowed on
watchWindow(windowHandle()); watchWindow(windowHandle());
auto dialogsManager = DependencyManager::get<DialogsManager>(); auto dialogsManager = DependencyManager::get<DialogsManager>();
if (qApp->getToolWindow()) {
watchWindow(qApp->getToolWindow()->windowHandle());
}
if (dialogsManager->getBandwidthDialog()) { if (dialogsManager->getBandwidthDialog()) {
watchWindow(dialogsManager->getBandwidthDialog()->windowHandle()); watchWindow(dialogsManager->getBandwidthDialog()->windowHandle());
} }

View file

@ -28,6 +28,7 @@
#include "Snapshot.h" #include "Snapshot.h"
#include "UserActivityLogger.h" #include "UserActivityLogger.h"
#include "UIUtil.h" #include "UIUtil.h"
#include "scripting/WebWindowClass.h"
const int PREFERENCES_HEIGHT_PADDING = 20; const int PREFERENCES_HEIGHT_PADDING = 20;
@ -135,7 +136,7 @@ void PreferencesDialog::openFullAvatarModelBrowser() {
const auto WIDTH = 900; const auto WIDTH = 900;
const auto HEIGHT = 700; const auto HEIGHT = 700;
if (!_marketplaceWindow) { if (!_marketplaceWindow) {
_marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false); _marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT);
} }
_marketplaceWindow->setVisible(true); _marketplaceWindow->setVisible(true);

View file

@ -17,7 +17,7 @@
#include <QDialog> #include <QDialog>
#include <QString> #include <QString>
#include "scripting/WebWindowClass.h" class WebWindowClass;
class PreferencesDialog : public QDialog { class PreferencesDialog : public QDialog {
Q_OBJECT Q_OBJECT
@ -41,7 +41,7 @@ private:
QString _displayNameString; QString _displayNameString;
WebWindowClass* _marketplaceWindow = NULL; WebWindowClass* _marketplaceWindow { nullptr };
private slots: private slots:
void accept(); void accept();

View file

@ -1,145 +0,0 @@
//
// ToolWindow.cpp
// interface/src/ui
//
// Created by Ryan Huffman on 11/13/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Application.h"
#include "MainWindow.h"
#include "ToolWindow.h"
#include "UIUtil.h"
const int DEFAULT_WIDTH = 300;
ToolWindow::ToolWindow(QWidget* parent) :
QMainWindow(parent),
_selfHidden(false),
_hasShown(false),
_lastGeometry() {
setTabPosition(Qt::TopDockWidgetArea, QTabWidget::TabPosition::North);
# ifndef Q_OS_LINUX
setDockOptions(QMainWindow::ForceTabbedDocks);
# endif
qApp->installEventFilter(this);
}
bool ToolWindow::event(QEvent* event) {
QEvent::Type type = event->type();
if (type == QEvent::Show) {
if (!_hasShown) {
_hasShown = true;
QMainWindow* mainWindow = qApp->getWindow();
QRect mainGeometry = mainWindow->geometry();
int titleBarHeight = UIUtil::getWindowTitleBarHeight(this);
int topMargin = titleBarHeight;
_lastGeometry = QRect(mainGeometry.topLeft().x(), mainGeometry.topLeft().y() + topMargin,
DEFAULT_WIDTH, mainGeometry.height() - topMargin);
}
setGeometry(_lastGeometry);
return true;
} else if (type == QEvent::Hide) {
_lastGeometry = geometry();
return true;
}
return QMainWindow::event(event);
}
bool ToolWindow::eventFilter(QObject* sender, QEvent* event) {
# ifndef Q_OS_LINUX
switch (event->type()) {
case QEvent::WindowStateChange:
if (qApp->getWindow()->isMinimized()) {
// If we are already visible, we are self-hiding
_selfHidden = isVisible();
setVisible(false);
} else {
if (_selfHidden) {
setVisible(true);
}
}
break;
case QEvent::ApplicationDeactivate:
_selfHidden = isVisible();
setVisible(false);
break;
case QEvent::ApplicationActivate:
if (_selfHidden) {
setVisible(true);
}
break;
default:
break;
}
# endif
return false;
}
void ToolWindow::onChildVisibilityUpdated(bool visible) {
if (!_selfHidden && visible) {
setVisible(true);
} else {
bool hasVisible = false;
QList<QDockWidget*> dockWidgets = findChildren<QDockWidget*>();
for (int i = 0; i < dockWidgets.count(); i++) {
if (dockWidgets[i]->isVisible()) {
hasVisible = true;
break;
}
}
// If a child was hidden and we don't have any children still visible, hide ourself.
if (!hasVisible) {
setVisible(false);
}
}
}
void ToolWindow::addDockWidget(Qt::DockWidgetArea area, QDockWidget* dockWidget) {
QList<QDockWidget*> dockWidgets = findChildren<QDockWidget*>();
QMainWindow::addDockWidget(area, dockWidget);
// We want to force tabbing, so retabify all of our widgets.
QDockWidget* lastDockWidget = dockWidget;
foreach (QDockWidget* nextDockWidget, dockWidgets) {
tabifyDockWidget(lastDockWidget, nextDockWidget);
lastDockWidget = nextDockWidget;
}
connect(dockWidget, &QDockWidget::visibilityChanged, this, &ToolWindow::onChildVisibilityUpdated);
}
void ToolWindow::addDockWidget(Qt::DockWidgetArea area, QDockWidget* dockWidget, Qt::Orientation orientation) {
QList<QDockWidget*> dockWidgets = findChildren<QDockWidget*>();
QMainWindow::addDockWidget(area, dockWidget, orientation);
QDockWidget* lastDockWidget = dockWidget;
foreach(QDockWidget* nextDockWidget, dockWidgets) {
tabifyDockWidget(lastDockWidget, nextDockWidget);
lastDockWidget = nextDockWidget;
}
connect(dockWidget, &QDockWidget::visibilityChanged, this, &ToolWindow::onChildVisibilityUpdated);
}
void ToolWindow::removeDockWidget(QDockWidget* dockWidget) {
QMainWindow::removeDockWidget(dockWidget);
disconnect(dockWidget, &QDockWidget::visibilityChanged, this, &ToolWindow::onChildVisibilityUpdated);
}

View file

@ -1,44 +0,0 @@
//
// ToolWindow.h
// interface/src/ui
//
// Created by Ryan Huffman on 11/13/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ToolWindow_h
#define hifi_ToolWindow_h
#include <QDockWidget>
#include <QEvent>
#include <QMainWindow>
#include <QRect>
#include <QWidget>
class ToolWindow : public QMainWindow {
Q_OBJECT
public:
ToolWindow(QWidget* parent = NULL);
virtual bool event(QEvent* event);
virtual void addDockWidget(Qt::DockWidgetArea area, QDockWidget* dockWidget);
virtual void addDockWidget(Qt::DockWidgetArea area, QDockWidget* dockWidget, Qt::Orientation orientation);
virtual void removeDockWidget(QDockWidget* dockWidget);
virtual bool eventFilter(QObject* sender, QEvent* event);
public slots:
void onChildVisibilityUpdated(bool visible);
private:
// Indicates whether this window was hidden by itself (because the main window lost focus).
bool _selfHidden;
bool _hasShown;
QRect _lastGeometry;
};
#endif // hifi_ToolWindow_h

View file

@ -340,12 +340,25 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
_qmlComponent = new QQmlComponent(_qmlEngine); _qmlComponent = new QQmlComponent(_qmlEngine);
} }
void OffscreenQmlSurface::resize(const QSize& newSize) { void OffscreenQmlSurface::resize(const QSize& newSize_) {
if (!_renderer || !_renderer->_quickWindow) { if (!_renderer || !_renderer->_quickWindow) {
return; return;
} }
const float MAX_OFFSCREEN_DIMENSION = 4096;
QSize newSize = newSize_;
if (newSize.width() > MAX_OFFSCREEN_DIMENSION || newSize.height() > MAX_OFFSCREEN_DIMENSION) {
float scale = std::min(
((float)newSize.width() / MAX_OFFSCREEN_DIMENSION),
((float)newSize.height() / MAX_OFFSCREEN_DIMENSION));
newSize = QSize(
std::max(static_cast<int>(scale * newSize.width()), 10),
std::max(static_cast<int>(scale * newSize.height()), 10));
}
QSize currentSize = _renderer->_quickWindow->geometry().size(); QSize currentSize = _renderer->_quickWindow->geometry().size();
if (newSize == currentSize) { if (newSize == currentSize) {

View file

@ -21,10 +21,6 @@
#include "MessageDialog.h" #include "MessageDialog.h"
// Needs to match the constants in resources/qml/Global.js // Needs to match the constants in resources/qml/Global.js
static const QString OFFSCREEN_ROOT_OBJECT_NAME = "desktopRoot";
static const QString OFFSCREEN_WINDOW_OBJECT_NAME = "topLevelWindow";
static QQuickItem* _desktop { nullptr };
class OffscreenFlags : public QObject { class OffscreenFlags : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged) Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged)
@ -252,6 +248,48 @@ void OffscreenUi::createDesktop() {
_desktop = dynamic_cast<QQuickItem*>(load("Root.qml")); _desktop = dynamic_cast<QQuickItem*>(load("Root.qml"));
Q_ASSERT(_desktop); Q_ASSERT(_desktop);
getRootContext()->setContextProperty("Desktop", _desktop); getRootContext()->setContextProperty("Desktop", _desktop);
_toolWindow = _desktop->findChild<QQuickItem*>("ToolWindow");
} }
void OffscreenUi::toggleToolWindow() {
_toolWindow->setEnabled(!_toolWindow->isEnabled());
}
QQuickItem* OffscreenUi::getDesktop() {
return _desktop;
}
QQuickItem* OffscreenUi::getToolWindow() {
return _toolWindow;
}
Q_DECLARE_METATYPE(std::function<void()>);
static auto VoidLambdaType = qRegisterMetaType<std::function<void()>>();
Q_DECLARE_METATYPE(std::function<QVariant()>);
static auto VariantLambdaType = qRegisterMetaType<std::function<QVariant()>>();
void OffscreenUi::executeOnUiThread(std::function<void()> function) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "executeOnUiThread", Qt::QueuedConnection,
Q_ARG(std::function<void()>, function));
return;
}
function();
}
QVariant OffscreenUi::returnFromUiThread(std::function<QVariant()> function) {
if (QThread::currentThread() != thread()) {
QVariant result;
QMetaObject::invokeMethod(this, "returnFromUiThread", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QVariant, result),
Q_ARG(std::function<QVariant()>, function));
return result;
}
return function();
}
#include "OffscreenUi.moc" #include "OffscreenUi.moc"

View file

@ -12,6 +12,7 @@
#ifndef hifi_OffscreenUi_h #ifndef hifi_OffscreenUi_h
#define hifi_OffscreenUi_h #define hifi_OffscreenUi_h
#include <QtCore/QVariant>
#include <gl/OffscreenQmlSurface.h> #include <gl/OffscreenQmlSurface.h>
#include <QMessageBox> #include <QMessageBox>
@ -33,6 +34,12 @@ public:
bool navigationFocused(); bool navigationFocused();
void setNavigationFocused(bool focused); void setNavigationFocused(bool focused);
QQuickItem* getDesktop();
QQuickItem* getToolWindow();
Q_INVOKABLE void executeOnUiThread(std::function<void()> function);
Q_INVOKABLE QVariant returnFromUiThread(std::function<QVariant()> function);
// Messagebox replacement functions // Messagebox replacement functions
using ButtonCallback = std::function<void(QMessageBox::StandardButton)>; using ButtonCallback = std::function<void(QMessageBox::StandardButton)>;
static ButtonCallback NO_OP_CALLBACK; static ButtonCallback NO_OP_CALLBACK;
@ -70,6 +77,11 @@ public:
QMessageBox::StandardButtons buttons = QMessageBox::Ok); QMessageBox::StandardButtons buttons = QMessageBox::Ok);
static void error(const QString& text); // Interim dialog in new style static void error(const QString& text); // Interim dialog in new style
void toggleToolWindow();
private:
QQuickItem* _desktop { nullptr };
QQuickItem* _toolWindow { nullptr };
}; };
#endif #endif

View file

@ -31,25 +31,15 @@ static const char* const URL_PROPERTY = "source";
// Method called by Qt scripts to create a new web window in the overlay // Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine, return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine,
[&](QQmlContext* context, QObject* object) { return new QmlWebWindowClass(object); }); [&](QObject* object) { return new QmlWebWindowClass(object); });
} }
QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) { QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) {
QObject::connect(_qmlWindow, SIGNAL(navigating(QString)), this, SLOT(handleNavigation(QString)));
} }
void QmlWebWindowClass::handleNavigation(const QString& url) {
bool handled = false;
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
if (handler) {
if (handler->canAcceptURL(url)) {
handled = handler->acceptURL(url);
}
}
if (handled) { // FIXME remove.
QMetaObject::invokeMethod(_qmlWindow, "stop", Qt::AutoConnection); void QmlWebWindowClass::handleNavigation(const QString& url) {
}
} }
QString QmlWebWindowClass::getURL() const { QString QmlWebWindowClass::getURL() const {

View file

@ -35,6 +35,7 @@ static const char* const TITLE_PROPERTY = "title";
static const char* const WIDTH_PROPERTY = "width"; static const char* const WIDTH_PROPERTY = "width";
static const char* const HEIGHT_PROPERTY = "height"; static const char* const HEIGHT_PROPERTY = "height";
static const char* const VISIBILE_PROPERTY = "visible"; static const char* const VISIBILE_PROPERTY = "visible";
static const char* const TOOLWINDOW_PROPERTY = "toolWindow";
void QmlScriptEventBridge::emitWebEvent(const QString& data) { void QmlScriptEventBridge::emitWebEvent(const QString& data) {
QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data)); QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
@ -86,13 +87,14 @@ void QmlWindowClass::setupServer() {
QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine, QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QQmlContext*, QObject*)> function) std::function<QmlWindowClass*(QObject*)> builder)
{ {
const auto argumentCount = context->argumentCount(); const auto argumentCount = context->argumentCount();
QString url; QString url;
QString title; QString title;
int width = -1, height = -1; int width = -1, height = -1;
bool visible = true; bool visible = true;
bool toolWindow = false;
if (argumentCount > 1) { if (argumentCount > 1) {
if (!context->argument(0).isUndefined()) { if (!context->argument(0).isUndefined()) {
@ -107,6 +109,9 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
if (context->argument(3).isNumber()) { if (context->argument(3).isNumber()) {
height = context->argument(3).toInt32(); height = context->argument(3).toInt32();
} }
if (context->argument(4).isBool()) {
toolWindow = context->argument(4).toBool();
}
} else { } else {
auto argumentObject = context->argument(0); auto argumentObject = context->argument(0);
qDebug() << argumentObject.toString(); qDebug() << argumentObject.toString();
@ -125,6 +130,9 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
if (argumentObject.property(VISIBILE_PROPERTY).isBool()) { if (argumentObject.property(VISIBILE_PROPERTY).isBool()) {
visible = argumentObject.property(VISIBILE_PROPERTY).toBool(); visible = argumentObject.property(VISIBILE_PROPERTY).toBool();
} }
if (argumentObject.property(TOOLWINDOW_PROPERTY).isBool()) {
toolWindow = argumentObject.property(TOOLWINDOW_PROPERTY).toBool();
}
} }
if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) { if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) {
@ -137,17 +145,44 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
} }
QmlWindowClass* retVal{ nullptr }; QmlWindowClass* retVal{ nullptr };
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
qDebug() << "Clearing component cache";
offscreenUi->getRootContext()->engine()->clearComponentCache();
// Build the event bridge and wrapper on the main thread if (toolWindow) {
QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection, auto toolWindow = offscreenUi->getToolWindow();
Q_ARG(const QString&, qmlSource), QVariantMap properties;
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) { properties.insert(TITLE_PROPERTY, title);
properties.insert(SOURCE_PROPERTY, url);
if (width != -1 && height != -1) {
properties.insert(WIDTH_PROPERTY, width);
properties.insert(HEIGHT_PROPERTY, height);
}
// Build the event bridge and wrapper on the main thread
QVariant newTabVar;
bool invokeResult = QMetaObject::invokeMethod(toolWindow, "addWebTab", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QVariant, newTabVar),
Q_ARG(QVariant, QVariant::fromValue(properties)));
QQuickItem* newTab = qvariant_cast<QQuickItem*>(newTabVar);
if (!invokeResult || !newTab) {
return QScriptValue();
}
offscreenUi->returnFromUiThread([&] {
setupServer(); setupServer();
retVal = function(context, object); retVal = builder(newTab);
retVal->_toolWindow = true;
offscreenUi->getRootContext()->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership);
registerObject(url.toLower(), retVal);
return QVariant();
});
} else {
// Build the event bridge and wrapper on the main thread
QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, qmlSource),
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
setupServer();
retVal = builder(object);
context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership); context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership);
registerObject(url.toLower(), retVal); registerObject(url.toLower(), retVal);
if (!title.isEmpty()) { if (!title.isEmpty()) {
@ -158,9 +193,12 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
} }
object->setProperty(SOURCE_PROPERTY, url); object->setProperty(SOURCE_PROPERTY, url);
if (visible) { if (visible) {
object->setProperty("enabled", true); object->setProperty("visible", true);
} }
})); }));
}
retVal->_source = url;
connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater); connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater);
return engine->newQObject(retVal); return engine->newQObject(retVal);
} }
@ -168,7 +206,7 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
// Method called by Qt scripts to create a new web window in the overlay // Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
return internalConstructor("QmlWindow.qml", context, engine, [&](QQmlContext* context, QObject* object){ return internalConstructor("QmlWindow.qml", context, engine, [&](QObject* object){
return new QmlWindowClass(object); return new QmlWindowClass(object);
}); });
} }
@ -181,6 +219,21 @@ QmlWindowClass::QmlWindowClass(QObject* qmlWindow)
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow)); Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow));
} }
QmlWindowClass::~QmlWindowClass() {
if (_qmlWindow) {
if (_toolWindow) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto toolWindow = offscreenUi->getToolWindow();
auto invokeResult = QMetaObject::invokeMethod(toolWindow, "removeTabForUrl", Qt::QueuedConnection,
Q_ARG(QVariant, _source));
Q_ASSERT(invokeResult);
} else {
_qmlWindow->deleteLater();
}
_qmlWindow = nullptr;
}
}
void QmlWindowClass::registerObject(const QString& name, QObject* object) { void QmlWindowClass::registerObject(const QString& name, QObject* object) {
webChannel.registerObject(name, object); webChannel.registerObject(name, object);
} }
@ -189,74 +242,85 @@ void QmlWindowClass::deregisterObject(QObject* object) {
webChannel.deregisterObject(object); webChannel.deregisterObject(object);
} }
void QmlWindowClass::setVisible(bool visible) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
return;
}
auto qmlWindow = asQuickItem();
if (qmlWindow->isEnabled() != visible) {
qmlWindow->setEnabled(visible);
emit visibilityChanged(visible);
}
}
QQuickItem* QmlWindowClass::asQuickItem() const { QQuickItem* QmlWindowClass::asQuickItem() const {
if (_toolWindow) {
return DependencyManager::get<OffscreenUi>()->getToolWindow();
}
return dynamic_cast<QQuickItem*>(_qmlWindow); return dynamic_cast<QQuickItem*>(_qmlWindow);
} }
bool QmlWindowClass::isVisible() const { void QmlWindowClass::setVisible(bool visible) {
if (QThread::currentThread() != thread()) { // For tool window tabs we special case visiblility as enable / disable of the tab, not the window
bool result; // The tool window itself has special logic based on whether any tabs are enabled
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "isVisible", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result)); if (_toolWindow) {
return result; auto targetTab = dynamic_cast<QQuickItem*>(_qmlWindow);
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
targetTab->setEnabled(visible);
//emit visibilityChanged(visible);
});
} else {
QQuickItem* targetWindow = asQuickItem();
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
targetWindow->setVisible(visible);
//emit visibilityChanged(visible);
});
} }
}
return asQuickItem()->isEnabled(); bool QmlWindowClass::isVisible() const {
// The tool window itself has special logic based on whether any tabs are enabled
if (_toolWindow) {
auto targetTab = dynamic_cast<QQuickItem*>(_qmlWindow);
return DependencyManager::get<OffscreenUi>()->returnFromUiThread([&] {
return QVariant::fromValue(targetTab->isEnabled());
}).toBool();
} else {
QQuickItem* targetWindow = asQuickItem();
return DependencyManager::get<OffscreenUi>()->returnFromUiThread([&] {
return QVariant::fromValue(targetWindow->isVisible());
}).toBool();
}
} }
glm::vec2 QmlWindowClass::getPosition() const { glm::vec2 QmlWindowClass::getPosition() const {
if (QThread::currentThread() != thread()) { QQuickItem* targetWindow = asQuickItem();
glm::vec2 result; QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "getPosition", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result)); return targetWindow->position();
return result; });
} return toGlm(result.toPointF());
return glm::vec2(asQuickItem()->x(), asQuickItem()->y());
} }
void QmlWindowClass::setPosition(const glm::vec2& position) { void QmlWindowClass::setPosition(const glm::vec2& position) {
if (QThread::currentThread() != thread()) { QQuickItem* targetWindow = asQuickItem();
QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position)); DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
return; targetWindow->setPosition(QPointF(position.x, position.y));
} });
asQuickItem()->setPosition(QPointF(position.x, position.y));
} }
void QmlWindowClass::setPosition(int x, int y) { void QmlWindowClass::setPosition(int x, int y) {
setPosition(glm::vec2(x, y)); setPosition(glm::vec2(x, y));
} }
// FIXME move to GLM helpers
glm::vec2 toGlm(const QSizeF& size) {
return glm::vec2(size.width(), size.height());
}
glm::vec2 QmlWindowClass::getSize() const { glm::vec2 QmlWindowClass::getSize() const {
if (QThread::currentThread() != thread()) { QQuickItem* targetWindow = asQuickItem();
glm::vec2 result; QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "getSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result)); return QSizeF(targetWindow->width(), targetWindow->height());
return result; });
} return toGlm(result.toSizeF());
return glm::vec2(asQuickItem()->width(), asQuickItem()->height());
} }
void QmlWindowClass::setSize(const glm::vec2& size) { void QmlWindowClass::setSize(const glm::vec2& size) {
if (QThread::currentThread() != thread()) { QQuickItem* targetWindow = asQuickItem();
QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size)); DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
} targetWindow->setSize(QSizeF(size.x, size.y));
});
asQuickItem()->setSize(QSizeF(size.x, size.y));
} }
void QmlWindowClass::setSize(int width, int height) { void QmlWindowClass::setSize(int width, int height) {
@ -264,27 +328,28 @@ void QmlWindowClass::setSize(int width, int height) {
} }
void QmlWindowClass::setTitle(const QString& title) { void QmlWindowClass::setTitle(const QString& title) {
if (QThread::currentThread() != thread()) { QQuickItem* targetWindow = asQuickItem();
QMetaObject::invokeMethod(this, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title)); DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
} targetWindow->setProperty(TITLE_PROPERTY, title);
});
_qmlWindow->setProperty(TITLE_PROPERTY, title);
} }
void QmlWindowClass::close() { void QmlWindowClass::close() {
if (QThread::currentThread() != thread()) { DependencyManager::get<OffscreenUi>()->executeOnUiThread([this] {
QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); if (_qmlWindow) {
} _qmlWindow->setProperty("destroyOnInvisible", true);
_qmlWindow->setProperty("destroyOnInvisible", true); _qmlWindow->setProperty("visible", false);
_qmlWindow->setProperty("visible", false); _qmlWindow->deleteLater();
_qmlWindow->deleteLater(); _qmlWindow = nullptr;
}
});
} }
void QmlWindowClass::hasClosed() { void QmlWindowClass::hasClosed() {
} }
void QmlWindowClass::raise() { void QmlWindowClass::raise() {
QMetaObject::invokeMethod(_qmlWindow, "raiseWindow", Qt::QueuedConnection); QMetaObject::invokeMethod(asQuickItem(), "raiseWindow", Qt::QueuedConnection);
} }
#include "QmlWindowClass.moc" #include "QmlWindowClass.moc"

View file

@ -51,6 +51,7 @@ class QmlWindowClass : public QObject {
public: public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
QmlWindowClass(QObject* qmlWindow); QmlWindowClass(QObject* qmlWindow);
~QmlWindowClass();
public slots: public slots:
bool isVisible() const; bool isVisible() const;
@ -84,7 +85,7 @@ protected slots:
protected: protected:
static QScriptValue internalConstructor(const QString& qmlSource, static QScriptValue internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine, QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QQmlContext*, QObject*)> function); std::function<QmlWindowClass*(QObject*)> function);
static void setupServer(); static void setupServer();
static void registerObject(const QString& name, QObject* object); static void registerObject(const QString& name, QObject* object);
static void deregisterObject(QObject* object); static void deregisterObject(QObject* object);
@ -95,9 +96,10 @@ protected:
// FIXME needs to be initialized in the ctor once we have support // FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML // for tool window panes in QML
const bool _isToolWindow { false }; bool _toolWindow { false };
const int _windowId; const int _windowId;
QObject* const _qmlWindow; QObject* _qmlWindow;
QString _source;
}; };
#endif #endif