overte-Armored-Dragon/interface/resources/qml/hifi/Card.qml
2019-03-15 12:56:43 -07:00

335 lines
11 KiB
QML

//
// Card.qml
// qml/hifi
//
// Displays a clickable card representing a user story or destination.
//
// Created by Howard Stearns on 7/13/2016
// 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 TabletScriptingInterface 1.0
import "toolbars"
import stylesUit 1.0
Item {
id: root;
property string userName: "";
property string placeName: "";
property string action: "";
property string timestamp: "";
property string hifiUrl: "";
property string thumbnail: defaultThumbnail;
property string imageUrl: "";
property var goFunction: null;
property string storyId: "";
property bool standaloneOptimized: false;
property bool drillDownToPlace: false;
property bool showPlace: isConcurrency;
property string messageColor: isAnnouncement ? "white" : hifi.colors.blueAccent;
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 bool has3DHTML: PlatformInfo.has3DHTML();
property int textPadding: 10;
property int smallMargin: 4;
property int messageHeight: 40;
property int textSize: 24;
property int textSizeSmall: 18;
property int stackShadowNarrowing: 5;
property string defaultThumbnail: Qt.resolvedUrl("../../images/default-domain.gif");
property int shadowHeight: 10;
property bool hovered: false
HifiConstants { id: hifi }
function pastTime(timestamp) { // Answer a descriptive string
timestamp = new Date(timestamp);
var then = timestamp.getTime(),
now = Date.now(),
since = now - then,
ONE_MINUTE = 1000 * 60,
ONE_HOUR = ONE_MINUTE * 60,
hours = since / ONE_HOUR,
minutes = (hours % 1) * 60;
if (hours > 24) {
return timestamp.toDateString();
}
if (hours > 1) {
return Math.floor(hours).toString() + ' hr ' + Math.floor(minutes) + ' min ago';
}
if (minutes >= 2) {
return Math.floor(minutes).toString() + ' min ago';
}
return 'about a minute ago';
}
property bool hasGif: imageUrl.indexOf('.gif') === (imageUrl.length - 4);
function pluralize(count, singular, optionalPlural) {
return (count === 1) ? singular : (optionalPlural || (singular + "s"));
}
DropShadow {
visible: isStacked;
anchors.fill: shadow1;
source: shadow1;
verticalOffset: 2;
radius: 4;
samples: 9;
color: hifi.colors.baseGrayShadow;
}
Rectangle {
id: shadow1;
visible: isStacked;
width: parent.width - stackShadowNarrowing;
height: shadowHeight;
anchors {
top: parent.bottom;
horizontalCenter: parent.horizontalCenter;
}
}
DropShadow {
anchors.fill: base;
source: base;
verticalOffset: 2;
radius: 4;
samples: 9;
color: hifi.colors.baseGrayShadow;
}
Rectangle {
id: base;
color: "white";
anchors.fill: parent;
}
AnimatedImage {
id: animation;
// Always visible, to drive loading, but initially covered up by lobby during load.
source: hasGif ? imageUrl : "";
fillMode: lobby.fillMode;
anchors.fill: lobby;
}
Image {
id: lobby;
visible: !hasGif || (animation.status !== Image.Ready);
width: parent.width - (isConcurrency ? 0 : (2 * smallMargin));
height: parent.height -(isAnnouncement ? smallMargin : messageHeight) - (isConcurrency ? 0 : smallMargin);
source: thumbnail || defaultThumbnail;
fillMode: Image.PreserveAspectCrop;
anchors {
horizontalCenter: parent.horizontalCenter;
top: parent.top;
topMargin: isConcurrency ? 0 : smallMargin;
}
onStatusChanged: {
if (status == Image.Error) {
console.log("source: " + source + ": failed to load");
source = defaultThumbnail;
}
}
}
property int dropHorizontalOffset: 0;
property int dropVerticalOffset: 1;
property int dropRadius: 2;
property int dropSamples: 9;
property int dropSpread: 0;
DropShadow {
visible: showPlace; // Do we have to check for whatever the modern equivalent is for desktop.gradientsSupported?
source: place;
anchors.fill: place;
horizontalOffset: dropHorizontalOffset;
verticalOffset: dropVerticalOffset;
radius: dropRadius;
samples: dropSamples;
color: hifi.colors.black;
spread: dropSpread;
}
RalewaySemiBold {
id: place;
visible: showPlace;
text: placeName;
color: hifi.colors.white;
size: textSize;
elide: Text.ElideRight; // requires constrained width
anchors {
top: parent.top;
left: parent.left;
right: parent.right;
margins: textPadding;
}
}
Rectangle {
id: lozenge;
visible: isAnnouncement;
color: lozengeHot.containsMouse ? hifi.colors.redAccent : hifi.colors.redHighlight;
anchors.fill: infoRow;
radius: lozenge.height / 2.0;
}
Row {
id: infoRow;
Image {
id: icon;
source: isAnnouncement ? "../../images/Announce-Blast.svg" : "../../images/snap-icon.svg";
width: 40;
height: 40;
visible: ((action === 'snapshot') || isAnnouncement) && (messageHeight >= 40);
}
FiraSansRegular {
id: users;
visible: isConcurrency || isAnnouncement;
text: onlineUsers;
size: textSize;
color: messageColor;
anchors.verticalCenter: message.verticalCenter;
}
RalewayRegular {
id: message;
visible: !isAnnouncement;
text: isConcurrency ? pluralize(onlineUsers, "person", "people") : (drillDownToPlace ? "snapshots" : ("by " + userName));
size: textSizeSmall;
color: messageColor;
elide: Text.ElideRight; // requires a width to be specified`
width: root.width - textPadding
- (icon.visible ? icon.width + parent.spacing : 0)
- (users.visible ? users.width + parent.spacing : 0)
- (actionIcon.width + (2 * smallMargin));
anchors {
bottom: parent.bottom;
bottomMargin: parent.spacing;
}
}
Column {
visible: isAnnouncement;
RalewayRegular {
text: pluralize(onlineUsers, "connection") + " "; // hack padding
size: textSizeSmall;
color: messageColor;
}
RalewayRegular {
text: pluralize(onlineUsers, "is here now", "are here now");
size: textSizeSmall * 0.7;
color: messageColor;
}
}
spacing: textPadding;
height: messageHeight;
anchors {
bottom: parent.bottom;
left: parent.left;
leftMargin: textPadding;
bottomMargin: isAnnouncement ? textPadding : 0;
}
}
// These two can be supplied to provide hover behavior.
// For example, AddressBarDialog provides functions that set the current list view item
// to that which is being hovered over.
property var hoverThunk: function () { };
property var unhoverThunk: function () { };
Rectangle {
anchors.fill: parent
visible: root.hovered
color: "transparent"
border.width: 4
border.color: hifiStyleConstants.colors.primaryHighlight
z: 1
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onContainsMouseChanged: {
// Use onContainsMouseChanged rather than onEntered and onExited because the latter aren't always
// triggered correctly - e.g., if drag rightwards from right hand side of a card to the next card
// onExited doesn't fire, in which case can end up with two cards highlighted.
if (containsMouse) {
Tablet.playSound(TabletEnums.ButtonHover);
hoverThunk();
} else {
unhoverThunk();
}
}
}
MouseArea {
// Separate MouseArea for click handling so that it doesn't interfere with hovering and interaction
// with containing ListView.
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: false
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
goFunction("hifi://" + hifiUrl, standaloneOptimized);
}
}
Image {
id: standaloneOptomizedBadge
anchors {
right: actionIcon.left
top: actionIcon.top
topMargin: 2
rightMargin: 3
}
height: root.standaloneOptimized ? 25 : 0
width: 25
visible: root.standaloneOptimized && isConcurrency
fillMode: Image.PreserveAspectFit
source: "../../icons/standalone-optimized.svg"
}
ColorOverlay {
anchors.fill: standaloneOptomizedBadge
source: standaloneOptomizedBadge
color: hifi.colors.blueHighlight
visible: root.standaloneOptimized && isConcurrency
}
StateImage {
id: actionIcon;
visible: !isAnnouncement && has3DHTML;
imageURL: "../../images/info-icon-2-state.svg";
size: 30;
buttonState: messageArea.containsMouse ? 1 : 0;
anchors {
bottom: parent.bottom;
right: parent.right;
margins: smallMargin;
}
}
function go() {
Tablet.playSound(TabletEnums.ButtonClick);
goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId));
}
MouseArea {
id: messageArea;
visible: !isAnnouncement && has3DHTML;
width: parent.width;
height: messageHeight;
anchors.top: lobby.bottom;
acceptedButtons: Qt.LeftButton;
onClicked: go();
hoverEnabled: true;
}
MouseArea {
id: lozengeHot;
visible: lozenge.visible;
anchors.fill: lozenge;
acceptedButtons: Qt.LeftButton;
onClicked: go();
hoverEnabled: true;
}
}