NightLight: refactored the code to make simpler

- using intensity instead of warmth
- animated color transition
- removed unecessary bindings and double properties
- using better icons to avoid confusion with brightness
- polished settings UI
This commit is contained in:
LemmyCook 2025-08-26 18:48:10 -04:00
parent 76f0368a64
commit 4cd94f0426
6 changed files with 113 additions and 216 deletions

View file

@ -261,7 +261,6 @@ Singleton {
// night light // night light
property JsonObject nightLight: JsonObject { property JsonObject nightLight: JsonObject {
property bool enabled: false property bool enabled: false
property real warmth: 0.0
property real intensity: 0.8 property real intensity: 0.8
property string startTime: "20:00" property string startTime: "20:00"
property string stopTime: "07:00" property string stopTime: "07:00"

View file

@ -15,33 +15,24 @@ Item {
implicitHeight: pill.height implicitHeight: pill.height
visible: true visible: true
function getIcon() {
if (!NightLightService.enabled) {
return "light_mode"
}
return NightLightService.isActive ? "dark_mode" : "light_mode"
}
function getTooltipText() {
if (!NightLightService.enabled) {
return "Night Light: Disabled\nLeft click to open settings.\nRight click to enable."
}
var status = NightLightService.isActive ? "Active" : "Inactive (outside schedule)"
var warmth = Math.round(NightLightService.warmth * 10)
var schedule = NightLightService.autoSchedule ? `Schedule: ${NightLightService.startTime} - ${NightLightService.stopTime}` : "Manual mode"
return `Night Light: ${status}\nWarmth: ${warmth}/10\n${schedule}\nLeft click to open settings.\nRight click to toggle.`
}
NPill { NPill {
id: pill id: pill
icon: getIcon() icon: NightLightService.isActive ? "bedtime" : "bedtime_off"
iconCircleColor: NightLightService.isActive ? Color.mSecondary : Color.mOnSurfaceVariant iconCircleColor: NightLightService.isActive ? Color.mSecondary : Color.mOnSurfaceVariant
collapsedIconColor: NightLightService.isActive ? Color.mOnSecondary : Color.mOnSurface collapsedIconColor: NightLightService.isActive ? Color.mOnSecondary : Color.mOnSurface
autoHide: false autoHide: false
text: NightLightService.enabled ? (NightLightService.isActive ? "ON" : "OFF") : "OFF" text: NightLightService.isActive ? "On" : "Off"
tooltipText: getTooltipText() tooltipText: {
if (!Settings.isLoaded || !Settings.data.nightLight.enabled) {
return "Night Light: Disabled\nLeft click to open settings.\nRight click to enable."
}
var status = NightLightService.isActive ? "Active" : "Inactive (outside schedule)"
var intensity = Math.round(Settings.data.nightLight.intensity * 100)
var schedule = Settings.data.nightLight.autoSchedule ? `Schedule: ${Settings.data.nightLight.startTime} - ${Settings.data.nightLight.stopTime}` : "Manual mode"
return `Intensity: ${intensity}%\n${schedule}\nLeft click to open settings.\nRight click to toggle.`
}
onClicked: { onClicked: {
// Left click - open settings // Left click - open settings
@ -52,34 +43,13 @@ Item {
onRightClicked: { onRightClicked: {
// Right click - toggle night light // Right click - toggle night light
NightLightService.toggle() Settings.data.nightLight.enabled = !Settings.data.nightLight.enabled
} }
}
// Update when service state changes onWheel: delta => {
Connections { var diff = delta > 0 ? 0.05 : -0.05
target: NightLightService Settings.data.nightLight.intensity = Math.max(0, Math.min(1.0,
function onEnabledChanged() { Settings.data.nightLight.intensity + diff))
pill.icon = getIcon() }
pill.text = NightLightService.enabled ? (NightLightService.isActive ? "ON" : "OFF") : "OFF"
pill.tooltipText = getTooltipText()
}
function onIsActiveChanged() {
pill.icon = getIcon()
pill.text = NightLightService.enabled ? (NightLightService.isActive ? "ON" : "OFF") : "OFF"
pill.tooltipText = getTooltipText()
}
function onWarmthChanged() {
pill.tooltipText = getTooltipText()
}
function onStartTimeChanged() {
pill.tooltipText = getTooltipText()
}
function onStopTimeChanged() {
pill.tooltipText = getTooltipText()
}
function onAutoScheduleChanged() {
pill.tooltipText = getTooltipText()
}
} }
} }

View file

@ -11,17 +11,11 @@ Variants {
required property ShellScreen modelData required property ShellScreen modelData
readonly property real scaling: ScalingService.scale(modelData) readonly property real scaling: ScalingService.scale(modelData)
active: NightLightService.enabled active: NightLightService.isActive
sourceComponent: PanelWindow { sourceComponent: PanelWindow {
id: nightlightWindow
screen: modelData screen: modelData
visible: NightLightService.isActive
color: Color.transparent color: Color.transparent
mask: Region {}
anchors { anchors {
top: true top: true
bottom: true bottom: true
@ -29,6 +23,9 @@ Variants {
right: true right: true
} }
// Ensure a full click through
mask: Region {}
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
@ -37,29 +34,13 @@ Variants {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: NightLightService.overlayColor color: NightLightService.overlayColor
}
// Safe connection that checks if the window still exists Behavior on color {
Connections { ColorAnimation {
target: NightLightService duration: Style.animationSlow
function onIsActiveChanged() {
if (nightlightWindow && typeof nightlightWindow.visible !== 'undefined') {
nightlightWindow.visible = NightLightService.isActive
} }
} }
} }
// Cleanup when component is being destroyed
Component.onDestruction: {
Logger.log("NightLight", "PanelWindow being destroyed")
}
}
// Safe state changes
onActiveChanged: {
if (!active) {
Logger.log("NightLight", "Loader deactivating for screen:", modelData.name)
}
} }
} }
} }

View file

@ -249,7 +249,7 @@ Item {
NToggle { NToggle {
label: "Enable Night Light" label: "Enable Night Light"
description: "Apply a warm color filter to reduce blue light emission." description: "Apply a warm color filter to reduce blue light emission."
checked: NightLightService.enabled checked: Settings.data.nightLight.enabled
onToggled: checked => { onToggled: checked => {
Settings.data.nightLight.enabled = checked Settings.data.nightLight.enabled = checked
} }
@ -258,62 +258,50 @@ Item {
NToggle { NToggle {
label: "Auto Schedule" label: "Auto Schedule"
description: "Automatically enable night light based on time schedule." description: "Automatically enable night light based on time schedule."
checked: NightLightService.autoSchedule checked: Settings.data.nightLight.autoSchedule
enabled: NightLightService.enabled enabled: Settings.data.nightLight.enabled
onToggled: checked => { onToggled: checked => Settings.data.nightLight.autoSchedule = checked
NightLightService.setAutoSchedule(checked)
}
} }
// Warmth settings // Intensity settings
NText { ColumnLayout {
text: "Warmth" spacing: Style.marginXS * scaling
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
enabled: NightLightService.enabled
}
NText { NText {
text: "Higher values create warmer (more orange) light, lower values create cooler (more blue) light." text: "Intensity"
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant font.weight: Style.fontWeightBold
wrapMode: Text.WordWrap color: Color.mOnSurface
Layout.fillWidth: true enabled: Settings.data.nightLight.enabled
enabled: NightLightService.enabled }
NText {
text: "Higher values create warmer (more orange) light, lower values create cooler (more blue) light."
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
enabled: Settings.data.nightLight.enabled
}
} }
RowLayout { RowLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
enabled: NightLightService.enabled enabled: Settings.data.nightLight.enabled
NSlider { NSlider {
id: warmthSlider
from: 0 from: 0
to: 10 to: 1
stepSize: 1 stepSize: 0.01
value: Math.round(NightLightService.warmth * 10) value: Settings.data.nightLight.intensity
onPressedChanged: { onMoved: Settings.data.nightLight.intensity = value
if (!pressed) {
NightLightService.setWarmth(value / 10)
}
}
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: 150 * scaling Layout.minimumWidth: 150 * scaling
} }
Connections {
target: NightLightService
function onWarmthChanged() {
if (!warmthSlider.pressed) {
warmthSlider.value = Math.round(NightLightService.warmth * 10)
}
}
}
NText { NText {
text: `${warmthSlider.value}` text: `${Math.round(Settings.data.nightLight.intensity * 100)}%`
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.minimumWidth: 60 * scaling Layout.minimumWidth: 60 * scaling
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
@ -321,55 +309,55 @@ Item {
} }
// Schedule settings // Schedule settings
NText { ColumnLayout {
text: "Schedule" spacing: Style.marginXS * scaling
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
enabled: NightLightService.enabled
}
RowLayout { NText {
spacing: Style.marginL * scaling text: "Schedule"
Layout.fillWidth: true font.pointSize: Style.fontSizeM * scaling
enabled: NightLightService.enabled font.weight: Style.fontWeightBold
color: Color.mOnSurface
ColumnLayout { enabled: Settings.data.nightLight.enabled
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Start Time"
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
}
NComboBox {
model: timeOptions
currentKey: NightLightService.startTime
placeholder: "Select time"
onSelected: function (key) {
NightLightService.setSchedule(key, NightLightService.stopTime)
}
}
} }
ColumnLayout { RowLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
enabled: Settings.data.nightLight.enabled
NText { ColumnLayout {
text: "Stop Time" spacing: Style.marginXXS * scaling
font.pointSize: Style.fontSizeS * scaling Layout.fillWidth: true
color: Color.mOnSurfaceVariant
NText {
text: "Start Time"
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
}
NComboBox {
model: timeOptions
currentKey: Settings.data.nightLight.startTime
placeholder: "Select start time"
onSelected: key => Settings.data.nightLight.startTime = key
}
} }
NComboBox { ColumnLayout {
model: timeOptions spacing: Style.marginXXS * scaling
currentKey: NightLightService.stopTime Layout.fillWidth: true
placeholder: "Select time"
onSelected: function (key) { NText {
NightLightService.setSchedule(NightLightService.startTime, key) text: "Stop Time"
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
}
NComboBox {
model: timeOptions
currentKey: Settings.data.nightLight.stopTime
placeholder: "Select stop time"
onSelected: key => Settings.data.nightLight.stopTime = key
} }
} }
} }

View file

@ -9,78 +9,40 @@ Singleton {
id: root id: root
// Night Light properties - directly bound to settings // Night Light properties - directly bound to settings
property bool enabled: Settings.data.nightLight?.enabled || false readonly property var params: Settings.data.nightLight
property real warmth: (Settings.data.nightLight
&& Settings.data.nightLight.warmth !== undefined) ? Settings.data.nightLight.warmth : 0.6
property real intensity: (Settings.data.nightLight
&& Settings.data.nightLight.intensity !== undefined) ? Settings.data.nightLight.intensity : 0.8
property string startTime: Settings.data.nightLight?.startTime || "20:00"
property string stopTime: Settings.data.nightLight?.stopTime || "07:00"
property bool autoSchedule: Settings.data.nightLight?.autoSchedule !== false
// Computed properties // Computed properties
property color overlayColor: enabled ? calculateOverlayColor() : "transparent" readonly property color overlayColor: params.enabled ? calculateOverlayColor() : "transparent"
property bool isActive: enabled && (autoSchedule ? isWithinSchedule() : true) property bool isActive: params.enabled && (params.autoSchedule ? isWithinSchedule() : true)
Component.onCompleted: { Component.onCompleted: {
Logger.log("NightLight", "Service started") Logger.log("NightLight", "Service started")
} }
function toggle() {
Settings.data.nightLight.enabled = !Settings.data.nightLight.enabled
Logger.log("NightLight", "Toggled:", Settings.data.nightLight.enabled)
}
function setWarmth(value) {
Settings.data.nightLight.warmth = Math.max(0.0, Math.min(1.0, value))
Logger.log("NightLight", "Warmth set to:", Settings.data.nightLight.warmth)
}
function setIntensity(value) {
Settings.data.nightLight.intensity = Math.max(0.0, Math.min(1.0, value))
Logger.log("NightLight", "Intensity set to:", Settings.data.nightLight.intensity)
}
function setSchedule(start, stop) {
Settings.data.nightLight.startTime = start
Settings.data.nightLight.stopTime = stop
Logger.log("NightLight", "Schedule set to:", Settings.data.nightLight.startTime, "-",
Settings.data.nightLight.stopTime)
}
function setAutoSchedule(auto) {
Settings.data.nightLight.autoSchedule = auto
Logger.log("NightLight", "Auto schedule set to:", Settings.data.nightLight.autoSchedule, "enabled:", enabled,
"isActive:", isActive, "withinSchedule:", isWithinSchedule())
}
function calculateOverlayColor() { function calculateOverlayColor() {
if (!isActive) if (!isActive) {
return "transparent" return "transparent"
}
// More vibrant color formula - stronger effect at high warmth // More vibrant color formula - stronger effect at high warmth
var red = 1.0 var red = 1.0
var green = 0.85 - warmth * 0.4 // More green reduction for stronger effect var green = 1.0 - (0.43 * params.intensity)
var blue = 0.5 - warmth * 0.45 // More blue reduction for warmer feel var blue = 1.0 - (0.84 * params.intensity)
var alpha = 0.1 + warmth * 0.25 // Higher alpha for more noticeable effect var alpha = (params.intensity * 0.25) // Higher alpha for more noticeable effect
// Apply intensity
red = red * intensity
green = green * intensity
blue = blue * intensity
return Qt.rgba(red, green, blue, alpha) return Qt.rgba(red, green, blue, alpha)
} }
function isWithinSchedule() { function isWithinSchedule() {
if (!autoSchedule) if (!params.autoSchedule) {
return true return true
}
var now = new Date() var now = new Date()
var currentTime = now.getHours() * 60 + now.getMinutes() var currentTime = now.getHours() * 60 + now.getMinutes()
var startParts = startTime.split(":") var startParts = params.startTime.split(":")
var stopParts = stopTime.split(":") var stopParts = params.stopTime.split(":")
var startMinutes = parseInt(startParts[0]) * 60 + parseInt(startParts[1]) var startMinutes = parseInt(startParts[0]) * 60 + parseInt(startParts[1])
var stopMinutes = parseInt(stopParts[0]) * 60 + parseInt(stopParts[1]) var stopMinutes = parseInt(stopParts[0]) * 60 + parseInt(stopParts[1])
@ -95,14 +57,10 @@ Singleton {
// Timer to check schedule changes // Timer to check schedule changes
Timer { Timer {
interval: 60000 // Check every minute interval: 60000 // Check every minute
running: true running: params.enabled && params.autoSchedule
repeat: true repeat: true
onTriggered: { onTriggered: {
if (autoSchedule && enabled) { isActive = isWithinSchedule()
// Force overlay update when schedule changes
Logger.log("NightLight", "Schedule check - enabled:", enabled, "autoSchedule:", autoSchedule, "isActive:",
isActive, "withinSchedule:", isWithinSchedule())
}
} }
} }
} }

View file

@ -40,7 +40,6 @@ ShellRoot {
ScreenCorners {} ScreenCorners {}
Bar {} Bar {}
Dock {} Dock {}
NightLight {}
Notification { Notification {
id: notification id: notification
@ -52,6 +51,8 @@ ShellRoot {
ToastOverlay {} ToastOverlay {}
NightLightOverlay {}
IPCManager {} IPCManager {}
// ------------------------------ // ------------------------------