fix: Refine header layout and animations

This commit is contained in:
Corey Woodworth
2025-10-10 23:34:56 -04:00
parent 5577938e50
commit e07e7e2bd1
+92 -128
View File
@@ -43,14 +43,20 @@ NPanel {
ColumnLayout { ColumnLayout {
id: blueColumn id: blueColumn
anchors.fill: parent anchors.top: parent.top
anchors.margins: Style.marginM * scaling anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.topMargin: Style.marginM * scaling
anchors.leftMargin: Style.marginM * scaling
anchors.bottomMargin: Style.marginM * scaling
anchors.rightMargin: clockItem.width + (Style.marginM * scaling * 2)
spacing: 0 spacing: 0
// Combined layout for weather icon, date, and weather text // Combined layout for weather icon, date, and weather text
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 60 * scaling height: 60 * scaling
clip: true
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
// Weather icon and temperature // Weather icon and temperature
@@ -70,14 +76,14 @@ NPanel {
text: { text: {
if (!weatherReady) if (!weatherReady)
return "" return ""
var temp = LocationService.data.weather.current_weather.temperature var temp = LocationService.data.weather.current_weather.temperature
var suffix = "C" var suffix = "C"
if (Settings.data.location.useFahrenheit) { if (Settings.data.location.useFahrenheit) {
temp = LocationService.celsiusToFahrenheit(temp) temp = LocationService.celsiusToFahrenheit(temp)
suffix = "F" suffix = "F"
} }
temp = Math.round(temp) temp = Math.round(temp)
return `${temp}°${suffix}` return `${temp}°${suffix}`
} }
pointSize: Style.fontSizeM * scaling pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
@@ -85,24 +91,27 @@ NPanel {
} }
} }
// Today day number // Today day number - with simple, stable animation
NText { NText {
opacity: content.isCurrentMonth ? 1.0 : 0.0 opacity: content.isCurrentMonth ? 1.0 : 0.0
Layout.preferredWidth: content.isCurrentMonth ? implicitWidth : 0 Layout.preferredWidth: content.isCurrentMonth ? implicitWidth : 0
elide: Text.ElideNone
clip: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
text: Time.date.getDate() text: Time.date.getDate()
pointSize: Style.fontSizeXXXL * 1.5 * scaling pointSize: Style.fontSizeXXXL * 1.5 * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnPrimary color: Color.mOnPrimary
Behavior on opacity { Behavior on opacity { NumberAnimation { duration: Style.animationFast } }
NumberAnimation { duration: Style.animationFast } Behavior on Layout.preferredWidth { NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad } }
}
} }
// Month, year, location // Month, year, location
ColumnLayout { ColumnLayout {
Layout.fillWidth: false // Give the whole column a fixed width to stabilize the layout
Layout.preferredWidth: 170 * scaling
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
spacing: -Style.marginXS * scaling spacing: -Style.marginXS * scaling
@@ -115,7 +124,6 @@ NPanel {
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnPrimary color: Color.mOnPrimary
Layout.alignment: Qt.AlignBaseline Layout.alignment: Qt.AlignBaseline
Layout.maximumWidth: 150 * scaling
elide: Text.ElideRight elide: Text.ElideRight
} }
@@ -135,8 +143,8 @@ NPanel {
text: { text: {
if (!weatherReady) if (!weatherReady)
return I18n.tr("calendar.weather.loading") return I18n.tr("calendar.weather.loading")
const chunks = Settings.data.location.name.split(",") const chunks = Settings.data.location.name.split(",")
return chunks[0] return chunks[0]
} }
pointSize: Style.fontSizeM * scaling pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
@@ -154,96 +162,86 @@ NPanel {
} }
} }
// Spacer between date and clock // Spacer to push content left
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
}
}
// Digital clock with circular progress // The Clock, anchored separately for stability
Item { Item {
width: Style.fontSizeXXXL * 1.9 * scaling id: clockItem
height: Style.fontSizeXXXL * 1.9 * scaling anchors.right: parent.right
Layout.alignment: Qt.AlignVCenter anchors.rightMargin: Style.marginM * scaling
anchors.verticalCenter: parent.verticalCenter
width: Style.fontSizeXXXL * 1.9 * scaling
height: Style.fontSizeXXXL * 1.9 * scaling
// Seconds circular progress Canvas {
Canvas { id: secondsProgress
id: secondsProgress anchors.fill: parent
anchors.fill: parent property real progress: Time.date.getSeconds() / 60
onProgressChanged: requestPaint()
property real progress: Time.date.getSeconds() / 60 Connections {
onProgressChanged: requestPaint() target: Time
function onDateChanged() {
Connections { const total = Time.date.getSeconds() * 1000 + Time.date.getMilliseconds()
target: Time secondsProgress.progress = total / 60000
function onDateChanged() {
const total = Time.date.getSeconds() * 1000 + Time.date.getMilliseconds()
secondsProgress.progress = total / 60000
}
}
onPaint: {
var ctx = getContext("2d")
var centerX = width / 2
var centerY = height / 2
var radius = Math.min(width, height) / 2 - 3 * scaling
ctx.reset()
// Background circle
ctx.beginPath()
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI)
ctx.lineWidth = 2.5 * scaling
ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15)
ctx.stroke()
// Progress arc
ctx.beginPath()
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI)
ctx.lineWidth = 2.5 * scaling
ctx.strokeStyle = Color.mOnPrimary
ctx.lineCap = "round"
ctx.stroke()
}
} }
}
onPaint: {
var ctx = getContext("2d")
var centerX = width / 2
var centerY = height / 2
var radius = Math.min(width, height) / 2 - 3 * scaling
ctx.reset()
ctx.beginPath()
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI)
ctx.lineWidth = 2.5 * scaling
ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15)
ctx.stroke()
ctx.beginPath()
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI)
ctx.lineWidth = 2.5 * scaling
ctx.strokeStyle = Color.mOnPrimary
ctx.lineCap = "round"
ctx.stroke()
}
}
// Digital clock ColumnLayout {
ColumnLayout { anchors.centerIn: parent
anchors.centerIn: parent spacing: -Style.marginXXS * scaling
spacing: -Style.marginXXS * scaling NText {
text: {
NText { var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH")
text: { return t.split(" ")[0]
var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH")
return t.split(" ")[0]
}
pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignHCenter
}
NText {
text: Qt.formatTime(Time.date, "mm")
pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignHCenter
}
} }
pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignHCenter
}
NText {
text: Qt.formatTime(Time.date, "mm")
pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignHCenter
} }
} }
} }
} }
// 6-day forecast (outside blue banner) // ... (rest of the file is unchanged) ...
RowLayout { RowLayout {
visible: weatherReady visible: weatherReady
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Repeater { Repeater {
model: weatherReady ? Math.min(6, LocationService.data.weather.daily.time.length) : 0 model: weatherReady ? Math.min(6, LocationService.data.weather.daily.time.length) : 0
delegate: ColumnLayout { delegate: ColumnLayout {
@@ -251,7 +249,6 @@ NPanel {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
NText { NText {
text: { text: {
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")) var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"))
@@ -262,14 +259,12 @@ NPanel {
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
} }
NIcon { NIcon {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index]) icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
pointSize: Style.fontSizeXXL * 1.5 * scaling pointSize: Style.fontSizeXXL * 1.5 * scaling
color: Color.mPrimary color: Color.mPrimary
} }
NText { NText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: { text: {
@@ -290,27 +285,19 @@ NPanel {
} }
} }
} }
// Loading indicator for weather
RowLayout { RowLayout {
visible: !weatherReady visible: !weatherReady
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
NBusyIndicator {} NBusyIndicator {}
} }
// Spacer
Item {} Item {}
// Navigation and divider
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
NDivider { NDivider {
Layout.fillWidth: true Layout.fillWidth: true
} }
NIconButton { NIconButton {
icon: "chevron-left" icon: "chevron-left"
onClicked: { onClicked: {
@@ -320,7 +307,6 @@ NPanel {
content.isCurrentMonth = content.checkIsCurrentMonth() content.isCurrentMonth = content.checkIsCurrentMonth()
} }
} }
NIconButton { NIconButton {
icon: "calendar" icon: "calendar"
onClicked: { onClicked: {
@@ -329,7 +315,6 @@ NPanel {
content.isCurrentMonth = true content.isCurrentMonth = true
} }
} }
NIconButton { NIconButton {
icon: "chevron-right" icon: "chevron-right"
onClicked: { onClicked: {
@@ -340,31 +325,24 @@ NPanel {
} }
} }
} }
// Names of days of the week
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 0 spacing: 0
Item { Item {
visible: Settings.data.location.showWeekNumberInCalendar visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0 Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0
} }
GridLayout { GridLayout {
Layout.fillWidth: true Layout.fillWidth: true
columns: 7 columns: 7
rows: 1 rows: 1
columnSpacing: 0 columnSpacing: 0
rowSpacing: 0 rowSpacing: 0
Repeater { Repeater {
model: 7 model: 7
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 0.6 * scaling Layout.preferredHeight: Style.baseWidgetSize * 0.6 * scaling
NText { NText {
anchors.centerIn: parent anchors.centerIn: parent
text: { text: {
@@ -381,27 +359,20 @@ NPanel {
} }
} }
} }
// Grid with weeks and days
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
spacing: 0 spacing: 0
// Column of week numbers
ColumnLayout { ColumnLayout {
visible: Settings.data.location.showWeekNumberInCalendar visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0 Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0
Layout.fillHeight: true Layout.fillHeight: true
spacing: 0 spacing: 0
Repeater { Repeater {
model: 6 model: 6
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
NText { NText {
anchors.centerIn: parent anchors.centerIn: parent
color: Color.mOutline color: Color.mOutline
@@ -433,42 +404,35 @@ NPanel {
} }
} }
} }
// Days Grid
MonthGrid { MonthGrid {
id: grid id: grid
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
month: Time.date.getMonth() month: Time.date.getMonth()
year: Time.date.getFullYear() year: Time.date.getFullYear()
locale: Qt.locale() locale: Qt.locale()
delegate: Item { delegate: Item {
Rectangle { Rectangle {
width: Style.baseWidgetSize * 0.9 * scaling width: Style.baseWidgetSize * 0.9 * scaling
height: Style.baseWidgetSize * 0.9 * scaling height: Style.baseWidgetSize * 0.9 * scaling
anchors.centerIn: parent anchors.centerIn: parent
radius: Style.radiusM * scaling radius: Style.radiusM * scaling
color: model.today ? Color.mSecondary : Color.transparent color: model.today ? Color.mSecondary : Color.transparent
NText { NText {
anchors.centerIn: parent anchors.centerIn: parent
text: model.day text: model.day
color: { color: {
if (model.today) if (model.today)
return Color.mOnSecondary return Color.mOnSecondary
if (model.month === grid.month) if (model.month === grid.month)
return Color.mOnSurface return Color.mOnSurface
return Color.mOnSurfaceVariant return Color.mOnSurfaceVariant
} }
opacity: model.month === grid.month ? 1.0 : 0.4 opacity: model.month === grid.month ? 1.0 : 0.4
pointSize: Style.fontSizeM * scaling pointSize: Style.fontSizeM * scaling
font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Style.animationFast duration: Style.animationFast