Add getting-started tips to the launcher
QML implementation of getting started tips, with a nice styled background box. Tips are defined+positioned inline, and displayed when their enclosing scope is active.
This commit is contained in:
parent
89de1defa0
commit
6b6defbead
27 changed files with 1660 additions and 5 deletions
|
@ -201,6 +201,14 @@ if (HAVE_QT)
|
|||
PathUrlHelper.hxx
|
||||
DialogStateController.cxx
|
||||
DialogStateController.hxx
|
||||
GettingStartedTip.hxx
|
||||
GettingStartedTip.cxx
|
||||
GettingStartedTipsController.cxx
|
||||
GettingStartedTipsController.hxx
|
||||
TipBackgroundBox.cxx
|
||||
TipBackgroundBox.hxx
|
||||
GettingStartedScope.hxx
|
||||
GettingStartedScope.cxx
|
||||
${QQUI_SOURCES}
|
||||
)
|
||||
|
||||
|
|
29
src/GUI/GettingStartedScope.cxx
Normal file
29
src/GUI/GettingStartedScope.cxx
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include "GettingStartedScope.hxx"
|
||||
|
||||
#include "GettingStartedTipsController.hxx"
|
||||
|
||||
|
||||
GettingStartedScope::GettingStartedScope(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
GettingStartedScopeAttached *GettingStartedScope::qmlAttachedProperties(QObject *object)
|
||||
{
|
||||
auto c = new GettingStartedScopeAttached(object);
|
||||
return c;
|
||||
}
|
||||
|
||||
GettingStartedScopeAttached::GettingStartedScopeAttached(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GettingStartedScopeAttached::setController(GettingStartedTipsController *controller)
|
||||
{
|
||||
if (_controller == controller)
|
||||
return;
|
||||
|
||||
_controller = controller;
|
||||
emit controllerChanged();
|
||||
}
|
43
src/GUI/GettingStartedScope.hxx
Normal file
43
src/GUI/GettingStartedScope.hxx
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
class GettingStartedTipsController;
|
||||
|
||||
class GettingStartedScopeAttached : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(GettingStartedTipsController* controller READ controller WRITE setController NOTIFY controllerChanged)
|
||||
public:
|
||||
GettingStartedScopeAttached(QObject* parent);
|
||||
|
||||
GettingStartedTipsController* controller() const
|
||||
{
|
||||
return _controller;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void setController(GettingStartedTipsController* controller);
|
||||
|
||||
signals:
|
||||
void controllerChanged();
|
||||
|
||||
private:
|
||||
GettingStartedTipsController* _controller = nullptr;
|
||||
};
|
||||
|
||||
class GettingStartedScope : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GettingStartedScope(QObject *parent = nullptr);
|
||||
|
||||
static GettingStartedScopeAttached* qmlAttachedProperties(QObject *object);
|
||||
signals:
|
||||
|
||||
};
|
||||
|
||||
QML_DECLARE_TYPEINFO(GettingStartedScope, QML_HAS_ATTACHED_PROPERTIES)
|
147
src/GUI/GettingStartedTip.cxx
Normal file
147
src/GUI/GettingStartedTip.cxx
Normal file
|
@ -0,0 +1,147 @@
|
|||
#include "GettingStartedTip.hxx"
|
||||
|
||||
#include "GettingStartedScope.hxx"
|
||||
#include "GettingStartedTipsController.hxx"
|
||||
|
||||
static bool static_globalEnableTips = true;
|
||||
|
||||
GettingStartedTip::GettingStartedTip(QQuickItem *parent) :
|
||||
QQuickItem(parent)
|
||||
{
|
||||
_enabled = static_globalEnableTips;
|
||||
|
||||
setImplicitHeight(1);
|
||||
setImplicitWidth(1);
|
||||
|
||||
if (_enabled) {
|
||||
registerWithScope();
|
||||
}
|
||||
}
|
||||
|
||||
GettingStartedTip::~GettingStartedTip()
|
||||
{
|
||||
if (_enabled) {
|
||||
unregisterFromScope();
|
||||
}
|
||||
}
|
||||
|
||||
GettingStartedTipsController *GettingStartedTip::controller() const
|
||||
{
|
||||
return _controller.data();
|
||||
}
|
||||
|
||||
void GettingStartedTip::componentComplete()
|
||||
{
|
||||
if (_enabled && !_controller) {
|
||||
registerWithScope();
|
||||
}
|
||||
|
||||
QQuickItem::componentComplete();
|
||||
}
|
||||
|
||||
void GettingStartedTip::setGlobalTipsEnabled(bool enable)
|
||||
{
|
||||
static_globalEnableTips = enable;
|
||||
}
|
||||
|
||||
void GettingStartedTip::setEnabled(bool enabled)
|
||||
{
|
||||
if (_enabled == enabled)
|
||||
return;
|
||||
|
||||
_enabled = enabled & static_globalEnableTips;
|
||||
if (enabled) {
|
||||
registerWithScope();
|
||||
} else {
|
||||
unregisterFromScope();
|
||||
}
|
||||
|
||||
emit enabledChanged();
|
||||
}
|
||||
|
||||
void GettingStartedTip::showOneShot()
|
||||
{
|
||||
auto ctl = findController();
|
||||
if (ctl) {
|
||||
ctl->showOneShotTip(this);
|
||||
}
|
||||
}
|
||||
|
||||
void GettingStartedTip::setStandalone(bool standalone)
|
||||
{
|
||||
if (_standalone == standalone)
|
||||
return;
|
||||
|
||||
_standalone = standalone;
|
||||
emit standaloneChanged(_standalone);
|
||||
|
||||
// re-register with our scope, which will also unregister us if
|
||||
// necessary.
|
||||
if (_enabled) {
|
||||
registerWithScope();
|
||||
}
|
||||
}
|
||||
|
||||
void GettingStartedTip::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
|
||||
{
|
||||
const bool isParentChanged = (change == ItemParentHasChanged);
|
||||
if (isParentChanged && _enabled) {
|
||||
unregisterFromScope();
|
||||
}
|
||||
|
||||
QQuickItem::itemChange(change, value);
|
||||
|
||||
if (isParentChanged && _enabled) {
|
||||
registerWithScope();
|
||||
}
|
||||
}
|
||||
|
||||
void GettingStartedTip::registerWithScope()
|
||||
{
|
||||
if (_controller) {
|
||||
unregisterFromScope();
|
||||
}
|
||||
|
||||
if (_standalone) {
|
||||
return; // standalone tips don't register
|
||||
}
|
||||
|
||||
auto ctl = findController();
|
||||
if (!ctl) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = ctl->addTip(this);
|
||||
if (ok) {
|
||||
_controller = ctl;
|
||||
emit controllerChanged();
|
||||
}
|
||||
}
|
||||
|
||||
GettingStartedTipsController* GettingStartedTip::findController()
|
||||
{
|
||||
QQuickItem* pr = const_cast<QQuickItem*>(parentItem());
|
||||
if (!pr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
while (pr) {
|
||||
auto sa = qobject_cast<GettingStartedScopeAttached*>(qmlAttachedPropertiesObject<GettingStartedScope>(pr, false));
|
||||
if (sa && sa->controller()) {
|
||||
return sa->controller();
|
||||
}
|
||||
|
||||
pr = pr->parentItem();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GettingStartedTip::unregisterFromScope()
|
||||
{
|
||||
if (_controller) {
|
||||
_controller->removeTip(this);
|
||||
_controller.clear();
|
||||
emit controllerChanged();
|
||||
}
|
||||
}
|
110
src/GUI/GettingStartedTip.hxx
Normal file
110
src/GUI/GettingStartedTip.hxx
Normal file
|
@ -0,0 +1,110 @@
|
|||
#ifndef GETTINGSTARTEDTIP_HXX
|
||||
#define GETTINGSTARTEDTIP_HXX
|
||||
|
||||
#include <QQuickItem>
|
||||
#include <QString>
|
||||
#include <QPointer>
|
||||
|
||||
#include "GettingStartedTipsController.hxx"
|
||||
|
||||
class GettingStartedTip : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString tipId MEMBER _id NOTIFY tipChanged)
|
||||
Q_PROPERTY(QString text MEMBER _text NOTIFY tipChanged)
|
||||
Q_PROPERTY(QString nextTip MEMBER _nextId NOTIFY tipChanged)
|
||||
|
||||
Q_PROPERTY(Arrow arrow MEMBER _arrow NOTIFY tipChanged)
|
||||
|
||||
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
|
||||
|
||||
Q_PROPERTY(GettingStartedTipsController* controller READ controller NOTIFY controllerChanged)
|
||||
|
||||
/// standalone tips are excluded from their scope when activated;
|
||||
/// instead they need to be activated manually
|
||||
Q_PROPERTY(bool standalone READ standalone WRITE setStandalone NOTIFY standaloneChanged)
|
||||
public:
|
||||
enum class Arrow
|
||||
{
|
||||
TopCenter, // directly below the item, centered
|
||||
LeftCenter,
|
||||
RightCenter,
|
||||
BottomCenter,
|
||||
BottomRight,
|
||||
TopRight,
|
||||
TopLeft,
|
||||
LeftTop, // on the left side, at the top
|
||||
};
|
||||
|
||||
Q_ENUM(Arrow)
|
||||
|
||||
explicit GettingStartedTip(QQuickItem *parent = nullptr);
|
||||
~GettingStartedTip() override;
|
||||
|
||||
QString tipId() const
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
Arrow arrow() const
|
||||
{
|
||||
return _arrow;
|
||||
}
|
||||
|
||||
bool isEnabled() const
|
||||
{
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
GettingStartedTipsController* controller() const;
|
||||
|
||||
void componentComplete() override; // from QQmlParserStatus
|
||||
|
||||
bool standalone() const
|
||||
{
|
||||
return _standalone;
|
||||
}
|
||||
|
||||
QString nextTip() const
|
||||
{
|
||||
return _nextId;
|
||||
}
|
||||
|
||||
// allow disabling all tips progrmatically : this is a temporary
|
||||
// measure to make life less annoying for our translators
|
||||
static void setGlobalTipsEnabled(bool enable);
|
||||
public slots:
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
void showOneShot();
|
||||
void setStandalone(bool standalone);
|
||||
|
||||
signals:
|
||||
|
||||
void tipChanged();
|
||||
void enabledChanged();
|
||||
void controllerChanged();
|
||||
|
||||
void standaloneChanged(bool standalone);
|
||||
|
||||
protected:
|
||||
void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value);
|
||||
|
||||
private:
|
||||
void registerWithScope();
|
||||
void unregisterFromScope();
|
||||
|
||||
GettingStartedTipsController* findController();
|
||||
|
||||
QString _id,
|
||||
_text,
|
||||
_nextId;
|
||||
|
||||
Arrow _arrow = Arrow::LeftCenter;
|
||||
bool _enabled = true;
|
||||
QPointer<GettingStartedTipsController> _controller;
|
||||
bool _standalone = false;
|
||||
};
|
||||
|
||||
#endif // GETTINGSTARTEDTIP_HXX
|
463
src/GUI/GettingStartedTipsController.cxx
Executable file
463
src/GUI/GettingStartedTipsController.cxx
Executable file
|
@ -0,0 +1,463 @@
|
|||
#include "GettingStartedTipsController.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QSettings>
|
||||
#include <QDebug>
|
||||
#include <QQmlContext>
|
||||
#include <QTimer>
|
||||
|
||||
#include <QtQml> // qmlContext
|
||||
|
||||
#include "GettingStartedTip.hxx"
|
||||
#include "TipBackgroundBox.hxx"
|
||||
|
||||
struct TipGeometryByArrowLocation
|
||||
{
|
||||
TipGeometryByArrowLocation(GettingStartedTip::Arrow a, const QRectF& g, Qt::Alignment al) :
|
||||
arrow(a),
|
||||
geometry(g),
|
||||
verticalAlignment(al)
|
||||
{
|
||||
}
|
||||
|
||||
GettingStartedTip::Arrow arrow;
|
||||
QRectF geometry;
|
||||
// specify how vertical space is adjusted; is the top, bottom or center fixed
|
||||
Qt::Alignment verticalAlignment = Qt::AlignVCenter;
|
||||
};
|
||||
|
||||
const double tipBoxWidth = 300.0;
|
||||
const double halfBoxWidth = tipBoxWidth * 0.5;
|
||||
const double arrowSideOffset = TipBackgroundBox::arrowSideOffset();
|
||||
const double rightSideOffset = -tipBoxWidth + arrowSideOffset;
|
||||
const double dummyHeight = 200.0;
|
||||
const double topHeightOffset = -TipBackgroundBox::arrowHeight();
|
||||
|
||||
static std::initializer_list<TipGeometryByArrowLocation> static_tipGeometries = {
|
||||
{GettingStartedTip::Arrow::BottomRight, QRectF{rightSideOffset, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignBottom},
|
||||
{GettingStartedTip::Arrow::BottomCenter, QRectF{halfBoxWidth, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignBottom},
|
||||
{GettingStartedTip::Arrow::TopCenter, QRectF{-halfBoxWidth, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignTop},
|
||||
{GettingStartedTip::Arrow::TopRight, QRectF{rightSideOffset, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignTop},
|
||||
{GettingStartedTip::Arrow::TopLeft, QRectF{-arrowSideOffset, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignTop},
|
||||
{GettingStartedTip::Arrow::LeftCenter, QRectF{0.0, 0.0, tipBoxWidth, dummyHeight}, Qt::AlignVCenter},
|
||||
{GettingStartedTip::Arrow::RightCenter, QRectF{-(tipBoxWidth + TipBackgroundBox::arrowHeight()), 0.0, tipBoxWidth, dummyHeight}, Qt::AlignVCenter},
|
||||
{GettingStartedTip::Arrow::LeftTop, QRectF{0.0, topHeightOffset, tipBoxWidth, dummyHeight}, Qt::AlignTop},
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The GettingStartedTipsController::ItemPositionObserver class
|
||||
*
|
||||
* This is a helper to observe the full position (and in the future, transform if required)
|
||||
* of a QQuickItem, so we can update a signal when the on-screen position changes. This
|
||||
* is necessary to re-transform the tooltip location if the item it's 'attached' to
|
||||
* moves, or some ancestor does.
|
||||
*
|
||||
* At present this does not handle arbitrary scaling or rotation, but observing those
|
||||
* signals would also be possible.
|
||||
*/
|
||||
class GettingStartedTipsController::ItemPositionObserver : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ItemPositionObserver(QObject* pr) :
|
||||
QObject(pr)
|
||||
{
|
||||
_notMovingTimeout = new QTimer(this);
|
||||
_notMovingTimeout->setSingleShot(true);
|
||||
_notMovingTimeout->setInterval(1000);
|
||||
|
||||
connect(this, SIGNAL(itemPositionChanged()),
|
||||
_notMovingTimeout, SLOT(start()));
|
||||
|
||||
connect(_notMovingTimeout, &QTimer::timeout,
|
||||
this, &ItemPositionObserver::itemNotMoving);
|
||||
}
|
||||
|
||||
void setObservedItem(QQuickItem* obs)
|
||||
{
|
||||
if (obs == _observedItem)
|
||||
return;
|
||||
|
||||
if (_observedItem) {
|
||||
for (auto o = _observedItem; o; o = o->parentItem()) {
|
||||
disconnect(o, nullptr, this, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
_observedItem = obs;
|
||||
if (obs) {
|
||||
startObserving(_observedItem);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasRecentlyMoved() const
|
||||
{
|
||||
return _notMovingTimeout->isActive();
|
||||
}
|
||||
signals:
|
||||
void itemPositionChanged();
|
||||
|
||||
void itemNotMoving();
|
||||
private:
|
||||
void startObserving(QQuickItem* obs)
|
||||
{
|
||||
|
||||
connect(obs, &QQuickItem::xChanged, this, &ItemPositionObserver::itemPositionChanged);
|
||||
connect(obs, &QQuickItem::yChanged, this, &ItemPositionObserver::itemPositionChanged);
|
||||
connect(obs, &QQuickItem::widthChanged, this, &ItemPositionObserver::itemPositionChanged);
|
||||
connect(obs, &QQuickItem::heightChanged, this, &ItemPositionObserver::itemPositionChanged);
|
||||
|
||||
// recurse up the item hierarchy
|
||||
if (obs->parentItem()) {
|
||||
startObserving(obs->parentItem());
|
||||
}
|
||||
}
|
||||
|
||||
QPointer<QQuickItem> _observedItem;
|
||||
QTimer* _notMovingTimeout = nullptr;
|
||||
};
|
||||
|
||||
GettingStartedTipsController::GettingStartedTipsController(QObject *parent) : QObject(parent)
|
||||
{
|
||||
// observer for the tip item
|
||||
_positionObserver = new ItemPositionObserver(this);
|
||||
connect(_positionObserver, &ItemPositionObserver::itemPositionChanged,
|
||||
this, &GettingStartedTipsController::tipPositionInVisualAreaChanged);
|
||||
|
||||
// observer for the visual area (which could also be scrolled)
|
||||
_viewAreaObserver = new ItemPositionObserver(this);
|
||||
connect(_viewAreaObserver, &ItemPositionObserver::itemPositionChanged,
|
||||
this, &GettingStartedTipsController::tipPositionInVisualAreaChanged);
|
||||
|
||||
connect(_positionObserver, &ItemPositionObserver::itemNotMoving,
|
||||
this, &GettingStartedTipsController::tipPositionInVisualAreaChanged);
|
||||
connect(_viewAreaObserver, &ItemPositionObserver::itemNotMoving,
|
||||
this, &GettingStartedTipsController::tipPositionInVisualAreaChanged);
|
||||
|
||||
auto qqParent = qobject_cast<QQuickItem*>(parent);
|
||||
if (qqParent) {
|
||||
setVisualArea(qqParent);
|
||||
}
|
||||
}
|
||||
|
||||
GettingStartedTipsController::~GettingStartedTipsController()
|
||||
{
|
||||
}
|
||||
|
||||
int GettingStartedTipsController::count() const
|
||||
{
|
||||
if (_oneShotTip) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return _tips.size();
|
||||
}
|
||||
|
||||
int GettingStartedTipsController::index() const
|
||||
{
|
||||
if (_oneShotTip) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _index;
|
||||
}
|
||||
|
||||
GettingStartedTip *GettingStartedTipsController::tip() const
|
||||
{
|
||||
if (_oneShotTip) {
|
||||
return _oneShotTip;
|
||||
}
|
||||
|
||||
if (_tips.empty())
|
||||
return nullptr;
|
||||
|
||||
if ((_index < 0) || (_index >= _tips.size()))
|
||||
return nullptr;
|
||||
|
||||
return _tips.at(_index);
|
||||
}
|
||||
|
||||
void GettingStartedTipsController::setVisualArea(QQuickItem *visualArea)
|
||||
{
|
||||
if (_visualArea == visualArea)
|
||||
return;
|
||||
|
||||
_visualArea = visualArea;
|
||||
_viewAreaObserver->setObservedItem(_visualArea);
|
||||
|
||||
emit tipPositionInVisualAreaChanged();
|
||||
emit visualAreaChanged(_visualArea);
|
||||
}
|
||||
|
||||
void GettingStartedTipsController::setActiveTipHeight(int activeTipHeight)
|
||||
{
|
||||
if (_activeTipHeight == activeTipHeight)
|
||||
return;
|
||||
|
||||
_activeTipHeight = activeTipHeight;
|
||||
emit activeTipHeightChanged(_activeTipHeight);
|
||||
emit tipGeometryChanged();
|
||||
}
|
||||
|
||||
void GettingStartedTipsController::showOneShotTip(GettingStartedTip *tip)
|
||||
{
|
||||
if (_scopeActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup("GettingStarted-DontShow");
|
||||
if (settings.value(tip->tipId()).toBool()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// mark the tip as shown
|
||||
settings.setValue(tip->tipId(), true);
|
||||
_oneShotTip = tip;
|
||||
|
||||
connect(_oneShotTip, &QObject::destroyed, this, &GettingStartedTipsController::onOneShotDestroyed);
|
||||
|
||||
currentTipUpdated();
|
||||
emit indexChanged(0);
|
||||
emit countChanged(count());
|
||||
}
|
||||
|
||||
void GettingStartedTipsController::tipsWereReset()
|
||||
{
|
||||
bool a = shouldShowScope();
|
||||
if (a != _scopeActive) {
|
||||
_scopeActive = a;
|
||||
emit activeChanged();
|
||||
currentTipUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
void GettingStartedTipsController::currentTipUpdated()
|
||||
{
|
||||
_positionObserver->setObservedItem(tip());
|
||||
|
||||
emit activeChanged();
|
||||
emit tipChanged();
|
||||
emit tipPositionInVisualAreaChanged();
|
||||
emit tipGeometryChanged();
|
||||
}
|
||||
|
||||
bool GettingStartedTipsController::addTip(GettingStartedTip *t)
|
||||
{
|
||||
if (_tips.contains(t)) {
|
||||
qWarning() << Q_FUNC_INFO << "Duplicate tip" << t;
|
||||
return false;
|
||||
}
|
||||
|
||||
// this logic is important to suppress duplicate tips inside a ListView or Repeater;
|
||||
// effectively, we only show a tip on the first registered instance.
|
||||
Q_FOREACH(GettingStartedTip* tip, _tips) {
|
||||
if (tip->tipId() == t->tipId()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_tips.append(t);
|
||||
// order tips by nextTip ID, if defined
|
||||
std::sort(_tips.begin(), _tips.end(), [](const GettingStartedTip* a, GettingStartedTip *b) {
|
||||
return a->nextTip() == b->tipId();
|
||||
});
|
||||
|
||||
currentTipUpdated();
|
||||
emit countChanged(count());
|
||||
return true;
|
||||
}
|
||||
|
||||
void GettingStartedTipsController::removeTip(GettingStartedTip *t)
|
||||
{
|
||||
const bool removedActive = (tip() == t);
|
||||
if (!_tips.removeOne(t)) {
|
||||
qWarning() << Q_FUNC_INFO << "tip not found";
|
||||
}
|
||||
|
||||
if (removedActive) {
|
||||
_index = qMax(_index - 1, 0);
|
||||
}
|
||||
|
||||
currentTipUpdated();
|
||||
emit countChanged(count());
|
||||
}
|
||||
|
||||
void GettingStartedTipsController::onOneShotDestroyed()
|
||||
{
|
||||
if (_oneShotTip == sender()) {
|
||||
emit activeChanged();
|
||||
currentTipUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
bool GettingStartedTipsController::isActive() const
|
||||
{
|
||||
if (_oneShotTip)
|
||||
return true;
|
||||
|
||||
return _scopeActive && !_tips.empty();
|
||||
}
|
||||
|
||||
QPointF GettingStartedTipsController::tipPositionInVisualArea() const
|
||||
{
|
||||
auto t = tip();
|
||||
if (!_visualArea || !t) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return _visualArea->mapFromItem(t, QPointF{0,0});
|
||||
}
|
||||
|
||||
QRectF GettingStartedTipsController::tipGeometry() const
|
||||
{
|
||||
auto t = tip();
|
||||
if (!t)
|
||||
return {};
|
||||
|
||||
const auto arrow = t->arrow();
|
||||
auto it = std::find_if(static_tipGeometries.begin(), static_tipGeometries.end(),
|
||||
[arrow](const TipGeometryByArrowLocation& tg)
|
||||
{
|
||||
return tg.arrow == arrow;
|
||||
});
|
||||
|
||||
if (it == static_tipGeometries.end()) {
|
||||
qWarning() << Q_FUNC_INFO << "Missing tip geometry" << arrow;
|
||||
return {};
|
||||
}
|
||||
|
||||
QRectF g = it->geometry;
|
||||
if ((arrow == GettingStartedTip::Arrow::LeftCenter) || (arrow == GettingStartedTip::Arrow::RightCenter)
|
||||
|| (arrow == GettingStartedTip::Arrow::LeftTop)) {
|
||||
g.setHeight(_activeTipHeight);
|
||||
} else {
|
||||
g.setHeight(_activeTipHeight + TipBackgroundBox::arrowHeight());
|
||||
}
|
||||
|
||||
|
||||
switch (it->verticalAlignment) {
|
||||
case Qt::AlignBottom:
|
||||
g.moveBottom(0);
|
||||
break;
|
||||
|
||||
case Qt::AlignTop:
|
||||
g.moveTop(0);
|
||||
break;
|
||||
|
||||
case Qt::AlignVCenter:
|
||||
g.moveTop(_activeTipHeight * -0.5);
|
||||
break;
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
bool GettingStartedTipsController::tipPositionValid() const
|
||||
{
|
||||
if (!_visualArea || !isActive())
|
||||
return false;
|
||||
|
||||
// hide tips when resizing the window or scrolling; it's visually distracting otherwise
|
||||
if (_positionObserver->hasRecentlyMoved() || _viewAreaObserver->hasRecentlyMoved()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_oneShotTip)
|
||||
return true;
|
||||
|
||||
return !_tips.empty();
|
||||
}
|
||||
|
||||
int GettingStartedTipsController::activeTipHeight() const
|
||||
{
|
||||
return _activeTipHeight;
|
||||
}
|
||||
|
||||
QRectF GettingStartedTipsController::contentGeometry() const
|
||||
{
|
||||
QRectF g(0.0, 0.0, tipBoxWidth, 200.0);
|
||||
|
||||
auto t = tip();
|
||||
if (!t)
|
||||
return g;
|
||||
|
||||
const auto arrow = t->arrow();
|
||||
if ((arrow == GettingStartedTip::Arrow::TopCenter) ||
|
||||
(arrow == GettingStartedTip::Arrow::TopLeft) ||
|
||||
(arrow == GettingStartedTip::Arrow::TopRight))
|
||||
{
|
||||
g.moveTop(TipBackgroundBox::arrowHeight());
|
||||
}
|
||||
|
||||
if (arrow == GettingStartedTip::Arrow::RightCenter) {
|
||||
g.setWidth(tipBoxWidth - TipBackgroundBox::arrowHeight());
|
||||
}
|
||||
|
||||
if (arrow == GettingStartedTip::Arrow::LeftCenter) {
|
||||
g.setWidth(tipBoxWidth - TipBackgroundBox::arrowHeight());
|
||||
g.moveLeft(TipBackgroundBox::arrowHeight());
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
void GettingStartedTipsController::close()
|
||||
{
|
||||
// one-shot tips handle this logic differently; we set the don't show
|
||||
// when the tip first appears
|
||||
if (_oneShotTip) {
|
||||
disconnect(_oneShotTip, nullptr, this, nullptr);
|
||||
_oneShotTip = nullptr;
|
||||
} else {
|
||||
QSettings settings;
|
||||
settings.beginGroup("GettingStarted-DontShow");
|
||||
settings.setValue(_scopeId, true);
|
||||
_scopeActive = false;
|
||||
}
|
||||
|
||||
emit activeChanged();
|
||||
currentTipUpdated();
|
||||
}
|
||||
|
||||
void GettingStartedTipsController::setIndex(int index)
|
||||
{
|
||||
if (_oneShotTip) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_index == index)
|
||||
return;
|
||||
|
||||
_index = qBound(0, index, _tips.size() - 1);
|
||||
_positionObserver->setObservedItem(tip());
|
||||
|
||||
emit indexChanged(_index);
|
||||
currentTipUpdated();
|
||||
}
|
||||
|
||||
void GettingStartedTipsController::setScopeId(QString scopeId)
|
||||
{
|
||||
if (_scopeId == scopeId)
|
||||
return;
|
||||
|
||||
_scopeId = scopeId;
|
||||
_scopeActive = shouldShowScope();
|
||||
emit scopeIdChanged(_scopeId);
|
||||
emit activeChanged();
|
||||
}
|
||||
|
||||
bool GettingStartedTipsController::shouldShowScope() const
|
||||
{
|
||||
if (_scopeId.isEmpty())
|
||||
return true;
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup("GettingStarted-DontShow");
|
||||
return settings.value(_scopeId).toBool() == false;
|
||||
}
|
||||
|
||||
|
||||
#include "GettingStartedTipsController.moc"
|
150
src/GUI/GettingStartedTipsController.hxx
Normal file
150
src/GUI/GettingStartedTipsController.hxx
Normal file
|
@ -0,0 +1,150 @@
|
|||
#ifndef GETTINGSTARTEDTIPSCONTROLLER_HXX
|
||||
#define GETTINGSTARTEDTIPSCONTROLLER_HXX
|
||||
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
#include <QQuickItem>
|
||||
#include <QPointer>
|
||||
|
||||
// forward decls
|
||||
class GettingStartedTip;
|
||||
|
||||
/**
|
||||
* @brief Manage presentation of getting started tips on a screen/page
|
||||
*/
|
||||
class GettingStartedTipsController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString scopeId READ scopeId WRITE setScopeId NOTIFY scopeIdChanged)
|
||||
|
||||
Q_PROPERTY(GettingStartedTip* tip READ tip NOTIFY tipChanged)
|
||||
|
||||
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
||||
Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged)
|
||||
|
||||
Q_PROPERTY(bool active READ isActive NOTIFY activeChanged)
|
||||
|
||||
Q_PROPERTY(QPointF tipPositionInVisualArea READ tipPositionInVisualArea NOTIFY tipPositionInVisualAreaChanged)
|
||||
Q_PROPERTY(bool tipPositionValid READ tipPositionValid NOTIFY tipPositionInVisualAreaChanged)
|
||||
|
||||
Q_PROPERTY(QRectF tipGeometry READ tipGeometry NOTIFY tipGeometryChanged)
|
||||
Q_PROPERTY(int activeTipHeight READ activeTipHeight WRITE setActiveTipHeight NOTIFY activeTipHeightChanged)
|
||||
Q_PROPERTY(QRectF contentGeometry READ contentGeometry NOTIFY tipChanged)
|
||||
|
||||
Q_PROPERTY(QQuickItem* visualArea READ visualArea WRITE setVisualArea NOTIFY visualAreaChanged)
|
||||
|
||||
public:
|
||||
explicit GettingStartedTipsController(QObject *parent = nullptr);
|
||||
~GettingStartedTipsController();
|
||||
|
||||
int count() const;
|
||||
|
||||
int index() const;
|
||||
|
||||
GettingStartedTip* tip() const;
|
||||
|
||||
QString scopeId() const
|
||||
{
|
||||
return _scopeId;
|
||||
}
|
||||
|
||||
bool isActive() const;
|
||||
|
||||
QPointF tipPositionInVisualArea() const;
|
||||
|
||||
QQuickItem* visualArea() const
|
||||
{
|
||||
return _visualArea;
|
||||
}
|
||||
|
||||
QRectF tipGeometry() const;
|
||||
|
||||
bool tipPositionValid() const;
|
||||
|
||||
int activeTipHeight() const;
|
||||
|
||||
/**
|
||||
* @brief contentGeometry - based on the active tip, return the box
|
||||
* (relative to the total tipGeometry) which content should occupy.
|
||||
* This allows for offseting due to the arrow position, and also specifies
|
||||
* the width for computing wrapped text height
|
||||
*
|
||||
* The actual height is the maximum height; the computed value should be
|
||||
* set by activeTipHeight
|
||||
*/
|
||||
QRectF contentGeometry() const;
|
||||
|
||||
public slots:
|
||||
|
||||
void close();
|
||||
|
||||
void setIndex(int index);
|
||||
|
||||
void setScopeId(QString scopeId);
|
||||
|
||||
void setVisualArea(QQuickItem* visualArea);
|
||||
|
||||
void setActiveTipHeight(int activeTipHeight);
|
||||
|
||||
/**
|
||||
* @brief showOneShotTip - show a single tip on its own, if it has not
|
||||
* previously been shown before.
|
||||
*
|
||||
* This is used for pieces of UI which are not always present; the first
|
||||
* time the UI is displayed to the user, we can show a tip describing it.
|
||||
* Once the user closes the tip, it will not reappear.
|
||||
*
|
||||
* The tip will be activated if the controller is currently inactive.
|
||||
* If the controller was alreayd active, the tip will be shown, and when
|
||||
* closed, the other tips will be shown. If the one-shot tip is part of
|
||||
* the currently active list, this actually does nothing, to avoid showing
|
||||
* the same tip twice
|
||||
*/
|
||||
void showOneShotTip(GettingStartedTip* tip);
|
||||
|
||||
void tipsWereReset();
|
||||
signals:
|
||||
|
||||
void countChanged(int count);
|
||||
void indexChanged(int index);
|
||||
void tipChanged();
|
||||
void scopeChanged(QObject* scope);
|
||||
void scopeIdChanged(QString scopeId);
|
||||
void activeChanged();
|
||||
void tipGeometryChanged();
|
||||
|
||||
void tipPositionInVisualAreaChanged();
|
||||
|
||||
void visualAreaChanged(QQuickItem* visualArea);
|
||||
|
||||
void activeTipHeightChanged(int activeTipHeight);
|
||||
|
||||
private:
|
||||
friend class GettingStartedTip;
|
||||
|
||||
bool shouldShowScope() const;
|
||||
|
||||
void currentTipUpdated();
|
||||
|
||||
bool addTip(GettingStartedTip* t);
|
||||
void removeTip(GettingStartedTip* t);
|
||||
|
||||
void onOneShotDestroyed();
|
||||
private:
|
||||
|
||||
class ItemPositionObserver;
|
||||
|
||||
bool _scopeActive = false;
|
||||
int _index = 0;
|
||||
QString _scopeId;
|
||||
ItemPositionObserver* _positionObserver = nullptr;
|
||||
ItemPositionObserver* _viewAreaObserver = nullptr;
|
||||
|
||||
QPointer<GettingStartedTip> _oneShotTip;
|
||||
QVector<GettingStartedTip*> _tips;
|
||||
QQuickItem* _visualArea = nullptr;
|
||||
int _activeTipHeight = 0;
|
||||
};
|
||||
|
||||
#endif // GETTINGSTARTEDTIPSCONTROLLER_HXX
|
|
@ -59,6 +59,10 @@
|
|||
#include "ThumbnailImageItem.hxx"
|
||||
#include "UnitsModel.hxx"
|
||||
#include "UpdateChecker.hxx"
|
||||
#include "GettingStartedTipsController.hxx"
|
||||
#include "GettingStartedTip.hxx"
|
||||
#include "TipBackgroundBox.hxx"
|
||||
#include "GettingStartedScope.hxx"
|
||||
|
||||
using namespace simgear::pkg;
|
||||
|
||||
|
@ -199,6 +203,11 @@ void LauncherController::initQML()
|
|||
|
||||
qmlRegisterSingletonType(QUrl("qrc:/qml/OverlayShared.qml"), "FlightGear", 1, 0, "OverlayShared");
|
||||
|
||||
qmlRegisterType<GettingStartedScope>("FlightGear", 1, 0, "GettingStartedScope");
|
||||
qmlRegisterType<GettingStartedTipsController>("FlightGear", 1, 0, "GettingStartedController");
|
||||
qmlRegisterType<GettingStartedTip>("FlightGear", 1, 0, "GettingStartedTip");
|
||||
qmlRegisterType<TipBackgroundBox>("FlightGear", 1, 0, "TipBackgroundBox");
|
||||
|
||||
QNetworkDiskCache* diskCache = new QNetworkDiskCache(this);
|
||||
SGPath cachePath = globals->get_fg_home() / "PreviewsCache";
|
||||
diskCache->setCacheDirectory(QString::fromStdString(cachePath.utf8Str()));
|
||||
|
@ -876,6 +885,18 @@ void LauncherController::setAircraftGridMode(bool aircraftGridMode)
|
|||
emit aircraftGridModeChanged(m_aircraftGridMode);
|
||||
}
|
||||
|
||||
void LauncherController::resetGettingStartedTips()
|
||||
{
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup("GettingStarted-DontShow");
|
||||
settings.remove(""); // remove all keys in the current group
|
||||
settings.endGroup();
|
||||
} // ensure settings are written, before we emit the signal
|
||||
|
||||
emit didResetGettingStartedTips();
|
||||
}
|
||||
|
||||
void LauncherController::setMinWindowSize(QSize sz)
|
||||
{
|
||||
if (sz == m_minWindowSize)
|
||||
|
|
|
@ -243,6 +243,7 @@ signals:
|
|||
void inAppChanged();
|
||||
void installedAircraftCountChanged(int installedAircraftCount);
|
||||
|
||||
void didResetGettingStartedTips();
|
||||
public slots:
|
||||
void setSelectedAircraft(QUrl selectedAircraft);
|
||||
|
||||
|
@ -263,6 +264,8 @@ public slots:
|
|||
void saveConfigAs();
|
||||
void setAircraftGridMode(bool aircraftGridMode);
|
||||
|
||||
void resetGettingStartedTips();
|
||||
|
||||
private slots:
|
||||
|
||||
void onAircraftInstalledCompleted(QModelIndex index);
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "LocationController.hxx"
|
||||
#include "QtLauncher.hxx"
|
||||
#include "UpdateChecker.hxx"
|
||||
#include "GettingStartedTip.hxx"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
#include "LocalAircraftCache.hxx"
|
||||
#include "PathListModel.hxx"
|
||||
#include "UnitsModel.hxx"
|
||||
#include "GettingStartedTip.hxx"
|
||||
|
||||
#if defined(SG_MAC)
|
||||
#include <GUI/CocoaHelpers.h>
|
||||
|
@ -110,6 +111,7 @@ void initNavCache()
|
|||
const char* waitForOtherMsg = QT_TRANSLATE_NOOP("initNavCache", "Another copy of FlightGear is creating the navigation database. Waiting for it to finish.");
|
||||
QString m = qApp->translate("initNavCache", waitForOtherMsg);
|
||||
|
||||
addSentryBreadcrumb("Launcher: showing wait for other process NavCache rebuild dialog", "info");
|
||||
QProgressDialog waitForRebuild(m,
|
||||
QString() /* cancel text */,
|
||||
0, 0, Q_NULLPTR,
|
||||
|
@ -133,6 +135,7 @@ void initNavCache()
|
|||
updateTimer.start(); // timer won't actually run until we process events
|
||||
waitForRebuild.exec();
|
||||
updateTimer.stop();
|
||||
addSentryBreadcrumb("Launcher: done waiting for other process NavCache rebuild dialog", "info");
|
||||
}
|
||||
|
||||
NavDataCache* cache = NavDataCache::createInstance();
|
||||
|
|
192
src/GUI/TipBackgroundBox.cxx
Normal file
192
src/GUI/TipBackgroundBox.cxx
Normal file
|
@ -0,0 +1,192 @@
|
|||
#include "TipBackgroundBox.hxx"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QDebug>
|
||||
|
||||
namespace {
|
||||
|
||||
void pathLineBy(QPainterPath& pp, double x, double y)
|
||||
{
|
||||
const auto c = pp.currentPosition();
|
||||
pp.lineTo(c.x() + x, c.y() + y);
|
||||
}
|
||||
|
||||
const double arrowDim1 = 20.0;
|
||||
const double arrowEndOffset = 30;
|
||||
|
||||
QPainterPath pathFromArrowAndGeometry(GettingStartedTip::Arrow arrow, const QRectF& g)
|
||||
{
|
||||
QPainterPath pp;
|
||||
|
||||
switch (arrow) {
|
||||
case GettingStartedTip::Arrow::TopCenter:
|
||||
pp.moveTo(g.center().x(), 0.0);
|
||||
pp.lineTo(g.center().x() + arrowDim1, arrowDim1);
|
||||
pp.lineTo(g.right(), arrowDim1);
|
||||
pp.lineTo(g.right(), g.bottom());
|
||||
pp.lineTo(g.left(), g.bottom());
|
||||
pp.lineTo(g.left(), arrowDim1);
|
||||
pp.lineTo(g.center().x() - arrowDim1, arrowDim1);
|
||||
pp.closeSubpath();
|
||||
break;
|
||||
|
||||
case GettingStartedTip::Arrow::BottomRight:
|
||||
pp.moveTo(g.right() - arrowEndOffset, g.bottom());
|
||||
pathLineBy(pp, arrowDim1, -arrowDim1);
|
||||
pp.lineTo(g.right(), g.bottom() - arrowDim1);
|
||||
pp.lineTo(g.right(), g.top());
|
||||
pp.lineTo(g.left(), g.top());
|
||||
pp.lineTo(g.left(), g.bottom() - arrowDim1);
|
||||
pp.lineTo(g.right() - arrowEndOffset - arrowDim1, g.bottom() - arrowDim1);
|
||||
pp.closeSubpath();
|
||||
break;
|
||||
|
||||
case GettingStartedTip::Arrow::TopRight:
|
||||
pp.moveTo(g.right() - arrowEndOffset, 0.0);
|
||||
pathLineBy(pp, arrowDim1, arrowDim1);
|
||||
pp.lineTo(g.right(), arrowDim1);
|
||||
pp.lineTo(g.right(), g.bottom());
|
||||
pp.lineTo(g.left(), g.bottom());
|
||||
pp.lineTo(g.left(), arrowDim1);
|
||||
pp.lineTo(g.right() - (arrowEndOffset + arrowDim1), arrowDim1);
|
||||
pp.closeSubpath();
|
||||
break;
|
||||
|
||||
case GettingStartedTip::Arrow::TopLeft:
|
||||
pp.moveTo(arrowEndOffset, 0.0);
|
||||
pathLineBy(pp, arrowDim1, arrowDim1);
|
||||
pp.lineTo(g.right(), arrowDim1);
|
||||
pp.lineTo(g.right(), g.bottom());
|
||||
pp.lineTo(g.left(), g.bottom());
|
||||
pp.lineTo(g.left(), arrowDim1);
|
||||
pp.lineTo(arrowEndOffset - arrowDim1, arrowDim1);
|
||||
pp.closeSubpath();
|
||||
break;
|
||||
|
||||
case GettingStartedTip::Arrow::LeftCenter:
|
||||
pp.moveTo(0.0, g.center().y());
|
||||
pathLineBy(pp, arrowDim1, -arrowDim1);
|
||||
pp.lineTo(arrowDim1, g.top());
|
||||
pp.lineTo(g.right(), g.top());
|
||||
pp.lineTo(g.right(), g.bottom());
|
||||
pp.lineTo(arrowDim1, g.bottom());
|
||||
pp.lineTo(arrowDim1, g.center().y() + arrowDim1);
|
||||
pp.closeSubpath();
|
||||
break;
|
||||
|
||||
case GettingStartedTip::Arrow::RightCenter:
|
||||
pp.moveTo(g.right(), g.center().y());
|
||||
pathLineBy(pp, -arrowDim1, arrowDim1);
|
||||
pp.lineTo(g.right() - arrowDim1, g.bottom());
|
||||
pp.lineTo(g.left(), g.bottom());
|
||||
pp.lineTo(g.left(), g.top());
|
||||
pp.lineTo(g.right() - arrowDim1, g.top());
|
||||
pp.lineTo(g.right() - arrowDim1, g.center().y() - arrowDim1);
|
||||
pp.closeSubpath();
|
||||
break;
|
||||
|
||||
|
||||
case GettingStartedTip::Arrow::LeftTop:
|
||||
pp.moveTo(0.0, g.top() + arrowDim1);
|
||||
pathLineBy(pp, arrowDim1, -arrowDim1);
|
||||
pp.lineTo(g.right(), g.top());
|
||||
pp.lineTo(g.right(), g.bottom());
|
||||
pp.lineTo(g.left() + arrowDim1, g.bottom());
|
||||
pp.lineTo(g.left() + arrowDim1, -g.left() + arrowDim1);
|
||||
pp.closeSubpath();
|
||||
break;
|
||||
|
||||
default:
|
||||
qWarning() << Q_FUNC_INFO << "unhandled:" << arrow;
|
||||
break;
|
||||
}
|
||||
|
||||
return pp;
|
||||
}
|
||||
|
||||
|
||||
} // of anonymous
|
||||
|
||||
int TipBackgroundBox::arrowSideOffset()
|
||||
{
|
||||
return static_cast<int>(arrowEndOffset);
|
||||
}
|
||||
|
||||
int TipBackgroundBox::arrowHeight()
|
||||
{
|
||||
return static_cast<int>(arrowDim1);
|
||||
}
|
||||
|
||||
TipBackgroundBox::TipBackgroundBox(QQuickItem* pr) :
|
||||
QQuickPaintedItem(pr)
|
||||
{
|
||||
}
|
||||
|
||||
GettingStartedTip::Arrow TipBackgroundBox::arrowPosition() const
|
||||
{
|
||||
return _arrow;
|
||||
}
|
||||
|
||||
QColor TipBackgroundBox::borderColor() const
|
||||
{
|
||||
return _borderColor;
|
||||
}
|
||||
|
||||
int TipBackgroundBox::borderWidth() const
|
||||
{
|
||||
return _borderWidth;
|
||||
}
|
||||
|
||||
QColor TipBackgroundBox::fill() const
|
||||
{
|
||||
return _fill;
|
||||
}
|
||||
|
||||
void TipBackgroundBox::setArrowPosition(GettingStartedTip::Arrow arrow)
|
||||
{
|
||||
if (_arrow == arrow)
|
||||
return;
|
||||
|
||||
_arrow = arrow;
|
||||
emit arrowPositionChanged(_arrow);
|
||||
update();
|
||||
}
|
||||
|
||||
void TipBackgroundBox::setBorderColor(QColor borderColor)
|
||||
{
|
||||
if (_borderColor == borderColor)
|
||||
return;
|
||||
|
||||
_borderColor = borderColor;
|
||||
emit borderColorChanged(_borderColor);
|
||||
update();
|
||||
}
|
||||
|
||||
void TipBackgroundBox::setBorderWidth(int borderWidth)
|
||||
{
|
||||
if (_borderWidth == borderWidth)
|
||||
return;
|
||||
|
||||
_borderWidth = borderWidth;
|
||||
emit borderWidthChanged(_borderWidth);
|
||||
update();
|
||||
}
|
||||
|
||||
void TipBackgroundBox::setFill(QColor fill)
|
||||
{
|
||||
if (_fill == fill)
|
||||
return;
|
||||
|
||||
_fill = fill;
|
||||
emit fillChanged(_fill);
|
||||
update();
|
||||
}
|
||||
|
||||
void TipBackgroundBox::paint(QPainter *painter)
|
||||
{
|
||||
QPainterPath pp = pathFromArrowAndGeometry(_arrow, QRectF{0.0,0.0,width(), height()});
|
||||
painter->setBrush(_fill);
|
||||
painter->setPen(QPen{_borderColor, static_cast<double>(_borderWidth)});
|
||||
painter->drawPath(pp);
|
||||
}
|
61
src/GUI/TipBackgroundBox.hxx
Normal file
61
src/GUI/TipBackgroundBox.hxx
Normal file
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include <QQuickPaintedItem>
|
||||
#include <QColor>
|
||||
|
||||
#include <GUI/GettingStartedTip.hxx>
|
||||
|
||||
class TipBackgroundBox : public QQuickPaintedItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(GettingStartedTip::Arrow arrow READ arrowPosition WRITE setArrowPosition NOTIFY arrowPositionChanged)
|
||||
|
||||
Q_PROPERTY(QColor fill READ fill WRITE setFill NOTIFY fillChanged)
|
||||
Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor NOTIFY borderColorChanged)
|
||||
Q_PROPERTY(int borderWidth READ borderWidth WRITE setBorderWidth NOTIFY borderWidthChanged)
|
||||
|
||||
public:
|
||||
TipBackgroundBox(QQuickItem* parent = nullptr);
|
||||
|
||||
GettingStartedTip::Arrow arrowPosition() const;
|
||||
|
||||
QColor borderColor() const;
|
||||
|
||||
int borderWidth() const;
|
||||
|
||||
QColor fill() const;
|
||||
|
||||
static int arrowSideOffset();
|
||||
|
||||
static int arrowHeight();
|
||||
|
||||
public slots:
|
||||
void setArrowPosition(GettingStartedTip::Arrow arrow);
|
||||
|
||||
void setBorderColor(QColor borderColor);
|
||||
|
||||
void setBorderWidth(int borderWidth);
|
||||
|
||||
void setFill(QColor fill);
|
||||
|
||||
signals:
|
||||
void arrowPositionChanged(GettingStartedTip::Arrow arrow);
|
||||
|
||||
void borderColorChanged(QColor borderColor);
|
||||
|
||||
void borderWidthChanged(int borderWidth);
|
||||
|
||||
void fillChanged(QColor fill);
|
||||
|
||||
protected:
|
||||
void paint(QPainter* painter) override;
|
||||
|
||||
private:
|
||||
GettingStartedTip::Arrow _arrow = GettingStartedTip::Arrow::TopCenter;
|
||||
|
||||
QColor _borderColor;
|
||||
int _borderWidth = -1; // off
|
||||
QColor _fill;
|
||||
};
|
||||
|
|
@ -107,6 +107,25 @@ Item {
|
|||
model.activeVariant = index
|
||||
root.select(model.uri)
|
||||
}
|
||||
|
||||
GettingStartedTip {
|
||||
tipId: "aircraftVariantTip"
|
||||
enabled: model.variantCount > 0
|
||||
standalone: true
|
||||
|
||||
Component.onCompleted: {
|
||||
if (enabled) {
|
||||
showOneShot();
|
||||
}
|
||||
}
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.bottom
|
||||
}
|
||||
arrow: GettingStartedTip.TopCenter
|
||||
text: qsTr("Click here to select different variants or models of this aircraft")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Controls 2.2
|
||||
import FlightGear.Launcher 1.0
|
||||
import FlightGear 1.0
|
||||
import "."
|
||||
|
||||
Rectangle {
|
||||
|
@ -24,16 +25,20 @@ Rectangle {
|
|||
|
||||
anchors.fill: parent
|
||||
contentWidth: parent.width
|
||||
contentHeight: content.childrenRect.height
|
||||
contentHeight: content.height
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
Item {
|
||||
id: content
|
||||
|
||||
width: root.width
|
||||
height: childrenRect.height
|
||||
|
||||
GettingStartedScope.controller: tipsLayer.controller
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
width: content.width - (Style.margin * 2)
|
||||
spacing: Style.margin
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
@ -172,6 +177,15 @@ Rectangle {
|
|||
onToggle: {
|
||||
aircraft.favourite = on;
|
||||
}
|
||||
|
||||
GettingStartedTip {
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
left: parent.right
|
||||
}
|
||||
arrow: GettingStartedTip.LeftCenter
|
||||
text: qsTr("Click here to mark this as a favourite aircraft")
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
|
@ -237,7 +251,12 @@ Rectangle {
|
|||
}
|
||||
|
||||
} // main layout column
|
||||
} // of main item
|
||||
|
||||
GettingStartedTipLayer {
|
||||
id: tipsLayer
|
||||
anchors.fill: parent
|
||||
scopeId: "aircraft-details"
|
||||
}
|
||||
} // of main item
|
||||
} // of Flickable
|
||||
} // of Rect
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick 2.2
|
||||
import FlightGear.Launcher 1.0 as FG
|
||||
import FlightGear 1.0
|
||||
import "." // -> forces the qmldir to be loaded
|
||||
|
||||
FocusScope
|
||||
|
@ -30,6 +31,8 @@ FocusScope
|
|||
}
|
||||
}
|
||||
|
||||
GettingStartedScope.controller: tipsLayer.controller
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: tabBar
|
||||
|
@ -43,6 +46,17 @@ FocusScope
|
|||
anchors.leftMargin: Style.margin
|
||||
gridMode: !_launcher.aircraftGridMode
|
||||
onClicked: _launcher.aircraftGridMode = !_launcher.aircraftGridMode
|
||||
|
||||
GettingStartedTip {
|
||||
tipId: "gridModeTip"
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.bottom
|
||||
}
|
||||
arrow: GettingStartedTip.TopLeft
|
||||
text: qsTr("Click here to switch between grid and list mode")
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
|
@ -57,6 +71,18 @@ FocusScope
|
|||
root.updateSelectionFromLauncher();
|
||||
}
|
||||
active: root.state == "installed"
|
||||
|
||||
GettingStartedTip {
|
||||
tipId: "installedAircraftTip"
|
||||
nextTip: "gridModeTip"
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.bottom
|
||||
}
|
||||
arrow: GettingStartedTip.TopCenter
|
||||
text: qsTr("Use this tab to view installed aircraft")
|
||||
}
|
||||
}
|
||||
|
||||
TabButton {
|
||||
|
@ -77,6 +103,18 @@ FocusScope
|
|||
root.updateSelectionFromLauncher();
|
||||
}
|
||||
active: root.state == "browse"
|
||||
|
||||
GettingStartedTip {
|
||||
tipId: "browseTip"
|
||||
nextTip: "searchAircraftTip"
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.bottom
|
||||
}
|
||||
arrow: GettingStartedTip.TopCenter
|
||||
text: qsTr("Use this tab to view available aircraft to download")
|
||||
}
|
||||
}
|
||||
|
||||
TabButton {
|
||||
|
@ -107,6 +145,18 @@ FocusScope
|
|||
}
|
||||
|
||||
active: root.state == "search"
|
||||
|
||||
GettingStartedTip {
|
||||
tipId: "searchAircraftTip"
|
||||
nextTip: "installedAircraftTip"
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.bottom
|
||||
}
|
||||
arrow: GettingStartedTip.TopRight
|
||||
text: qsTr("Enter text here to search aircraft names and descriptions.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,6 +173,7 @@ FocusScope
|
|||
id: ratingsHeader
|
||||
AircraftRatingsPanel {
|
||||
width: aircraftContent.width
|
||||
tips: tipsLayer
|
||||
onClearSelection: {
|
||||
_launcher.selectedAircraft = "";
|
||||
root.updateSelectionFromLauncher()
|
||||
|
@ -347,6 +398,13 @@ FocusScope
|
|||
detailsView.visible = false;
|
||||
}
|
||||
|
||||
// we don't want our tips to interfere with the details views
|
||||
GettingStartedTipLayer {
|
||||
id: tipsLayer
|
||||
anchors.fill: parent
|
||||
scopeId: "aircraft"
|
||||
}
|
||||
|
||||
AircraftDetailsView {
|
||||
id: detailsView
|
||||
anchors.fill: parent
|
||||
|
@ -358,5 +416,7 @@ FocusScope
|
|||
onClicked: root.goBack();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import QtQuick 2.4
|
||||
import FlightGear.Launcher 1.0
|
||||
import FlightGear 1.0
|
||||
import "." // -> forces the qmldir to be loaded
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
@ -66,7 +68,7 @@ Rectangle {
|
|||
height: 8
|
||||
width: 8
|
||||
radius: 4
|
||||
color: (model.index == root.activePreview) ? "white" : "#cfcfcf"
|
||||
color: (model.index == root.activePreview) ? "white" : Style.themeColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,5 +109,14 @@ Rectangle {
|
|||
root.activePreview = Math.min(root.activePreview + 1, root.previews.length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
GettingStartedTip {
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: parent.left
|
||||
}
|
||||
arrow: GettingStartedTip.RightCenter
|
||||
text: qsTr("Click here to cycle through preview images")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import QtQuick 2.2
|
||||
import FlightGear.Launcher 1.0 as FG
|
||||
import FlightGear 1.0
|
||||
|
||||
import "."
|
||||
|
||||
ListHeaderBox
|
||||
|
@ -7,6 +9,8 @@ ListHeaderBox
|
|||
id: root
|
||||
signal clearSelection();
|
||||
|
||||
GettingStartedScope.controller: tips.controller
|
||||
|
||||
contents: [
|
||||
|
||||
ToggleSwitch {
|
||||
|
@ -45,6 +49,23 @@ ListHeaderBox
|
|||
editRatingsPanel.visible = true
|
||||
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
editTip.showOneShot()
|
||||
}
|
||||
|
||||
GettingStartedTip {
|
||||
id: editTip
|
||||
tipId: "editRatingsTip"
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.bottom
|
||||
}
|
||||
standalone: true
|
||||
arrow: GettingStartedTip.TopRight
|
||||
text: qsTr("Click here to change which aircraft are shown or hidden based on their ratings")
|
||||
}
|
||||
},
|
||||
|
||||
// mouse are behind panel to consume clicks
|
||||
|
|
|
@ -12,7 +12,7 @@ Text {
|
|||
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: Style.subHeadingFontPixelSize
|
||||
color: "white"
|
||||
color: Style.themeContrastTextColor
|
||||
|
||||
onLinkActivated: {
|
||||
_launcher.requestUpdateAllAircraft();
|
||||
|
|
134
src/GUI/qml/GettingStartedTipDisplay.qml
Normal file
134
src/GUI/qml/GettingStartedTipDisplay.qml
Normal file
|
@ -0,0 +1,134 @@
|
|||
import QtQuick 2.4
|
||||
import QtQml 2.4
|
||||
import FlightGear 1.0
|
||||
import "."
|
||||
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
width: 100
|
||||
height: 100
|
||||
|
||||
property GettingStartedController controller: nil
|
||||
|
||||
property rect contentGeom: controller.contentGeometry
|
||||
property rect tipGeom: controller.tipGeometry
|
||||
|
||||
// pass the tip height into the controller, when it's valid
|
||||
Binding {
|
||||
target: controller
|
||||
property: "activeTipHeight"
|
||||
value: contentBox.height
|
||||
}
|
||||
|
||||
// the visible tip box
|
||||
TipBackgroundBox {
|
||||
id: tipBox
|
||||
|
||||
fill: Style.themeColor
|
||||
borderWidth: 1
|
||||
borderColor: Qt.darker(Style.themeColor)
|
||||
|
||||
x: tipGeom.x
|
||||
y: tipGeom.y
|
||||
width: tipGeom.width
|
||||
height: tipGeom.height
|
||||
arrow: controller.tip.arrow
|
||||
|
||||
Item {
|
||||
id: contentBox
|
||||
x: contentGeom.x
|
||||
y: contentGeom.y
|
||||
width: contentGeom.width
|
||||
height: tipText.height + closeText.height + (Style.margin * 3)
|
||||
|
||||
Text {
|
||||
id: tipText
|
||||
|
||||
font.pixelSize: Style.subHeadingFontPixelSize
|
||||
color: Style.themeContrastTextColor
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: Style.margin
|
||||
}
|
||||
|
||||
// size this to the text, after wrapping
|
||||
height: implicitHeight
|
||||
|
||||
text: controller.tip.text
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors {
|
||||
margins: Style.margin
|
||||
left: parent.left
|
||||
top: tipText.bottom
|
||||
}
|
||||
|
||||
text: "<"
|
||||
color: prevMouseArea.containsMouse ? Style.themeContrastLinkColor : Style.themeContrastTextColor
|
||||
visible: (controller.index > 0)
|
||||
MouseArea {
|
||||
id: prevMouseArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
|
||||
onClicked: {
|
||||
controller.index = controller.index - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: closeText
|
||||
|
||||
anchors {
|
||||
margins: Style.margin
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: tipText.bottom
|
||||
}
|
||||
|
||||
text: qsTr("Close")
|
||||
color: closeMouseArea.containsMouse ? Style.themeContrastLinkColor : Style.themeContrastTextColor
|
||||
width: implicitWidth
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
MouseArea {
|
||||
id: closeMouseArea
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: controller.close();
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors {
|
||||
margins: Style.margin
|
||||
right: parent.right
|
||||
top: tipText.bottom
|
||||
}
|
||||
|
||||
text: ">"
|
||||
color: nextMouseArea.containsMouse ? Style.themeContrastLinkColor : Style.themeContrastTextColor
|
||||
visible: controller.index < (controller.count - 1)
|
||||
|
||||
MouseArea {
|
||||
id: nextMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
controller.index = controller.index + 1
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
} // of tip content box
|
||||
} // of tip background shape
|
||||
}
|
45
src/GUI/qml/GettingStartedTipLayer.qml
Normal file
45
src/GUI/qml/GettingStartedTipLayer.qml
Normal file
|
@ -0,0 +1,45 @@
|
|||
import QtQuick 2.4
|
||||
import QtQml 2.4
|
||||
|
||||
import FlightGear 1.0
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property GettingStartedController controller: ctl
|
||||
|
||||
property alias scopeId: ctl.scopeId
|
||||
property alias active: ctl.active
|
||||
|
||||
function showOneShot(tip) {
|
||||
ctl.showOneShotTip(tip);
|
||||
}
|
||||
|
||||
GettingStartedController {
|
||||
id: ctl
|
||||
visualArea: root
|
||||
}
|
||||
|
||||
// ensure active-ness is updated, if the 'reset all tips' function is used
|
||||
Connections {
|
||||
target: _launcher
|
||||
onDidResetGettingStartedTips: ctl.tipsWereReset();
|
||||
}
|
||||
|
||||
// use a Loader to handle tip display, so we're not creating visual items
|
||||
// for the tip, in the common case that no tip is displayed.
|
||||
Loader {
|
||||
id: load
|
||||
source: (ctl.active && ctl.tipPositionValid) ? "qrc:///qml/GettingStartedTipDisplay.qml" : ""
|
||||
x: ctl.tipPositionInVisualArea.x
|
||||
y: ctl.tipPositionInVisualArea.y
|
||||
}
|
||||
|
||||
// pass the controller into our loaded item
|
||||
Binding {
|
||||
target: load.item
|
||||
property: "controller"
|
||||
value: ctl
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import QtQuick 2.4
|
|||
import QtQuick.Controls 2.2
|
||||
|
||||
import FlightGear.Launcher 1.0
|
||||
import FlightGear 1.0
|
||||
import "."
|
||||
|
||||
Item {
|
||||
|
@ -56,6 +57,20 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
font.pixelSize: Style.baseFontPixelSize * 1.5
|
||||
color: Style.baseTextColor
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: qsTr("<p>For help using this launcher, <a %1>try enabling the getting started hints</a>.</p>\n").arg("href=\"enable-tips\"");
|
||||
|
||||
onLinkActivated: {
|
||||
// reset tips, so they are shown again
|
||||
_launcher.resetGettingStartedTips();
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
font.pixelSize: Style.baseFontPixelSize * 1.5
|
||||
|
|
|
@ -7,6 +7,7 @@ Item {
|
|||
height: visible ? contentBox.height + (Style.margin * 2) : 0
|
||||
z: 100
|
||||
|
||||
property GettingStartedTipLayer tips
|
||||
property alias contents: contentBox.children
|
||||
|
||||
Rectangle {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick 2.4
|
||||
import "."
|
||||
import FlightGear.Launcher 1.0
|
||||
import FlightGear 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
@ -11,7 +12,6 @@ Item {
|
|||
property string summary: ""
|
||||
readonly property bool haveAdvancedSettings: anyAdvancedSettings(contents)
|
||||
|
||||
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: headerRect.height + contentBox.height + (Style.margin * 2)
|
||||
|
||||
|
@ -89,6 +89,18 @@ Item {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: parent.height
|
||||
visible: root.haveAdvancedSettings
|
||||
|
||||
GettingStartedTip {
|
||||
tipId: "expandSectionTip"
|
||||
enabled: root.haveAdvancedSettings
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.bottom
|
||||
}
|
||||
arrow: GettingStartedTip.TopRight
|
||||
text: qsTr("Click here to show advanced settings in this section")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import QtQuick 2.4
|
|||
import QtQuick.Controls 2.2
|
||||
|
||||
import FlightGear.Launcher 1.0
|
||||
import FlightGear 1.0
|
||||
import "."
|
||||
|
||||
Item {
|
||||
|
@ -38,6 +39,8 @@ Item {
|
|||
id: sectionColumn
|
||||
width: parent.width
|
||||
|
||||
GettingStartedScope.controller: tips.controller
|
||||
|
||||
Item {
|
||||
// top margin
|
||||
width: parent.width
|
||||
|
@ -67,6 +70,18 @@ Item {
|
|||
onSearch: {
|
||||
_launcher.settingsSearchTerm = term
|
||||
}
|
||||
|
||||
GettingStartedTip {
|
||||
tipId: "searchSettingsTip"
|
||||
nextTip: "expandSectionTip"
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.bottom
|
||||
}
|
||||
arrow: GettingStartedTip.TopRight
|
||||
text: qsTr("Enter text here to search for a setting")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -490,5 +505,11 @@ Item {
|
|||
]
|
||||
}
|
||||
} // of Column
|
||||
|
||||
GettingStartedTipLayer {
|
||||
id: tips
|
||||
anchors.fill: parent
|
||||
scopeId: "settings"
|
||||
}
|
||||
} // of Flickable
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import QtQuick 2.4
|
||||
import QtQml 2.4
|
||||
import FlightGear.Launcher 1.0
|
||||
import FlightGear 1.0
|
||||
|
||||
import "."
|
||||
|
||||
Item {
|
||||
|
@ -10,6 +12,8 @@ Item {
|
|||
signal showSelectedLocation();
|
||||
signal showFlightPlan();
|
||||
|
||||
GettingStartedScope.controller: tips.controller
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#7f7f7f"
|
||||
|
@ -166,6 +170,18 @@ Item {
|
|||
onSelected: {
|
||||
_launcher.selectedAircraft = _launcher.aircraftHistory.uriAt(index)
|
||||
}
|
||||
|
||||
GettingStartedTip {
|
||||
tipId: "aircraftHistoryTip"
|
||||
nextTip: "locationHistoryTip"
|
||||
arrow: GettingStartedTip.BottomRight
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.top
|
||||
}
|
||||
text: qsTr("Click here to select a recently used aircraft.")
|
||||
}
|
||||
}
|
||||
|
||||
// empty space in next row (thumbnail, long aircraft description)
|
||||
|
@ -240,6 +256,27 @@ Item {
|
|||
_launcher.selectedAircraftState = model.tagForState(index);
|
||||
}
|
||||
}
|
||||
|
||||
GettingStartedTip {
|
||||
id: aircraftStateTip
|
||||
tipId: "aircraftStateTip"
|
||||
arrow: GettingStartedTip.LeftCenter
|
||||
enabled: stateSelectionGroup.visible
|
||||
standalone: true
|
||||
|
||||
// show as a one-shot tip, first time we're enabled
|
||||
onEnabledChanged: {
|
||||
if (enabled) {
|
||||
tips.showOneShot(this)
|
||||
}
|
||||
}
|
||||
|
||||
x: parent.implicitWidth
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
text: qsTr("Use this menu to choose the starting state of the aircraft")
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
|
@ -282,6 +319,17 @@ Item {
|
|||
font.pixelSize: Style.headingFontPixelSize
|
||||
width: summaryGrid.middleColumnWidth
|
||||
onClicked: root.showSelectedLocation()
|
||||
|
||||
GettingStartedTip {
|
||||
tipId: "currentLocationTextTip"
|
||||
anchors {
|
||||
top: parent.bottom
|
||||
left: parent.left
|
||||
leftMargin: Style.strutSize
|
||||
}
|
||||
arrow: GettingStartedTip.TopLeft
|
||||
text: qsTr("Click this description to view and change the current location.")
|
||||
}
|
||||
}
|
||||
|
||||
HistoryPopup {
|
||||
|
@ -291,6 +339,16 @@ Item {
|
|||
onSelected: {
|
||||
_launcher.restoreLocation(_launcher.locationHistory.locationAt(index))
|
||||
}
|
||||
|
||||
GettingStartedTip {
|
||||
tipId: "locationHistoryTip"
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.top
|
||||
}
|
||||
arrow: GettingStartedTip.BottomRight
|
||||
text: qsTr("Click here to access recently used locations")
|
||||
}
|
||||
}
|
||||
|
||||
// flight plan summary row
|
||||
|
@ -338,4 +396,10 @@ Item {
|
|||
}
|
||||
}
|
||||
} // of summary box
|
||||
|
||||
GettingStartedTipLayer {
|
||||
id: tips
|
||||
anchors.fill: parent
|
||||
scopeId: "summary"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,6 +146,8 @@
|
|||
<file>qml/DownloadsInDocumentsWarning.qml</file>
|
||||
<file>qml/BackButton.qml</file>
|
||||
<file>qml/ScrollToBottomHint.qml</file>
|
||||
<file>qml/GettingStartedTipLayer.qml</file>
|
||||
<file>qml/GettingStartedTipDisplay.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/preview">
|
||||
<file alias="close-icon">assets/preview-close.png</file>
|
||||
|
|
Loading…
Add table
Reference in a new issue