forked from Shinonome/dots-hyprland
waffles: more continuous infinite scrolling calendar
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
pragma Singleton
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
function getMonday(date, american = false) {
|
||||
const d = new Date(date); // Copy
|
||||
const day = d.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
|
||||
|
||||
// Calculate difference to Monday
|
||||
if (american) {
|
||||
// Week starts on Sunday
|
||||
d.setDate(d.getDate() - day);
|
||||
} else {
|
||||
// Week starts on Monday
|
||||
const diff = day === 0 ? -6 : 1 - day;
|
||||
d.setDate(d.getDate() + diff);
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
function sameDate(d1, d2) {
|
||||
return (
|
||||
d1.getFullYear() === d2.getFullYear() &&
|
||||
d1.getMonth() === d2.getMonth() &&
|
||||
d1.getDate() === d2.getDate()
|
||||
);
|
||||
}
|
||||
|
||||
function getIthDayDateOfSameWeek(date, i, american = false) {
|
||||
const monday = root.getMonday(date, american);
|
||||
const targetDate = new Date(monday);
|
||||
targetDate.setDate(monday.getDate() + i);
|
||||
return targetDate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Expose delegate
|
||||
property Component delegate: Text {
|
||||
required property var model
|
||||
text: model.day
|
||||
}
|
||||
|
||||
// Configuration
|
||||
property int paddingWeeks: 2 // 1 should be sufficient with proper clipping and no padding
|
||||
property bool american: false // 🍔🦅 = Sunday first
|
||||
|
||||
// Scrolling
|
||||
function scrollMonthsAndSnap(x) { // Scroll x months and snap to month
|
||||
const focusedDate = root.focusedDate;
|
||||
const focusedMonth = focusedDate.getMonth();
|
||||
const focusedYear = focusedDate.getFullYear();
|
||||
const targetMonth = focusedMonth + x;
|
||||
const targetDate = new Date(focusedYear, targetMonth, 1);
|
||||
const currentFirstShownDate = new Date(root.dateInFirstWeek.getTime() + (root.paddingWeeks * root.millisPerWeek));
|
||||
const diffMillis = targetDate.getTime() - currentFirstShownDate.getTime();
|
||||
const diffWeeks = Math.round(diffMillis / root.millisPerWeek);
|
||||
root.targetWeekDiff += diffWeeks;
|
||||
}
|
||||
property int weeksPerScroll: 1
|
||||
property real targetWeekDiff: 0
|
||||
property real weekDiff: targetWeekDiff
|
||||
property int contentWeekDiff: weekDiff // whole part of weekDiff
|
||||
property bool scrolling: false
|
||||
|
||||
Behavior on weekDiff {
|
||||
id: weekScrollBehavior
|
||||
animation: Looks.transition.scroll.createObject(this)
|
||||
}
|
||||
Timer {
|
||||
id: scrollAnimationCheckTimer
|
||||
interval: 30 // Should be plenty for 60fps
|
||||
onTriggered: root.scrolling = false;
|
||||
}
|
||||
onWeekDiffChanged: {
|
||||
scrolling = true;
|
||||
scrollAnimationCheckTimer.restart();
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onWheel: wheel => {
|
||||
root.targetWeekDiff += wheel.angleDelta.y / 120 * -root.weeksPerScroll; // Reverse cuz scrolling down should advance
|
||||
}
|
||||
}
|
||||
|
||||
// Date calculations
|
||||
readonly property int millisPerWeek: 7 * 24 * 60 * 60 * 1000
|
||||
readonly property int totalWeeks: 6 + (paddingWeeks * 2)
|
||||
readonly property int focusedWeekIndex: 2 // The third row, 0-indexed
|
||||
readonly property int focusDayOfWeekIndex: 6 // Non-American
|
||||
property date dateInFirstWeek: {
|
||||
const currentDate = new Date();
|
||||
const currentMonth = currentDate.getMonth();
|
||||
const currentYear = currentDate.getFullYear();
|
||||
const firstDayThisMonth = new Date(currentYear, currentMonth, 1);
|
||||
return new Date(firstDayThisMonth.getTime() - (paddingWeeks * millisPerWeek) + contentWeekDiff * millisPerWeek);
|
||||
}
|
||||
property date focusedDate: {
|
||||
// The last day of 3rd week shown is considered the focused month
|
||||
const addedTime = (root.paddingWeeks + root.focusedWeekIndex) * root.millisPerWeek
|
||||
const dateInTargetWeek = new Date(root.dateInFirstWeek.getTime() + addedTime);
|
||||
return DateUtils.getIthDayDateOfSameWeek(dateInTargetWeek, root.focusDayOfWeekIndex - (1 * root.american), root.american); // 4 = Thursday
|
||||
}
|
||||
property int focusedMonth: focusedDate.getMonth() + 1 // 0-indexed -> 1-indexed
|
||||
|
||||
// Sizes
|
||||
property real verticalPadding: 0
|
||||
property real buttonSize: 40
|
||||
property real buttonSpacing: 2
|
||||
implicitHeight: (6 * buttonSize) + (5 * buttonSpacing) + (2 * verticalPadding)
|
||||
implicitWidth: weeksColumn.implicitWidth
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
id: weeksColumn
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
y: {
|
||||
const spacePerExtraRow = root.buttonSize + root.buttonSpacing;
|
||||
const origin = -(spacePerExtraRow * root.paddingWeeks);
|
||||
const diff = root.weekDiff * spacePerExtraRow;
|
||||
return origin + (-diff % spacePerExtraRow) + root.verticalPadding;
|
||||
}
|
||||
|
||||
spacing: root.buttonSpacing
|
||||
Repeater {
|
||||
model: root.totalWeeks
|
||||
WeekRow {
|
||||
required property int index
|
||||
date: new Date(root.dateInFirstWeek.getTime() + (index * root.millisPerWeek))
|
||||
Layout.fillWidth: true
|
||||
spacing: root.buttonSpacing
|
||||
delegate: root.delegate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.services
|
||||
import qs.modules.common.functions
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
// Pls supply
|
||||
required property date date
|
||||
property bool sundayFirst: false
|
||||
|
||||
// Expose model and delegate for flexibility
|
||||
property list<var> model: {
|
||||
// Should expose props like here: https://doc.qt.io/qt-6/qml-qtquick-controls-monthgrid.html#delegate-prop
|
||||
// (except weekNumber because i'm lazy and it's not so important)
|
||||
const firstDayOfWeek = DateUtils.getMonday(root.date, root.sundayFirst);
|
||||
const weekDates = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const dayDate = new Date(firstDayOfWeek);
|
||||
dayDate.setDate(firstDayOfWeek.getDate() + i);
|
||||
weekDates.push({
|
||||
date: dayDate,
|
||||
day: dayDate.getDate(),
|
||||
month: dayDate.getMonth() + 1,
|
||||
year: dayDate.getFullYear(),
|
||||
today: DateUtils.sameDate(dayDate, DateTime.clock.date)
|
||||
});
|
||||
}
|
||||
return weekDates;
|
||||
}
|
||||
property Component delegate: Text {
|
||||
required property var model
|
||||
text: model.day
|
||||
}
|
||||
|
||||
// Obvious
|
||||
Repeater {
|
||||
model: root.model
|
||||
delegate: root.delegate
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user