From c5f1455872b5b7b20e61db98a0b3aef97af29f38 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 12:47:28 -0700 Subject: [PATCH 1/9] go horizontal --- interface/resources/qml/hifi/Feed.qml | 78 +++++++++++++++++++ .../qml/hifi/tablet/TabletAddressDialog.qml | 72 +++++------------ 2 files changed, 96 insertions(+), 54 deletions(-) create mode 100644 interface/resources/qml/hifi/Feed.qml diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml new file mode 100644 index 0000000000..1adeec8bf2 --- /dev/null +++ b/interface/resources/qml/hifi/Feed.qml @@ -0,0 +1,78 @@ +// +// Feed.qml +// qml/hifi +// +// Displays a particular type of feed +// +// Created by Howard Stearns on 4/18/2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 +import QtQuick 2.5 +import QtGraphicalEffects 1.0 +import "toolbars" +import "../styles-uit" + +Rectangle { + id: root; + + property int cardWidth: 212; + property int cardHeight: 152; + property int stackedCardShadowHeight: 10; + property alias suggestions: feed; + + HifiConstants { id: hifi } + ListModel { id: feed; } + + RalewayLight { + id: label; + text: "Places"; + color: hifi.colors.blueHighlight; + size: 38; + width: root.width; + anchors { + top: parent.top; + left: parent.left; + margins: 10; + } + } + + ListView { + id: scroll; + clip: true; + model: feed; + orientation: ListView.Horizontal; + + spacing: 14; + height: cardHeight + stackedCardShadowHeight; + anchors { + top: label.bottom; + left: parent.left; + right: parent.right; + leftMargin: 10; + } + + delegate: Card { + width: cardWidth; + height: cardHeight; + goFunction: goCard; + userName: model.username; + placeName: model.place_name; + hifiUrl: model.place_name + model.path; + thumbnail: model.thumbnail_url; + imageUrl: model.image_url; + action: model.action; + timestamp: model.created_at; + onlineUsers: model.online_users; + storyId: model.metaverseId; + drillDownToPlace: model.drillDownToPlace; + shadowHeight: stackedCardShadowHeight; + hoverThunk: function () { scroll.currentIndex = index; } + unhoverThunk: function () { scroll.currentIndex = -1; } + } + } +} diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index bed1f82ac2..b97eaeafdb 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -31,8 +31,8 @@ StackView { height: parent !== null ? parent.height : undefined property var eventBridge; property var allStories: []; - property int cardWidth: 460; - property int cardHeight: 320; + property int cardWidth: 212; + property int cardHeight: 152; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; property var tablet: null; @@ -275,58 +275,21 @@ StackView { } Rectangle { - id: bgMain - color: hifiStyleConstants.colors.white - anchors.bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom - anchors.bottomMargin: 0 - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.top: topBar.bottom - anchors.topMargin: 0 - - ListModel { id: suggestions } - - ListView { - id: scroll - - property int stackedCardShadowHeight: 0; - clip: true - spacing: 14 - anchors { - bottom: parent.bottom - top: parent.top - left: parent.left - right: parent.right - leftMargin: 10 - } - - model: suggestions - orientation: ListView.Vertical - - delegate: Card { - width: cardWidth; - height: cardHeight; - goFunction: goCard; - userName: model.username; - placeName: model.place_name; - hifiUrl: model.place_name + model.path; - thumbnail: model.thumbnail_url; - imageUrl: model.image_url; - action: model.action; - timestamp: model.created_at; - onlineUsers: model.online_users; - storyId: model.metaverseId; - drillDownToPlace: model.drillDownToPlace; - shadowHeight: scroll.stackedCardShadowHeight; - hoverThunk: function () { scroll.currentIndex = index; } - unhoverThunk: function () { scroll.currentIndex = -1; } - } - - highlightMoveDuration: -1; - highlightMoveVelocity: -1; - highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.blueHighlight; z: 1; } + id: bgMain; + color: hifiStyleConstants.colors.white; + anchors { + bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom; + bottomMargin: 0; + right: parent.right; + rightMargin: 0; + left: parent.left; + leftMargin: 0; + top: topBar.bottom; + topMargin: 0; + } + Feed { + id: feed; + width: bgMain.width; } } @@ -363,6 +326,7 @@ StackView { } } + property alias suggestions: feed.suggestions; function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects. // TODO: make available to other .qml. From 3ce238b8a0a8a21ac93740fc4d652ec9575079d7 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 13:45:58 -0700 Subject: [PATCH 2/9] make feed self contained --- interface/resources/qml/hifi/Feed.qml | 122 ++++++++++++++++- .../qml/hifi/tablet/TabletAddressDialog.qml | 123 +----------------- 2 files changed, 122 insertions(+), 123 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 1adeec8bf2..2a7156b31e 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -23,11 +23,125 @@ Rectangle { property int cardWidth: 212; property int cardHeight: 152; property int stackedCardShadowHeight: 10; - property alias suggestions: feed; + property string metaverseServerUrl: ''; + property string filter: ''; + onFilterChanged: filterChoicesByText(); + property alias suggestions: feed; // fixme. don't need to expose HifiConstants { id: hifi } ListModel { id: feed; } - + + function resolveUrl(url) { + return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url; + } + function makeModelData(data) { // create a new obj from data + // ListModel elements will only ever have those properties that are defined by the first obj that is added. + // So here we make sure that we have all the properties we need, regardless of whether it is a place data or user story. + var name = data.place_name, + tags = data.tags || [data.action, data.username], + description = data.description || "", + thumbnail_url = data.thumbnail_url || ""; + return { + place_name: name, + username: data.username || "", + path: data.path || "", + created_at: data.created_at || "", + action: data.action || "", + thumbnail_url: resolveUrl(thumbnail_url), + image_url: resolveUrl(data.details.image_url), + + metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. + + tags: tags, + description: description, + online_users: data.details.concurrency || 0, + drillDownToPlace: false, + + searchText: [name].concat(tags, description || []).join(' ').toUpperCase() + } + } + property var allStories: []; + property var placeMap: ({}); // Used for making stacks. FIXME: generalize to not just by place. + property int requestId: 0; + function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model + var options = [ + 'now=' + new Date().toISOString(), + 'include_actions=' + selectedTab.includeActions, + 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), + 'require_online=true', + 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), + 'page=' + pageNumber + ]; + var url = metaverseBase + 'user_stories?' + options.join('&'); + var thisRequestId = ++requestId; + getRequest(url, function (error, data) { + if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { + return; // abandon stale requests + } + var stories = data.user_stories.map(function (story) { // explicit single-argument function + return makeModelData(story, url); + }); + allStories = allStories.concat(stories); + stories.forEach(makeFilteredPlaceProcessor()); + if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now + return getUserStoryPage(pageNumber + 1, cb); + } + cb(); + }); + } + function fillDestinations() { // Public + allStories = []; + suggestions.clear(); + placeMap = {}; + getUserStoryPage(1, function (error) { + console.log('user stories query', error || 'ok', allStories.length); + }); + } + function addToSuggestions(place) { // fixme: move to makeFilteredPlaceProcessor + var collapse = allTab.selected && (place.action !== 'concurrency'); + if (collapse) { + var existing = placeMap[place.place_name]; + if (existing) { + existing.drillDownToPlace = true; + return; + } + } + suggestions.append(place); + if (collapse) { + placeMap[place.place_name] = suggestions.get(suggestions.count - 1); + } else if (place.action === 'concurrency') { + suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). + } + } + function suggestable(place) { // fixme add to makeFilteredPlaceProcessor + if (place.action === 'snapshot') { + return true; + } + return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + } + function makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches + var words = filter.toUpperCase().split(/\s+/).filter(identity), + data = allStories; + function matches(place) { + if (!words.length) { + return suggestable(place); + } + return words.every(function (word) { + return place.searchText.indexOf(word) >= 0; + }); + } + return function (place) { + if (matches(place)) { + addToSuggestions(place); + } + }; + } + function filterChoicesByText() { + suggestions.clear(); + placeMap = {}; + allStories.forEach(makeFilteredPlaceProcessor()); + } + RalewayLight { id: label; text: "Places"; @@ -40,7 +154,6 @@ Rectangle { margins: 10; } } - ListView { id: scroll; clip: true; @@ -55,11 +168,10 @@ Rectangle { right: parent.right; leftMargin: 10; } - delegate: Card { width: cardWidth; height: cardHeight; - goFunction: goCard; + goFunction: goCard; // fixme global userName: model.username; placeName: model.place_name; hifiUrl: model.place_name + model.path; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index b97eaeafdb..57e24c2421 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -30,7 +30,6 @@ StackView { width: parent !== null ? parent.width : undefined height: parent !== null ? parent.height : undefined property var eventBridge; - property var allStories: []; property int cardWidth: 212; property int cardHeight: 152; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; @@ -39,9 +38,8 @@ StackView { Component { id: tabletWebView; TabletWebView {} } Component.onCompleted: { - fillDestinations(); updateLocationText(false); - fillDestinations(); + feed.fillDestinations(); addressLine.focus = !HMD.active; root.parentChanged.connect(center); center(); @@ -189,7 +187,7 @@ StackView { } font.pixelSize: hifi.fonts.pixelSize * 0.75 onTextChanged: { - filterChoicesByText(); + console.log('fixme onTextChanged "' + addressLine.text + "'."); updateLocationText(text.length > 0); } onAccepted: { @@ -290,6 +288,8 @@ StackView { Feed { id: feed; width: bgMain.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + filter: addressLine.text; } } @@ -326,7 +326,6 @@ StackView { } } - property alias suggestions: feed.suggestions; function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects. // TODO: make available to other .qml. @@ -372,122 +371,10 @@ StackView { return true; } - - function resolveUrl(url) { - return (url.indexOf('/') === 0) ? (addressBarDialog.metaverseServerUrl + url) : url; - } - - function makeModelData(data) { // create a new obj from data - // ListModel elements will only ever have those properties that are defined by the first obj that is added. - // So here we make sure that we have all the properties we need, regardless of whether it is a place data or user story. - var name = data.place_name, - tags = data.tags || [data.action, data.username], - description = data.description || "", - thumbnail_url = data.thumbnail_url || ""; - return { - place_name: name, - username: data.username || "", - path: data.path || "", - created_at: data.created_at || "", - action: data.action || "", - thumbnail_url: resolveUrl(thumbnail_url), - image_url: resolveUrl(data.details.image_url), - - metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. - - tags: tags, - description: description, - online_users: data.details.concurrency || 0, - drillDownToPlace: false, - - searchText: [name].concat(tags, description || []).join(' ').toUpperCase() - } - } - function suggestable(place) { - if (place.action === 'snapshot') { - return true; - } - return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. - } property var selectedTab: allTab; function tabSelect(textButton) { selectedTab = textButton; - fillDestinations(); - } - property var placeMap: ({}); - function addToSuggestions(place) { - var collapse = allTab.selected && (place.action !== 'concurrency'); - if (collapse) { - var existing = placeMap[place.place_name]; - if (existing) { - existing.drillDownToPlace = true; - return; - } - } - suggestions.append(place); - if (collapse) { - placeMap[place.place_name] = suggestions.get(suggestions.count - 1); - } else if (place.action === 'concurrency') { - suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). - } - } - property int requestId: 0; - function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model - var options = [ - 'now=' + new Date().toISOString(), - 'include_actions=' + selectedTab.includeActions, - 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), - 'require_online=true', - 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), - 'page=' + pageNumber - ]; - var url = metaverseBase + 'user_stories?' + options.join('&'); - var thisRequestId = ++requestId; - getRequest(url, function (error, data) { - if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { - return; - } - var stories = data.user_stories.map(function (story) { // explicit single-argument function - return makeModelData(story, url); - }); - allStories = allStories.concat(stories); - stories.forEach(makeFilteredPlaceProcessor()); - if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now - return getUserStoryPage(pageNumber + 1, cb); - } - cb(); - }); - } - function makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches - var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity), - data = allStories; - function matches(place) { - if (!words.length) { - return suggestable(place); - } - return words.every(function (word) { - return place.searchText.indexOf(word) >= 0; - }); - } - return function (place) { - if (matches(place)) { - addToSuggestions(place); - } - }; - } - function filterChoicesByText() { - suggestions.clear(); - placeMap = {}; - allStories.forEach(makeFilteredPlaceProcessor()); - } - - function fillDestinations() { - allStories = []; - suggestions.clear(); - placeMap = {}; - getUserStoryPage(1, function (error) { - console.log('user stories query', error || 'ok', allStories.length); - }); + feed.fillDestinations(); } function updateLocationText(enteringAddress) { From 2512255dd05efd186d686e72685e3d53580f77e9 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 16:33:02 -0700 Subject: [PATCH 3/9] reusable feeds --- interface/resources/qml/hifi/Feed.qml | 68 ++++++++----------- .../qml/hifi/tablet/TabletAddressDialog.qml | 21 +++++- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 2a7156b31e..a1bd38c281 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -17,19 +17,22 @@ import QtGraphicalEffects 1.0 import "toolbars" import "../styles-uit" -Rectangle { +Column { id: root; property int cardWidth: 212; property int cardHeight: 152; property int stackedCardShadowHeight: 10; property string metaverseServerUrl: ''; + property string actions: 'snapshot'; + onActionsChanged: fillDestinations(); + Component.onCompleted: fillDestinations(); + property string labelText: actions; property string filter: ''; onFilterChanged: filterChoicesByText(); - property alias suggestions: feed; // fixme. don't need to expose HifiConstants { id: hifi } - ListModel { id: feed; } + ListModel { id: suggestions; } function resolveUrl(url) { return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url; @@ -66,7 +69,7 @@ Rectangle { function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model var options = [ 'now=' + new Date().toISOString(), - 'include_actions=' + selectedTab.includeActions, + 'include_actions=' + actions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), @@ -78,11 +81,7 @@ Rectangle { if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { return; // abandon stale requests } - var stories = data.user_stories.map(function (story) { // explicit single-argument function - return makeModelData(story, url); - }); - allStories = allStories.concat(stories); - stories.forEach(makeFilteredPlaceProcessor()); + allStories = allStories.concat(data.user_stories.map(makeModelData)); if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now return getUserStoryPage(pageNumber + 1, cb); } @@ -94,11 +93,12 @@ Rectangle { suggestions.clear(); placeMap = {}; getUserStoryPage(1, function (error) { - console.log('user stories query', error || 'ok', allStories.length); + allStories.forEach(makeFilteredStoryProcessor()); + console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); }); } - function addToSuggestions(place) { // fixme: move to makeFilteredPlaceProcessor - var collapse = allTab.selected && (place.action !== 'concurrency'); + function addToSuggestions(place) { // fixme: move to makeFilteredStoryProcessor + var collapse = (actions === 'snapshot,concurrency') && (place.action !== 'concurrency'); // fixme generalize? if (collapse) { var existing = placeMap[place.place_name]; if (existing) { @@ -113,61 +113,49 @@ Rectangle { suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). } } - function suggestable(place) { // fixme add to makeFilteredPlaceProcessor - if (place.action === 'snapshot') { + function suggestable(story) { // fixme add to makeFilteredStoryProcessor + if (story.action === 'snapshot') { return true; } - return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + return (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. } - function makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches - var words = filter.toUpperCase().split(/\s+/).filter(identity), - data = allStories; - function matches(place) { + function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches + var words = filter.toUpperCase().split(/\s+/).filter(identity); + function matches(story) { if (!words.length) { - return suggestable(place); + return suggestable(story); } return words.every(function (word) { - return place.searchText.indexOf(word) >= 0; + return story.searchText.indexOf(word) >= 0; }); } - return function (place) { - if (matches(place)) { - addToSuggestions(place); + return function (story) { + if (matches(story)) { + addToSuggestions(story); } }; } function filterChoicesByText() { suggestions.clear(); placeMap = {}; - allStories.forEach(makeFilteredPlaceProcessor()); + allStories.forEach(makeFilteredStoryProcessor()); } RalewayLight { id: label; - text: "Places"; + text: labelText; color: hifi.colors.blueHighlight; - size: 38; - width: root.width; - anchors { - top: parent.top; - left: parent.left; - margins: 10; - } + size: 28; } ListView { id: scroll; clip: true; - model: feed; + model: suggestions; orientation: ListView.Horizontal; spacing: 14; + width: parent.width; height: cardHeight + stackedCardShadowHeight; - anchors { - top: label.bottom; - left: parent.left; - right: parent.right; - leftMargin: 10; - } delegate: Card { width: cardWidth; height: cardHeight; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 57e24c2421..e79855ec51 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -39,7 +39,6 @@ StackView { Component { id: tabletWebView; TabletWebView {} } Component.onCompleted: { updateLocationText(false); - feed.fillDestinations(); addressLine.focus = !HMD.active; root.parentChanged.connect(center); center(); @@ -286,11 +285,28 @@ StackView { topMargin: 0; } Feed { - id: feed; + id: happeningNow; width: bgMain.width; metaverseServerUrl: addressBarDialog.metaverseServerUrl; + actions: selectedTab.includeActions; filter: addressLine.text; } + Feed { + id: places; + width: bgMain.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + actions: 'concurrency'; + filter: addressLine.text; + anchors.top: happeningNow.bottom; + } + Feed { + id: snapshots; + width: bgMain.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + actions: 'snapshot'; + filter: addressLine.text; + anchors.top: places.bottom; + } } Timer { @@ -374,7 +390,6 @@ StackView { property var selectedTab: allTab; function tabSelect(textButton) { selectedTab = textButton; - feed.fillDestinations(); } function updateLocationText(enteringAddress) { From b8e9fb67b8e991a25b9a064ad0b86006f4eb639d Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 16:34:10 -0700 Subject: [PATCH 4/9] old style card fonts, margins, etc. --- interface/resources/qml/hifi/Card.qml | 120 ++++++++++---------------- 1 file changed, 46 insertions(+), 74 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index b72901fbdf..c6e833826a 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -80,7 +80,7 @@ Rectangle { id: lobby; visible: !hasGif || (animation.status !== Image.Ready); width: parent.width - (isConcurrency ? 0 : (2 * smallMargin)); - height: parent.height - (isConcurrency ? 0 : smallMargin); + height: parent.height - messageHeight - (isConcurrency ? 0 : smallMargin); source: thumbnail || defaultThumbnail; fillMode: Image.PreserveAspectCrop; anchors { @@ -129,7 +129,7 @@ Rectangle { property int dropSamples: 9; property int dropSpread: 0; DropShadow { - visible: true; + visible: showPlace && desktop.gradientsSupported; source: place; anchors.fill: place; horizontalOffset: dropHorizontalOffset; @@ -139,12 +139,12 @@ Rectangle { color: hifi.colors.black; spread: dropSpread; } - RalewayLight { + RalewaySemiBold { id: place; visible: showPlace; text: placeName; color: hifi.colors.white; - size: 38; + size: textSize; elide: Text.ElideRight; // requires constrained width anchors { top: parent.top; @@ -153,57 +153,44 @@ Rectangle { margins: textPadding; } } - Rectangle { - id: rectRow - z: 1 - width: message.width + (users.visible ? users.width + bottomRow.spacing : 0) - + (icon.visible ? icon.width + bottomRow.spacing: 0) + bottomRow.spacing; - height: messageHeight + 1; - radius: 25 - - anchors { - bottom: parent.bottom - left: parent.left - leftMargin: textPadding - bottomMargin: textPadding + Row { + FiraSansRegular { + id: users; + visible: isConcurrency; + text: onlineUsers; + size: textSize; + color: messageColor; + anchors.verticalCenter: message.verticalCenter; } - - Row { - id: bottomRow - FiraSansRegular { - id: users; - visible: isConcurrency; - text: onlineUsers; - size: textSize; - color: messageColor; - anchors.verticalCenter: message.verticalCenter; - } - Image { - id: icon; - source: "../../images/snap-icon.svg" - width: 40; - height: 40; - visible: action === 'snapshot'; - } - RalewayRegular { - id: message; - text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (drillDownToPlace ? "snapshots" : ("by " + userName)); - size: textSizeSmall; - color: messageColor; - elide: Text.ElideRight; // requires a width to be specified` - anchors { - bottom: parent.bottom; - bottomMargin: parent.spacing; - } - } - spacing: textPadding; - height: messageHeight; + Image { + id: icon; + source: "../../images/snap-icon.svg" + width: 40; + height: 40; + visible: action === 'snapshot'; + } + RalewayRegular { + id: message; + text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (drillDownToPlace ? "snapshots" : ("by " + userName)); + size: textSizeSmall; + color: messageColor; + elide: Text.ElideRight; // requires a width to be specified` + width: root.width - textPadding + - (users.visible ? users.width + parent.spacing : 0) + - (icon.visible ? icon.width + parent.spacing : 0) + - (actionIcon.width + (2 * smallMargin)); anchors { bottom: parent.bottom; - left: parent.left; - leftMargin: 4 + bottomMargin: parent.spacing; } } + spacing: textPadding; + height: messageHeight; + anchors { + bottom: parent.bottom; + left: parent.left; + leftMargin: textPadding; + } } // These two can be supplied to provide hover behavior. // For example, AddressBarDialog provides functions that set the current list view item @@ -218,37 +205,22 @@ Rectangle { onEntered: hoverThunk(); onExited: unhoverThunk(); } - Rectangle { - id: rectIcon - z: 1 - width: 32 - height: 32 - radius: 15 + StateImage { + id: actionIcon; + imageURL: "../../images/info-icon-2-state.svg"; + size: 32; + buttonState: messageArea.containsMouse ? 1 : 0; anchors { bottom: parent.bottom; right: parent.right; - bottomMargin: textPadding; - rightMargin: textPadding; - } - - StateImage { - id: actionIcon; - imageURL: "../../images/info-icon-2-state.svg"; - size: 32; - buttonState: messageArea.containsMouse ? 1 : 0; - anchors { - bottom: parent.bottom; - right: parent.right; - //margins: smallMargin; - } + margins: smallMargin; } } - MouseArea { id: messageArea; - width: rectIcon.width; - height: rectIcon.height; - anchors.fill: rectIcon + width: parent.width; + height: messageHeight; + anchors.top: lobby.bottom; acceptedButtons: Qt.LeftButton; onClicked: goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId)); hoverEnabled: true; From bc77eeeabdee888d815face0961c057b599dc43c Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 20 Apr 2017 13:43:55 -0700 Subject: [PATCH 5/9] scroll, margins --- interface/resources/qml/hifi/Feed.qml | 5 +- .../qml/hifi/tablet/TabletAddressDialog.qml | 134 +++++++----------- 2 files changed, 55 insertions(+), 84 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index a1bd38c281..9f2dfcd553 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -144,7 +144,7 @@ Column { RalewayLight { id: label; text: labelText; - color: hifi.colors.blueHighlight; + color: hifi.colors.white; size: 28; } ListView { @@ -152,6 +152,9 @@ Column { clip: true; model: suggestions; orientation: ListView.Horizontal; + highlightMoveDuration: -1; + highlightMoveVelocity: -1; + highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.blueHighlight; z: 1; } spacing: 14; width: parent.width; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index e79855ec51..1d07625e34 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -221,91 +221,64 @@ StackView { } } } - Rectangle { - id: topBar - height: 37 - color: hifiStyleConstants.colors.white - - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.topMargin: 0 - anchors.top: addressBar.bottom - - Row { - id: thing - spacing: 5 * hifi.layout.spacing - - anchors { - top: parent.top; - left: parent.left - leftMargin: 25 - } - - TabletTextButton { - id: allTab; - text: "ALL"; - property string includeActions: 'snapshot,concurrency'; - selected: allTab === selectedTab; - action: tabSelect; - } - - TabletTextButton { - id: placeTab; - text: "PLACES"; - property string includeActions: 'concurrency'; - selected: placeTab === selectedTab; - action: tabSelect; - - } - - TabletTextButton { - id: snapTab; - text: "SNAP"; - property string includeActions: 'snapshot'; - selected: snapTab === selectedTab; - action: tabSelect; - } - } - - } Rectangle { id: bgMain; - color: hifiStyleConstants.colors.white; + color: hifiStyleConstants.colors.faintGray50; anchors { + top: addressBar.bottom; bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom; - bottomMargin: 0; - right: parent.right; - rightMargin: 0; left: parent.left; - leftMargin: 0; - top: topBar.bottom; - topMargin: 0; + right: parent.right; } - Feed { - id: happeningNow; - width: bgMain.width; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; - actions: selectedTab.includeActions; - filter: addressLine.text; - } - Feed { - id: places; - width: bgMain.width; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; - actions: 'concurrency'; - filter: addressLine.text; - anchors.top: happeningNow.bottom; - } - Feed { - id: snapshots; - width: bgMain.width; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; - actions: 'snapshot'; - filter: addressLine.text; - anchors.top: places.bottom; + ScrollView { + anchors.fill: bgMain; + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff; + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn; //Qt.ScrollBarAsNeeded; + Rectangle { // Column margins require QtQuick 2.7, which we don't use yet. + id: column; + property real pad: 10; + width: bgMain.width - column.pad; + height: stack.height; + color: "transparent"; + anchors { + left: bgMain.left; + leftMargin: column.pad; + topMargin: column.pad; + } + Column { + id: stack; + width: column.width; + spacing: column.pad; + Feed { + id: happeningNow; + width: parent.width; + property real cardScale: 1.5; + cardWidth: places.cardWidth * happeningNow.cardScale; + cardHeight: places.cardHeight * happeningNow.cardScale; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + labelText: 'Happening Now'; + actions: 'concurrency,snapshot'; //selectedTab.includeActions; + filter: addressLine.text; + } + Feed { + id: places; + width: parent.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + labelText: 'Places'; + actions: 'concurrency'; + filter: addressLine.text; + } + Feed { + id: snapshots; + width: parent.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + labelText: 'Recent Activity'; + actions: 'snapshot'; + filter: addressLine.text; + } + } + } } } @@ -387,11 +360,6 @@ StackView { return true; } - property var selectedTab: allTab; - function tabSelect(textButton) { - selectedTab = textButton; - } - function updateLocationText(enteringAddress) { if (enteringAddress) { notice.text = "Go To a place, @user, path, or network address:"; From ce7145a3884ad1633fb73c64c201eb96357e526e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 20 Apr 2017 14:40:24 -0700 Subject: [PATCH 6/9] Announcement cards, debug data. --- interface/resources/qml/hifi/Card.qml | 5 +- interface/resources/qml/hifi/Feed.qml | 55 +++++++++++-------- .../qml/hifi/tablet/TabletAddressDialog.qml | 6 +- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index c6e833826a..1a3ae43692 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -35,6 +35,7 @@ Rectangle { property string timePhrase: pastTime(timestamp); property int onlineUsers: 0; property bool isConcurrency: action === 'concurrency'; + property bool isAnnouncement: action === 'announcement'; property bool isStacked: !isConcurrency && drillDownToPlace; property int textPadding: 10; @@ -156,7 +157,7 @@ Rectangle { Row { FiraSansRegular { id: users; - visible: isConcurrency; + visible: isConcurrency || isAnnouncement; text: onlineUsers; size: textSize; color: messageColor; @@ -171,7 +172,7 @@ Rectangle { } RalewayRegular { id: message; - text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (drillDownToPlace ? "snapshots" : ("by " + userName)); + text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (isAnnouncement ? "connections" : (drillDownToPlace ? "snapshots" : ("by " + userName))); size: textSizeSmall; color: messageColor; elide: Text.ElideRight; // requires a width to be specified` diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 9f2dfcd553..4f73d5bd16 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -19,6 +19,7 @@ import "../styles-uit" Column { id: root; + visible: false; property int cardWidth: 212; property int cardHeight: 152; @@ -44,6 +45,11 @@ Column { tags = data.tags || [data.action, data.username], description = data.description || "", thumbnail_url = data.thumbnail_url || ""; + if (actions === 'concurrency,snapshot') { + // A temporary hack for simulating announcements. We won't use this in production, but if requested, we'll use this data like announcements. + data.details.connections = 4; + data.action = 'announcement'; + } return { place_name: name, username: data.username || "", @@ -57,14 +63,14 @@ Column { tags: tags, description: description, - online_users: data.details.concurrency || 0, + online_users: data.details.connections || data.details.concurrency || 0, drillDownToPlace: false, searchText: [name].concat(tags, description || []).join(' ').toUpperCase() } } property var allStories: []; - property var placeMap: ({}); // Used for making stacks. FIXME: generalize to not just by place. + property var placeMap: ({}); // Used for making stacks. property int requestId: 0; function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model var options = [ @@ -95,32 +101,17 @@ Column { getUserStoryPage(1, function (error) { allStories.forEach(makeFilteredStoryProcessor()); console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); + root.visible = !!suggestions.count; // fixme reset on filtering, too! }); } - function addToSuggestions(place) { // fixme: move to makeFilteredStoryProcessor - var collapse = (actions === 'snapshot,concurrency') && (place.action !== 'concurrency'); // fixme generalize? - if (collapse) { - var existing = placeMap[place.place_name]; - if (existing) { - existing.drillDownToPlace = true; - return; - } - } - suggestions.append(place); - if (collapse) { - placeMap[place.place_name] = suggestions.get(suggestions.count - 1); - } else if (place.action === 'concurrency') { - suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). - } - } - function suggestable(story) { // fixme add to makeFilteredStoryProcessor - if (story.action === 'snapshot') { - return true; - } - return (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. - } function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches var words = filter.toUpperCase().split(/\s+/).filter(identity); + function suggestable(story) { // fixme add to makeFilteredStoryProcessor + if (story.action === 'snapshot') { + return true; + } + return (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + } function matches(story) { if (!words.length) { return suggestable(story); @@ -129,6 +120,22 @@ Column { return story.searchText.indexOf(word) >= 0; }); } + function addToSuggestions(place) { + var collapse = ((actions === 'concurrency,snapshot') && (place.action !== 'concurrency')) || (place.action === 'announcement'); + if (collapse) { + var existing = placeMap[place.place_name]; + if (existing) { + existing.drillDownToPlace = true; + return; + } + } + suggestions.append(place); + if (collapse) { + placeMap[place.place_name] = suggestions.get(suggestions.count - 1); + } else if (place.action === 'concurrency') { + suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). + } + } return function (story) { if (matches(story)) { addToSuggestions(story); diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 1d07625e34..5be8e158f4 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -186,7 +186,6 @@ StackView { } font.pixelSize: hifi.fonts.pixelSize * 0.75 onTextChanged: { - console.log('fixme onTextChanged "' + addressLine.text + "'."); updateLocationText(text.length > 0); } onAccepted: { @@ -234,7 +233,7 @@ StackView { ScrollView { anchors.fill: bgMain; horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff; - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn; //Qt.ScrollBarAsNeeded; + verticalScrollBarPolicy: Qt.ScrollBarAsNeeded; Rectangle { // Column margins require QtQuick 2.7, which we don't use yet. id: column; property real pad: 10; @@ -258,7 +257,8 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - actions: 'concurrency,snapshot'; //selectedTab.includeActions; + actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + //actions: 'announcement'; filter: addressLine.text; } Feed { From 4ea882cad13fbc52e74cab5c3600655290a4e185 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 20 Apr 2017 14:56:34 -0700 Subject: [PATCH 7/9] faster initial response --- interface/resources/qml/hifi/Feed.qml | 18 +++++++++++++++--- .../qml/hifi/tablet/TabletAddressDialog.qml | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 4f73d5bd16..c02cd1dd36 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -72,7 +72,8 @@ Column { property var allStories: []; property var placeMap: ({}); // Used for making stacks. property int requestId: 0; - function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model + function getUserStoryPage(pageNumber, cb, cb1) { // cb(error) after all pages of domain data have been added to model + // If supplied, cb1 will be run after the first page IFF it is not the last, for responsiveness. var options = [ 'now=' + new Date().toISOString(), 'include_actions=' + actions, @@ -89,19 +90,29 @@ Column { } allStories = allStories.concat(data.user_stories.map(makeModelData)); if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now + if ((pageNumber === 1) && cb1) { + cb1(); + } return getUserStoryPage(pageNumber + 1, cb); } cb(); }); } function fillDestinations() { // Public + var filter = makeFilteredStoryProcessor(), counter = 0; allStories = []; suggestions.clear(); placeMap = {}; getUserStoryPage(1, function (error) { - allStories.forEach(makeFilteredStoryProcessor()); + allStories.slice(counter).forEach(filter); console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); - root.visible = !!suggestions.count; // fixme reset on filtering, too! + root.visible = !!suggestions.count; + }, function () { // If there's more than a page, put what we have in the model right away, keeping track of how many are processed. + allStories.forEach(function (story) { + counter++; + filter(story); + root.visible = !!suggestions.count; + }); }); } function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches @@ -146,6 +157,7 @@ Column { suggestions.clear(); placeMap = {}; allStories.forEach(makeFilteredStoryProcessor()); + root.visible = !!suggestions.count; } RalewayLight { diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 5be8e158f4..02881f14c5 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -257,8 +257,8 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. - //actions: 'announcement'; + //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + actions: 'announcement'; filter: addressLine.text; } Feed { From 56196c03a355beb7a32ea006de1b6ebbca789c06 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 27 Apr 2017 12:56:32 -0700 Subject: [PATCH 8/9] pr review --- interface/resources/qml/hifi/Card.qml | 2 +- interface/resources/qml/hifi/Feed.qml | 5 +++-- .../resources/qml/hifi/tablet/TabletAddressDialog.qml | 9 ++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 1a3ae43692..59fa66af0b 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -130,7 +130,7 @@ Rectangle { property int dropSamples: 9; property int dropSpread: 0; DropShadow { - visible: showPlace && desktop.gradientsSupported; + visible: showPlace; // Do we have to check for whatever the modern equivalent is for desktop.gradientsSupported? source: place; anchors.fill: place; horizontalOffset: dropHorizontalOffset; diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index c02cd1dd36..d95518b891 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -31,6 +31,7 @@ Column { property string labelText: actions; property string filter: ''; onFilterChanged: filterChoicesByText(); + property var goFunction: null; HifiConstants { id: hifi } ListModel { id: suggestions; } @@ -57,7 +58,7 @@ Column { created_at: data.created_at || "", action: data.action || "", thumbnail_url: resolveUrl(thumbnail_url), - image_url: resolveUrl(data.details.image_url), + image_url: resolveUrl(data.details && data.details.image_url), metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. @@ -181,7 +182,7 @@ Column { delegate: Card { width: cardWidth; height: cardHeight; - goFunction: goCard; // fixme global + goFunction: root.goFunction; userName: model.username; placeName: model.place_name; hifiUrl: model.place_name + model.path; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index ef120b9c4f..6695ac08f6 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -242,7 +242,7 @@ StackView { height: stack.height; color: "transparent"; anchors { - left: bgMain.left; + left: parent.left; leftMargin: column.pad; topMargin: column.pad; } @@ -258,9 +258,10 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. - actions: 'announcement'; + actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + //actions: 'announcement'; filter: addressLine.text; + goFunction: goCard; } Feed { id: places; @@ -269,6 +270,7 @@ StackView { labelText: 'Places'; actions: 'concurrency'; filter: addressLine.text; + goFunction: goCard; } Feed { id: snapshots; @@ -277,6 +279,7 @@ StackView { labelText: 'Recent Activity'; actions: 'snapshot'; filter: addressLine.text; + goFunction: goCard; } } } From e4f259e6303dd64da07b47631fd0a6a3ab8237ec Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 27 Apr 2017 13:01:33 -0700 Subject: [PATCH 9/9] back to non-fake data --- interface/resources/qml/hifi/tablet/TabletAddressDialog.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 6695ac08f6..5578b94168 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -258,8 +258,8 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. - //actions: 'announcement'; + //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + actions: 'announcement'; filter: addressLine.text; goFunction: goCard; }