diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bca704a..04c0481 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ qt_add_qml_module(${PROJECT_NAME} calendarlistmodel.cpp calendarlistmodel.h database.cpp database.h mnemonicattached.cpp mnemonicattached.h + timelineview.cpp timelineview.h QML_FILES ErrorNotification.qml Expander.qml diff --git a/src/ReservationsPage.qml b/src/ReservationsPage.qml index 5a3bb61..006d802 100644 --- a/src/ReservationsPage.qml +++ b/src/ReservationsPage.qml @@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Camper Page { title: qsTr("Reservations") @@ -17,17 +18,76 @@ Page { } ListView { - anchors.fill: parent + id: lodgingList - delegate: Text { + ScrollBar.vertical: verticalScroll + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.top: parent.top + headerPositioning: ListView.OverlayHeader + model: calendarList + width: 100 + + delegate: Label { required property string name text: name } - model: CalendarListModel { + header: Pane { + z: 2 + + Label { + text: qsTr("Lodging") + } } } + ListView { + id: timelineList + + anchors.bottom: parent.bottom + anchors.left: lodgingList.right + anchors.right: parent.right + anchors.top: parent.top + clip: true + contentWidth: 2184 + flickableDirection: Flickable.AutoFlickDirection + headerPositioning: ListView.OverlayHeader + model: calendarList + + ScrollBar.horizontal: ScrollBar { + } + ScrollBar.vertical: ScrollBar { + id: verticalScroll + + } + delegate: TimelineView { + fromDate: "2024-11-01" + height: 16 + toDate: "2025-01-31" + viewportWidth: ListView.view.width + viewportX: ListView.view.contentX + + delegate: Rectangle { + border.color: "black" + border.width: 1 + color: "blue" + } + } + header: Pane { + z: 2 + + Label { + text: qsTr("Calendar") + } + } + } + + CalendarListModel { + id: calendarList + + } + MnemonicAction { id: logoutAction diff --git a/src/timelineview.cpp b/src/timelineview.cpp new file mode 100644 index 0000000..b43b08e --- /dev/null +++ b/src/timelineview.cpp @@ -0,0 +1,260 @@ +#include "timelineview.h" +#include +#include + +struct TimelineViewItem +{ + TimelineViewItem(qint64 day, qint64 len, QQuickItem &qitem, TimelineView &view) + : item(&qitem) + , day(day) + { + item->setParentItem(&view); + item->setPosition(QPointF(day * view.dayWidth(), 0.0)); + item->setSize(QSizeF(len * view.dayWidth(), view.height())); + item->setVisible(true); + } + + ~TimelineViewItem() + { + item->setVisible(false); + item->setParent(nullptr); + } + + qreal left() const { return item->x(); } + qreal right() const { return item->x() + item->width(); } + + QQuickItem *item; + const qint64 day; +}; + +TimelineView::TimelineView(QQuickItem *parent) + : QQuickItem(parent) + , m_dayWidth(24.0) + , m_delegate(nullptr) + , m_fromDate() + , m_items() + , m_reusableItems() + , m_toDate() + , m_viewportX(0) + , m_prevViewportX(0) + , m_viewportWidth(0) + , m_prevViewportWidth(0) +{} + +TimelineView::~TimelineView() +{ + clear(); + drainItems(); +} + +qreal TimelineView::dayWidth() const +{ + return m_dayWidth; +} + +void TimelineView::setDayWidth(qreal width) +{ + if (width == m_dayWidth) { + return; + } + m_dayWidth = width; + emit dayWidthChanged(m_dayWidth); + updateImplicitWidth(); + clear(); + populate(); +} + +QQmlComponent *TimelineView::delegate() const +{ + return m_delegate; +} + +void TimelineView::setDelegate(QQmlComponent *delegate) +{ + if (delegate == m_delegate) { + return; + } + m_delegate = delegate; + emit delegateChanged(m_delegate); + clear(); + drainItems(); + populate(); + update(); +} + +QDate TimelineView::fromDate() const +{ + return m_fromDate; +} + +void TimelineView::setFromDate(QDate date) +{ + if (date == m_fromDate) { + return; + } + m_fromDate = date; + emit fromDateChanged(m_fromDate); + updateImplicitWidth(); + populate(); +} + +QDate TimelineView::toDate() const +{ + return m_toDate; +} + +void TimelineView::setToDate(QDate date) +{ + if (date == m_toDate) { + return; + } + m_toDate = date; + emit toDateChanged(m_toDate); + updateImplicitWidth(); + populate(); +} + +qreal TimelineView::viewportX() const +{ + return m_viewportX; +} + +void TimelineView::setViewportX(qreal x) +{ + if (x == m_viewportX) { + return; + } + m_viewportX = x; + emit viewportXChanged(m_viewportX); + populate(); +} + +qreal TimelineView::viewportWidth() const +{ + return m_viewportWidth; +} + +void TimelineView::setViewportWidth(qreal width) +{ + if (width == m_viewportWidth) { + return; + } + m_viewportWidth = width; + emit viewportWidthChanged(m_viewportWidth); + populate(); +} + +void TimelineView::componentComplete() +{ + QQuickItem::componentComplete(); + populate(); +} + +TimelineViewItem *TimelineView::createItem(qint64 day, qint64 len) +{ + QQuickItem *item = m_reusableItems.isEmpty() ? qobject_cast(m_delegate->create( + m_delegate->creationContext())) + : m_reusableItems.takeLast(); + if (!item) { + qmlWarning(m_delegate) << TimelineView::tr("Delegate must be of Item type"); + return nullptr; + } + auto *viewItem = new TimelineViewItem(day, len, *item, *this); + return viewItem; +} + +void TimelineView::releaseItem(TimelineViewItem *item) +{ + if (!item) { + return; + } + qsizetype index = m_items.indexOf(item); + if (index < 0) { + return; + } + QQuickItem *qitem = item->item; + if (!m_reusableItems.contains(qitem)) { + m_reusableItems.append(qitem); + } + m_items.removeAt(index); + delete item; +} + +void TimelineView::drainItems() +{ + qDeleteAll(m_reusableItems); + m_reusableItems.clear(); +} + +void TimelineView::clear() +{ + while (!m_items.isEmpty()) { + releaseItem(m_items.last()); + } +} + +void TimelineView::populate() +{ + if (!isComponentComplete() || !m_delegate) { + return; + } + if (m_viewportX > m_prevViewportX) { + // Delete from the left + while (!m_items.isEmpty()) { + TimelineViewItem *item = m_items.first(); + if (item->right() >= m_viewportX) { + break; + } + releaseItem(item); + } + } else if (m_viewportX < m_prevViewportX) { + // Insert from the left + for (qint64 day = m_items.isEmpty() ? qCeil(m_prevViewportX / m_dayWidth) - 1 + : m_items.first()->day - 1, + len = 1, + lastDay = qMax(-1, qFloor((m_viewportX - (len * m_dayWidth)) / m_dayWidth)); + day > lastDay; + day -= len) { + TimelineViewItem *viewItem = createItem(day, len); + if (!viewItem) { + break; + } + m_items.prepend(viewItem); + } + } + int currentRight = m_viewportX + m_viewportWidth; + int prevRight = m_prevViewportX + m_prevViewportWidth; + if (currentRight < prevRight) { + // Delete from the right + while (!m_items.isEmpty()) { + TimelineViewItem *item = m_items.last(); + if (item->left() < currentRight) { + break; + } + releaseItem(item); + } + } else if (currentRight > prevRight) { + // Insert from the right + for (qint64 day = m_items.isEmpty() ? qCeil(prevRight / m_dayWidth) - 1 + : m_items.last()->day + 1, + len = 1, + lastDay = qFloor((currentRight + (len * m_dayWidth)) / m_dayWidth); + day < lastDay; + day += len) { + TimelineViewItem *viewItem = createItem(day, len); + if (!viewItem) { + break; + } + m_items.append(viewItem); + } + } + m_prevViewportX = m_viewportX; + m_prevViewportWidth = m_viewportWidth; +} + +void TimelineView::updateImplicitWidth() +{ + setImplicitWidth(m_dayWidth * m_fromDate.daysTo(m_toDate)); +} + +#include "moc_timelineview.cpp" diff --git a/src/timelineview.h b/src/timelineview.h new file mode 100644 index 0000000..01e39c2 --- /dev/null +++ b/src/timelineview.h @@ -0,0 +1,77 @@ +#ifndef TIMELINEVIEW_H +#define TIMELINEVIEW_H + +#include +#include +#include + +struct TimelineViewItem; +class TimelineView : public QQuickItem +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(qreal dayWidth READ dayWidth WRITE setDayWidth NOTIFY dayWidthChanged) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(QDate fromDate READ fromDate WRITE setFromDate NOTIFY fromDateChanged) + Q_PROPERTY(QDate toDate READ toDate WRITE setToDate NOTIFY toDateChanged) + Q_PROPERTY(qreal viewportX READ viewportX WRITE setViewportX NOTIFY viewportXChanged) + Q_PROPERTY( + qreal viewportWidth READ viewportWidth WRITE setViewportWidth NOTIFY viewportWidthChanged) + +public: + TimelineView(QQuickItem *parent = nullptr); + ~TimelineView() override; + + qreal dayWidth() const; + void setDayWidth(qreal width); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + + QDate fromDate() const; + void setFromDate(QDate date); + + QDate toDate() const; + void setToDate(QDate date); + + qreal viewportX() const; + void setViewportX(qreal x); + + qreal viewportWidth() const; + void setViewportWidth(qreal width); + +signals: + void dayWidthChanged(qreal width); + void delegateChanged(QQmlComponent *delegate); + void fromDateChanged(QDate date); + void toDateChanged(QDate date); + void viewportXChanged(qreal x); + void viewportWidthChanged(qreal width); + +protected: + void componentComplete() override; + +private: + Q_DISABLE_COPY_MOVE(TimelineView) + + TimelineViewItem *createItem(qint64 day, qint64 len); + void releaseItem(TimelineViewItem *item); + void drainItems(); + + void clear(); + void populate(); + void updateImplicitWidth(); + + qreal m_dayWidth; + QQmlComponent *m_delegate; + QDate m_fromDate; + QList m_items; + QList m_reusableItems; + QDate m_toDate; + qreal m_viewportX; + qreal m_prevViewportX; + qreal m_viewportWidth; + qreal m_prevViewportWidth; +}; + +#endif // TIMELINEVIEW_H