Personal noctalia plugins collection
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Layouts
4import Quickshell
5import qs.Commons
6import qs.Services.Location
7import qs.Services.UI
8import qs.Widgets
9
10Item {
11 id: root
12 property var pluginApi: null
13 readonly property var mainInstance: pluginApi?.mainInstance
14 readonly property var geometryPlaceholder: panelContainer
15 property real contentPreferredWidth: 950 * Style.uiScaleRatio
16 property real contentPreferredHeight: 700 * Style.uiScaleRatio
17 property real topHeaderHeight: 60 * Style.uiScaleRatio
18 readonly property bool allowAttach: mainInstance ? mainInstance.panelModeSetting === "attached" : false
19 readonly property bool panelAnchorHorizontalCenter: mainInstance ? mainInstance.panelModeSetting === "centered" : false
20 readonly property bool panelAnchorVerticalCenter: mainInstance ? mainInstance.panelModeSetting === "centered" : false
21 anchors.fill: parent
22
23 property bool showCreateDialog: false
24
25 property real hourHeight: 50 * Style.uiScaleRatio
26 property real timeColumnWidth: 65 * Style.uiScaleRatio
27 property real daySpacing: 1 * Style.uiScaleRatio
28
29 // Panel doesn't need its own CalendarService connection - Main.qml handles it.
30 // When panel opens, trigger a fresh load if needed.
31 Component.onCompleted: mainInstance?.initializePlugin()
32 onVisibleChanged: if (visible && mainInstance) {
33 mainInstance.loadEvents()
34 mainInstance.goToToday()
35 Qt.callLater(root.scrollToCurrentTime)
36 }
37
38 // Scroll to time indicator position
39 function scrollToCurrentTime() {
40 if (!mainInstance || !calendarFlickable) return
41 var now = new Date(), today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
42 var weekStart = new Date(mainInstance.weekStart)
43 var weekEnd = new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate() + 7)
44
45 if (today >= weekStart && today < weekEnd) {
46 var currentHour = now.getHours() + now.getMinutes() / 60
47 var scrollPos = (currentHour * hourHeight) - (calendarFlickable.height / 2)
48 var maxScroll = Math.max(0, (24 * hourHeight) - calendarFlickable.height)
49 scrollAnim.targetY = Math.max(0, Math.min(scrollPos, maxScroll))
50 scrollAnim.start()
51 }
52 }
53
54 // Event creation dialog
55 Rectangle {
56 id: createEventOverlay
57 anchors.fill: parent
58 color: Qt.rgba(0, 0, 0, 0.5)
59 visible: showCreateDialog
60 z: 2000
61
62 MouseArea { anchors.fill: parent; onClicked: showCreateDialog = false }
63
64 Rectangle {
65 anchors.centerIn: parent
66 width: 400 * Style.uiScaleRatio
67 height: createDialogColumn.implicitHeight + 2 * Style.marginM
68 color: Color.mSurface
69 radius: Style.radiusM
70
71 MouseArea { anchors.fill: parent } // block clicks through
72
73 ColumnLayout {
74 id: createDialogColumn
75 anchors.fill: parent
76 anchors.margins: Style.marginM
77 spacing: Style.marginS
78
79 NText {
80 text: pluginApi.tr("panel.add_event")
81 font.pointSize: Style.fontSizeL; font.weight: Font.Bold
82 color: Color.mOnSurface
83 }
84
85 NText { text: pluginApi.tr("panel.summary"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
86 TextField {
87 id: createEventSummary
88 Layout.fillWidth: true
89 placeholderText: pluginApi.tr("panel.summary")
90 color: Color.mOnSurface
91 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
92 }
93
94 NText { text: pluginApi.tr("panel.date"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
95 TextField {
96 id: createEventDate
97 Layout.fillWidth: true
98 placeholderText: "YYYY-MM-DD"
99 color: Color.mOnSurface
100 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
101 }
102
103 RowLayout {
104 spacing: Style.marginS
105 ColumnLayout {
106 Layout.fillWidth: true
107 NText { text: pluginApi.tr("panel.start_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
108 TextField {
109 id: createEventStartTime
110 Layout.fillWidth: true
111 placeholderText: "HH:MM"
112 color: Color.mOnSurface
113 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
114 }
115 }
116 ColumnLayout {
117 Layout.fillWidth: true
118 NText { text: pluginApi.tr("panel.end_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
119 TextField {
120 id: createEventEndTime
121 Layout.fillWidth: true
122 placeholderText: "HH:MM"
123 color: Color.mOnSurface
124 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
125 }
126 }
127 }
128
129 NText { text: pluginApi.tr("panel.location"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
130 TextField {
131 id: createEventLocation
132 Layout.fillWidth: true
133 placeholderText: pluginApi.tr("panel.location")
134 color: Color.mOnSurface
135 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
136 }
137
138 NText { text: pluginApi.tr("panel.description"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
139 TextField {
140 id: createEventDescription
141 Layout.fillWidth: true
142 placeholderText: pluginApi.tr("panel.description")
143 color: Color.mOnSurface
144 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
145 }
146
147 NText { text: pluginApi.tr("panel.calendar_select"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS }
148 ComboBox {
149 id: calendarSelector
150 Layout.fillWidth: true
151 model: CalendarService.calendars || []
152 textRole: "name"
153 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS }
154 }
155
156 RowLayout {
157 Layout.fillWidth: true
158 spacing: Style.marginS
159
160 Item { Layout.fillWidth: true }
161
162 Rectangle {
163 Layout.preferredWidth: cancelBtn.implicitWidth + 2 * Style.marginM
164 Layout.preferredHeight: cancelBtn.implicitHeight + Style.marginS
165 color: Color.mSurfaceVariant; radius: Style.radiusS
166 NText {
167 id: cancelBtn; anchors.centerIn: parent
168 text: pluginApi.tr("panel.cancel"); color: Color.mOnSurfaceVariant
169 }
170 MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: showCreateDialog = false }
171 }
172
173 Rectangle {
174 Layout.preferredWidth: createBtn.implicitWidth + 2 * Style.marginM
175 Layout.preferredHeight: createBtn.implicitHeight + Style.marginS
176 color: Color.mPrimary; radius: Style.radiusS
177 opacity: createEventSummary.text.trim() !== "" ? 1.0 : 0.5
178 NText {
179 id: createBtn; anchors.centerIn: parent
180 text: pluginApi.tr("panel.create"); color: Color.mOnPrimary; font.weight: Font.Bold
181 }
182 MouseArea {
183 anchors.fill: parent; cursorShape: Qt.PointingHandCursor
184 onClicked: {
185 if (createEventSummary.text.trim() === "") return
186 var cal = CalendarService.calendars?.[calendarSelector.currentIndex]
187 var calUid = cal?.uid || ""
188 var dateParts = createEventDate.text.split("-")
189 var startParts = createEventStartTime.text.split(":")
190 var endParts = createEventEndTime.text.split(":")
191 var startDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1])-1, parseInt(dateParts[2]),
192 parseInt(startParts[0]), parseInt(startParts[1]), 0)
193 var endDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1])-1, parseInt(dateParts[2]),
194 parseInt(endParts[0]), parseInt(endParts[1]), 0)
195 mainInstance?.createEvent(calUid, createEventSummary.text.trim(),
196 Math.floor(startDate.getTime()/1000), Math.floor(endDate.getTime()/1000),
197 createEventLocation.text.trim(), createEventDescription.text.trim())
198 showCreateDialog = false
199 }
200 }
201 }
202 }
203 }
204 }
205 }
206
207 // UI
208 Rectangle {
209 id: panelContainer
210 anchors.fill: parent
211 color: "transparent"
212
213 ColumnLayout {
214 anchors.fill: parent
215 anchors.margins: Style.marginM
216 spacing: Style.marginM
217
218 //Header Section
219 Rectangle {
220 id: header
221 Layout.fillWidth: true
222 Layout.preferredHeight: topHeaderHeight
223 color: Color.mSurfaceVariant
224 radius: Style.radiusM
225
226 RowLayout {
227 anchors.margins: Style.marginM
228 anchors.fill: parent
229
230 NIcon { icon: "calendar-week"; pointSize: Style.fontSizeXXL; color: Color.mPrimary }
231
232 ColumnLayout {
233 Layout.fillHeight: true
234 spacing: 0
235 NText {
236 text: pluginApi.tr("panel.header")
237 font.pointSize: Style.fontSizeL; font.weight: Font.Bold; color: Color.mOnSurface
238 }
239 RowLayout {
240 spacing: Style.marginS
241 NText {
242 text: mainInstance?.monthRangeText || ""
243 font.pointSize: Style.fontSizeS; font.weight: Font.Medium; color: Color.mOnSurfaceVariant
244 }
245 Rectangle {
246 Layout.preferredWidth: 8; Layout.preferredHeight: 8; radius: 4
247 color: mainInstance?.isLoading ? Color.mError :
248 mainInstance?.syncStatus?.includes("No") ? Color.mError : Color.mOnSurfaceVariant
249 }
250 NText {
251 text: mainInstance?.syncStatus || ""
252 font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant
253 }
254 }
255 }
256
257 Item { Layout.fillWidth: true }
258
259 RowLayout {
260 spacing: Style.marginS
261 NIconButton {
262 icon: "plus"; tooltipText: pluginApi.tr("panel.add_event")
263 onClicked: {
264 createEventSummary.text = ""
265 createEventLocation.text = ""
266 createEventDescription.text = ""
267 var now = new Date()
268 var startH = now.getHours() + 1
269 createEventDate.text = now.getFullYear() + "-" + String(now.getMonth()+1).padStart(2,'0') + "-" + String(now.getDate()).padStart(2,'0')
270 createEventStartTime.text = String(startH).padStart(2,'0') + ":00"
271 createEventEndTime.text = String(startH+1).padStart(2,'0') + ":00"
272 showCreateDialog = true
273 }
274 }
275 NIconButton {
276 icon: "chevron-left"
277 onClicked: { mainInstance?.navigateWeek(-7); mainInstance?.loadEvents() }
278 }
279 NIconButton {
280 icon: "calendar"; tooltipText: pluginApi.tr("panel.today")
281 onClicked: { mainInstance?.goToToday(); mainInstance?.loadEvents(); Qt.callLater(root.scrollToCurrentTime) }
282 }
283 NIconButton {
284 icon: "chevron-right"
285 onClicked: { mainInstance?.navigateWeek(7); mainInstance?.loadEvents() }
286 }
287 NIconButton {
288 icon: "refresh"; tooltipText: I18n.tr("common.refresh")
289 onClicked: mainInstance?.loadEvents()
290 enabled: mainInstance ? !mainInstance.isLoading : false
291 }
292 NIconButton {
293 icon: "close"; tooltipText: I18n.tr("common.close")
294 onClicked: pluginApi.closePanel(pluginApi.panelOpenScreen)
295 }
296 }
297 }
298 }
299
300 // Calendar
301 Rectangle {
302 Layout.fillWidth: true
303 Layout.fillHeight: true
304 color: Color.mSurfaceVariant
305 radius: Style.radiusM
306 clip: true
307
308 Column {
309 anchors.fill: parent
310 spacing: 0
311
312 //Day Headers
313 Rectangle {
314 id: dayHeaders
315 width: parent.width
316 height: 56
317 color: Color.mSurfaceVariant
318 radius: Style.radiusM
319
320 Row {
321 anchors.fill: parent
322 anchors.leftMargin: root.timeColumnWidth
323 spacing: root.daySpacing
324
325 Repeater {
326 model: 7
327 Rectangle {
328 width: mainInstance?.dayColumnWidth
329 height: parent.height
330 color: "transparent"
331 property date dayDate: mainInstance?.weekDates?.[index] || new Date()
332 property bool isToday: {
333 var today = new Date()
334 return dayDate.getDate() === today.getDate() &&
335 dayDate.getMonth() === today.getMonth() &&
336 dayDate.getFullYear() === today.getFullYear()
337 }
338 Rectangle {
339 anchors.fill: parent
340 anchors.margins: 4
341 color: Color.mSurfaceVariant
342 border.color: isToday ? Color.mPrimary : "transparent"
343 border.width: 2
344 radius: Style.radiusM
345 Column {
346 anchors.centerIn: parent
347 spacing: 2
348 NText {
349 anchors.horizontalCenter: parent.horizontalCenter
350 text: dayDate ? I18n.locale.dayName(dayDate.getDay(), Locale.ShortFormat).toUpperCase() : ""
351 color: isToday ? Color.mPrimary : Color.mOnSurface
352 font.pointSize: Style.fontSizeS; font.weight: Font.Medium
353 }
354 NText {
355 anchors.horizontalCenter: parent.horizontalCenter
356 text: dayDate ? ((dayDate.getDate() < 10 ? "0" : "") + dayDate.getDate()) : ""
357 color: isToday ? Color.mPrimary : Color.mOnSurface
358 font.pointSize: Style.fontSizeM; font.weight: Font.Bold
359 }
360 }
361 }
362 }
363 }
364 }
365 }
366 // All-day row
367 Rectangle {
368 id: allDayEventsSection
369 width: parent.width
370 height: mainInstance ? Math.round(mainInstance.allDaySectionHeight * Style.uiScaleRatio) : 0
371 color: Color.mSurfaceVariant
372 visible: height > 0
373
374 Item {
375 id: allDayEventsContainer
376 anchors.fill: parent
377 anchors.leftMargin: root.timeColumnWidth
378
379 Repeater {
380 model: 6
381 delegate: Rectangle {
382 width: 1; height: parent.height
383 x: (index + 1) * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) - ((root.daySpacing) / 2)
384 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.dayLineOpacitySetting || 0.9)
385 }
386 }
387
388 Repeater {
389 model: mainInstance?.allDayEventsWithLayout || []
390 delegate: Item {
391 property var eventData: modelData
392 x: eventData.startDay * ((mainInstance?.dayColumnWidth) + (root.daySpacing))
393 y: eventData.lane * 25
394 width: (eventData.spanDays * ((mainInstance?.dayColumnWidth) + (root.daySpacing))) - (root.daySpacing)
395 height: 24
396
397 Rectangle {
398 anchors.fill: parent
399 color: Color.mTertiary
400 radius: Style.radiusS
401 NText {
402 anchors.fill: parent; anchors.margins: 4
403 text: eventData.title
404 color: Color.mOnTertiary
405 font.pointSize: Style.fontSizeXXS; font.weight: Font.Medium
406 elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter
407 }
408 }
409 MouseArea {
410 anchors.fill: parent
411 hoverEnabled: true
412 cursorShape: Qt.PointingHandCursor
413 onEntered: {
414 var tip = mainInstance?.getEventTooltip(eventData) || ""
415 TooltipService.show(parent, tip, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed)
416 }
417 onClicked: mainInstance?.handleEventClick(eventData)
418 onExited: TooltipService.hide()
419 }
420 }
421 }
422 }
423 }
424 // Calendar flickable
425 Rectangle {
426 width: parent.width
427 height: parent.height - dayHeaders.height - (allDayEventsSection.visible ? allDayEventsSection.height : 0)
428 color: Color.mSurfaceVariant
429 radius: Style.radiusM
430 clip: true
431
432 Flickable {
433 id: calendarFlickable
434 anchors.fill: parent
435 clip: true
436 contentHeight: 24 * (root.hourHeight)
437 boundsBehavior: Flickable.DragOverBounds
438
439 Component.onCompleted: {
440 calendarFlickable.forceActiveFocus()
441 }
442
443 // Keyboard interaction
444 Keys.onPressed: function(event) {
445 if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) {
446 var step = root.hourHeight
447 var targetY = event.key === Qt.Key_Up ? Math.max(0, contentY - step) :
448 Math.min(Math.max(0, contentHeight - height), contentY + step)
449 scrollAnim.targetY = targetY
450 scrollAnim.start()
451 event.accepted = true
452 } else if (event.key === Qt.Key_Left || event.key === Qt.Key_Right) {
453 if (mainInstance) {
454 mainInstance.navigateWeek(event.key === Qt.Key_Left ? -7 : 7)
455 mainInstance.loadEvents()
456 }
457 event.accepted = true
458 }
459 }
460
461 NumberAnimation {
462 id: scrollAnim
463 target: calendarFlickable; property: "contentY"; duration: 100
464 easing.type: Easing.OutCubic; property real targetY: 0; to: targetY
465 }
466
467 ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
468
469 Row {
470 width: parent.width
471 height: parent.height
472
473 // Time Column
474 Column {
475 width: root.timeColumnWidth
476 height: parent.height
477 Repeater {
478 model: 23
479 Rectangle {
480 width: root.timeColumnWidth
481 height: root.hourHeight
482 color: "transparent"
483 NText {
484 text: {
485 var hour = index + 1
486 if (mainInstance?.use12hourFormat) {
487 var d = new Date(); d.setHours(hour, 0, 0, 0)
488 return mainInstance.formatTime(d)
489 }
490 return (hour < 10 ? "0" : "") + hour + ':00'
491 }
492 anchors.right: parent.right
493 anchors.rightMargin: Style.marginS
494 anchors.verticalCenter: parent.top
495 anchors.verticalCenterOffset: root.hourHeight
496 font.pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant
497 }
498 }
499 }
500 }
501
502 // Hour Rectangles
503 Item {
504 width: 7 * ((mainInstance?.dayColumnWidth) + (root.daySpacing))
505 height: parent.height
506
507 Row {
508 anchors.fill: parent
509 spacing: root.daySpacing
510 Repeater {
511 model: 7
512 Column {
513 width: mainInstance?.dayColumnWidth
514 height: parent.height
515 Repeater {
516 model: 24
517 Rectangle { width: parent.width; height: 1; color: Color.mSurfaceVariant }
518 }
519 }
520 }
521 }
522 // Hour Lines
523 Repeater {
524 model: 24
525 Rectangle {
526 width: parent.width; height: 1
527 y: index * (root.hourHeight)
528 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.hourLineOpacitySetting || 0.5)
529 }
530 }
531 // Day Lines
532 Repeater {
533 model: 6
534 Rectangle {
535 width: 1; height: parent.height
536 x: (index + 1) * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) - ((root.daySpacing) / 2)
537 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.dayLineOpacitySetting || 0.9)
538 }
539 }
540
541 // Event positioning
542 Repeater {
543 model: mainInstance?.eventsModel
544 delegate: Item {
545 property var eventData: model
546 property int dayIndex: mainInstance?.getDisplayDayIndexForDate(model.startTime) ?? -1
547 property real startHour: model.startTime.getHours() + model.startTime.getMinutes() / 60
548 property real endHour: model.endTime.getHours() + model.endTime.getMinutes() / 60
549 property real duration: Math.max(0, (model.endTime - model.startTime) / 3600000)
550
551 property real exactHeight: Math.max(1, duration * (mainInstance?.hourHeight || 50) - 1)
552 property bool isCompact: exactHeight < 40
553 property var overlapInfo: mainInstance?.overlappingEventsData?.[index] ?? {
554 xOffset: 0, width: (mainInstance?.dayColumnWidth) - 8, lane: 0, totalLanes: 1
555 }
556 property real eventWidth: overlapInfo.width - 1
557 property real eventXOffset: overlapInfo.xOffset
558
559 visible: dayIndex >= 0 && dayIndex < 7 && duration > 0
560 width: eventWidth
561 height: exactHeight
562 x: dayIndex * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) + eventXOffset
563 y: startHour * (mainInstance?.hourHeight || 50)
564 z: 100 + overlapInfo.lane
565
566 Rectangle {
567 anchors.fill: parent
568 color: Color.mPrimary
569 radius: Style.radiusS
570 opacity: 0.9
571 clip: true
572 Rectangle {
573 visible: exactHeight < 5 && overlapInfo.lane > 0
574 anchors.fill: parent
575 color: "transparent"
576 radius: parent.radius
577 border.width: 1
578 border.color: Color.mPrimary
579 }
580 Loader {
581 anchors.fill: parent
582 anchors.margins: exactHeight < 10 ? 1 : Style.marginS
583 anchors.leftMargin: exactHeight < 10 ? 1 : Style.marginS + 3
584 sourceComponent: isCompact ? compactLayout : normalLayout
585 }
586 }
587
588 Component {
589 id: normalLayout
590 Column {
591 spacing: 2
592 width: parent.width - 3
593 NText {
594 visible: exactHeight >= 20
595 text: model.title
596 color: Color.mOnPrimary
597 font.pointSize: Style.fontSizeXS; font.weight: Font.Medium
598 elide: Text.ElideRight; width: parent.width
599 }
600 NText {
601 visible: exactHeight >= 30
602 text: mainInstance?.formatTimeRangeForDisplay(model) || ""
603 color: Color.mOnPrimary
604 font.pointSize: Style.fontSizeXXS; opacity: 0.9
605 elide: Text.ElideRight; width: parent.width
606 }
607 NText {
608 visible: exactHeight >= 45 && model.location && model.location !== ""
609 text: "\u26B2 " + (model.location || "")
610 color: Color.mOnPrimary
611 font.pointSize: Style.fontSizeXXS; opacity: 0.8
612 elide: Text.ElideRight; width: parent.width
613 }
614 }
615 }
616
617 Component {
618 id: compactLayout
619 NText {
620 text: exactHeight < 15 ? model.title :
621 model.title + " • " + (mainInstance?.formatTimeRangeForDisplay(model) || "")
622 color: Color.mOnPrimary
623 font.pointSize: exactHeight < 15 ? Style.fontSizeXXS : Style.fontSizeXS
624 font.weight: Font.Medium
625 elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter
626 width: parent.width - 3
627 }
628 }
629
630 MouseArea {
631 anchors.fill: parent
632 hoverEnabled: true
633 cursorShape: Qt.PointingHandCursor
634 onEntered: {
635 var tip = mainInstance?.getEventTooltip(model) || ""
636 TooltipService.show(parent, tip, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed)
637 }
638 onClicked: mainInstance?.handleEventClick(eventData)
639 onExited: TooltipService.hide()
640 }
641 }
642 }
643
644 // Time Indicator
645 Rectangle {
646 property var now: new Date()
647 property date today: new Date(now.getFullYear(), now.getMonth(), now.getDate())
648 property date weekStartDate: mainInstance?.weekStart ?? new Date()
649 property date weekEndDate: mainInstance ?
650 new Date(mainInstance.weekStart.getFullYear(), mainInstance.weekStart.getMonth(), mainInstance.weekStart.getDate() + 7) : new Date()
651 property bool inCurrentWeek: today >= weekStartDate && today < weekEndDate
652 property int currentDay: mainInstance?.getDayIndexForDate(now) ?? -1
653 property real currentHour: now.getHours() + now.getMinutes() / 60
654
655 visible: inCurrentWeek && currentDay >= 0
656 width: mainInstance?.dayColumnWidth
657 height: 2
658 x: currentDay * ((mainInstance?.dayColumnWidth) + (root.daySpacing))
659 y: currentHour * (root.hourHeight)
660 color: Color.mError
661 radius: 1
662 z: 1000
663 Rectangle {
664 width: 8; height: 8; radius: 4; color: Color.mError
665 anchors.verticalCenter: parent.verticalCenter; x: -4
666 }
667 Timer {
668 interval: 60000; running: true; repeat: true
669 onTriggered: parent.now = new Date()
670 }
671 }
672 }
673 }
674 }
675 }
676 }
677 }
678 }
679 }
680}