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.refreshView() 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) 278 } 279 NIconButton { 280 icon: "calendar"; tooltipText: pluginApi.tr("panel.today") 281 onClicked: { mainInstance?.goToToday(); Qt.callLater(root.scrollToCurrentTime) } 282 } 283 NIconButton { 284 icon: "chevron-right" 285 onClicked: mainInstance?.navigateWeek(7) 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 } 456 event.accepted = true 457 } 458 } 459 460 NumberAnimation { 461 id: scrollAnim 462 target: calendarFlickable; property: "contentY"; duration: 100 463 easing.type: Easing.OutCubic; property real targetY: 0; to: targetY 464 } 465 466 ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } 467 468 Row { 469 width: parent.width 470 height: parent.height 471 472 // Time Column 473 Column { 474 width: root.timeColumnWidth 475 height: parent.height 476 Repeater { 477 model: 23 478 Rectangle { 479 width: root.timeColumnWidth 480 height: root.hourHeight 481 color: "transparent" 482 NText { 483 text: { 484 var hour = index + 1 485 if (mainInstance?.use12hourFormat) { 486 var d = new Date(); d.setHours(hour, 0, 0, 0) 487 return mainInstance.formatTime(d) 488 } 489 return (hour < 10 ? "0" : "") + hour + ':00' 490 } 491 anchors.right: parent.right 492 anchors.rightMargin: Style.marginS 493 anchors.verticalCenter: parent.top 494 anchors.verticalCenterOffset: root.hourHeight 495 font.pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant 496 } 497 } 498 } 499 } 500 501 // Hour Rectangles 502 Item { 503 width: 7 * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) 504 height: parent.height 505 506 Row { 507 anchors.fill: parent 508 spacing: root.daySpacing 509 Repeater { 510 model: 7 511 Column { 512 width: mainInstance?.dayColumnWidth 513 height: parent.height 514 Repeater { 515 model: 24 516 Rectangle { width: parent.width; height: 1; color: Color.mSurfaceVariant } 517 } 518 } 519 } 520 } 521 // Hour Lines 522 Repeater { 523 model: 24 524 Rectangle { 525 width: parent.width; height: 1 526 y: index * (root.hourHeight) 527 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.hourLineOpacitySetting || 0.5) 528 } 529 } 530 // Day Lines 531 Repeater { 532 model: 6 533 Rectangle { 534 width: 1; height: parent.height 535 x: (index + 1) * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) - ((root.daySpacing) / 2) 536 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.dayLineOpacitySetting || 0.9) 537 } 538 } 539 540 // Event positioning 541 Repeater { 542 model: mainInstance?.eventsModel 543 delegate: Item { 544 property var eventData: model 545 property int dayIndex: mainInstance?.getDisplayDayIndexForDate(model.startTime) ?? -1 546 property real startHour: model.startTime.getHours() + model.startTime.getMinutes() / 60 547 property real endHour: model.endTime.getHours() + model.endTime.getMinutes() / 60 548 property real duration: Math.max(0, (model.endTime - model.startTime) / 3600000) 549 550 property real exactHeight: Math.max(1, duration * (mainInstance?.hourHeight || 50) - 1) 551 property bool isCompact: exactHeight < 40 552 property var overlapInfo: mainInstance?.overlappingEventsData?.[index] ?? { 553 xOffset: 0, width: (mainInstance?.dayColumnWidth) - 8, lane: 0, totalLanes: 1 554 } 555 property real eventWidth: overlapInfo.width - 1 556 property real eventXOffset: overlapInfo.xOffset 557 558 visible: dayIndex >= 0 && dayIndex < 7 && duration > 0 559 width: eventWidth 560 height: exactHeight 561 x: dayIndex * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) + eventXOffset 562 y: startHour * (mainInstance?.hourHeight || 50) 563 z: 100 + overlapInfo.lane 564 565 Rectangle { 566 anchors.fill: parent 567 color: Color.mPrimary 568 radius: Style.radiusS 569 opacity: 0.9 570 clip: true 571 Rectangle { 572 visible: exactHeight < 5 && overlapInfo.lane > 0 573 anchors.fill: parent 574 color: "transparent" 575 radius: parent.radius 576 border.width: 1 577 border.color: Color.mPrimary 578 } 579 Loader { 580 anchors.fill: parent 581 anchors.margins: exactHeight < 10 ? 1 : Style.marginS 582 anchors.leftMargin: exactHeight < 10 ? 1 : Style.marginS + 3 583 sourceComponent: isCompact ? compactLayout : normalLayout 584 } 585 } 586 587 Component { 588 id: normalLayout 589 Column { 590 spacing: 2 591 width: parent.width - 3 592 NText { 593 visible: exactHeight >= 20 594 text: model.title 595 color: Color.mOnPrimary 596 font.pointSize: Style.fontSizeXS; font.weight: Font.Medium 597 elide: Text.ElideRight; width: parent.width 598 } 599 NText { 600 visible: exactHeight >= 30 601 text: mainInstance?.formatTimeRangeForDisplay(model) || "" 602 color: Color.mOnPrimary 603 font.pointSize: Style.fontSizeXXS; opacity: 0.9 604 elide: Text.ElideRight; width: parent.width 605 } 606 NText { 607 visible: exactHeight >= 45 && model.location && model.location !== "" 608 text: "\u26B2 " + (model.location || "") 609 color: Color.mOnPrimary 610 font.pointSize: Style.fontSizeXXS; opacity: 0.8 611 elide: Text.ElideRight; width: parent.width 612 } 613 } 614 } 615 616 Component { 617 id: compactLayout 618 NText { 619 text: exactHeight < 15 ? model.title : 620 model.title + " • " + (mainInstance?.formatTimeRangeForDisplay(model) || "") 621 color: Color.mOnPrimary 622 font.pointSize: exactHeight < 15 ? Style.fontSizeXXS : Style.fontSizeXS 623 font.weight: Font.Medium 624 elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter 625 width: parent.width - 3 626 } 627 } 628 629 MouseArea { 630 anchors.fill: parent 631 hoverEnabled: true 632 cursorShape: Qt.PointingHandCursor 633 onEntered: { 634 var tip = mainInstance?.getEventTooltip(model) || "" 635 TooltipService.show(parent, tip, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed) 636 } 637 onClicked: mainInstance?.handleEventClick(eventData) 638 onExited: TooltipService.hide() 639 } 640 } 641 } 642 643 // Time Indicator 644 Rectangle { 645 property var now: new Date() 646 property date today: new Date(now.getFullYear(), now.getMonth(), now.getDate()) 647 property date weekStartDate: mainInstance?.weekStart ?? new Date() 648 property date weekEndDate: mainInstance ? 649 new Date(mainInstance.weekStart.getFullYear(), mainInstance.weekStart.getMonth(), mainInstance.weekStart.getDate() + 7) : new Date() 650 property bool inCurrentWeek: today >= weekStartDate && today < weekEndDate 651 property int currentDay: mainInstance?.getDayIndexForDate(now) ?? -1 652 property real currentHour: now.getHours() + now.getMinutes() / 60 653 654 visible: inCurrentWeek && currentDay >= 0 655 width: mainInstance?.dayColumnWidth 656 height: 2 657 x: currentDay * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) 658 y: currentHour * (root.hourHeight) 659 color: Color.mError 660 radius: 1 661 z: 1000 662 Rectangle { 663 width: 8; height: 8; radius: 4; color: Color.mError 664 anchors.verticalCenter: parent.verticalCenter; x: -4 665 } 666 Timer { 667 interval: 60000; running: true; repeat: true 668 onTriggered: parent.now = new Date() 669 } 670 } 671 } 672 } 673 } 674 } 675 } 676 } 677 } 678 } 679}