Merge branch 'main' into hefty-hype

This commit is contained in:
end-4
2026-03-27 23:20:08 +01:00
31 changed files with 1279 additions and 352 deletions
@@ -12,11 +12,11 @@ QuickToggleModel {
name: Translation.tr("Night Light")
statusText: (auto ? Translation.tr("Auto, ") : "") + (toggled ? Translation.tr("Active") : Translation.tr("Inactive"))
toggled: Hyprsunset.active
toggled: Hyprsunset.temperatureActive
icon: auto ? "night_sight_auto" : "bedtime"
mainAction: () => {
Hyprsunset.toggle()
Hyprsunset.toggleTemperature()
}
hasMenu: true
@@ -17,6 +17,7 @@ Slider {
id: root
property list<real> stopIndicatorValues: [1]
property list<real> dividerValues: []
enum Configuration {
Wavy = 4,
XS = 12,
@@ -45,6 +46,7 @@ Slider {
property real handleHeight: (configuration === StyledSlider.Configuration.Wavy) ? 24 : Math.max(33, trackWidth + 9)
property real handleWidth: root.pressed ? handlePressedWidth : handleDefaultWidth
property real handleMargins: 4
property real dividerMargins: 2
property real trackDotSize: 3
property bool usePercentTooltip: true
property string tooltipContent: usePercentTooltip ? `${Math.round(((value - from) / (to - from)) * 100)}%` : `${Math.round(value)}`
@@ -94,71 +96,94 @@ Slider {
}
background: Item {
id: background
anchors.verticalCenter: parent.verticalCenter
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
width: root.width
implicitHeight: trackWidth
property var normalized: root.dividerValues.map(v => (v - root.from) / (root.to - root.from))
property var filtered: normalized.filter(v => Math.abs(v - root.visualPosition) * effectiveDraggingWidth > handleMargins + handleWidth / 2 - dividerMargins)
property var leftValues: [0, ...filtered.filter(v => v < root.visualPosition), root.visualPosition]
property var rightValues: [root.visualPosition, ...filtered.filter(v => v > root.visualPosition), 1]
property var leftWidths: leftValues.map((v, i, a) => a[i + 1] - v).slice(0, -1)
property var rightWidths: rightValues.map((v, i, a) => a[i + 1] - v).slice(0, -1)
// Fill left
Loader {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
}
width: root.handleMargins + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2 + root.handleMargins)
height: root.trackWidth
active: !root.wavy
sourceComponent: Rectangle {
color: root.highlightColor
topLeftRadius: root.trackRadius
bottomLeftRadius: root.trackRadius
topRightRadius: root.unsharpenRadius
bottomRightRadius: root.unsharpenRadius
Repeater {
model: background.leftWidths.length
Loader {
required property real index
anchors.verticalCenter: background.verticalCenter
property real leftMargin: index > 0 ? root.dividerMargins : 0
property real rightMargin: index < background.leftWidths.length - 1 ? root.dividerMargins : root.handleMargins
x: background.leftValues[index] * root.effectiveDraggingWidth + leftMargin + (index > 0 ? leftPadding : 0)
width: background.leftWidths[index] * root.effectiveDraggingWidth - leftMargin - rightMargin - (index === background.leftWidths.length - 1 ? handleWidth / 2 : 0) + (index === 0 ? leftPadding : 0)
height: root.trackWidth
active: !root.wavy
sourceComponent: Rectangle {
color: root.highlightColor
topLeftRadius: index === 0 ? root.trackRadius : root.unsharpenRadius
bottomLeftRadius: index === 0 ? root.trackRadius : root.unsharpenRadius
topRightRadius: root.unsharpenRadius
bottomRightRadius: root.unsharpenRadius
}
}
}
Loader {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
}
width: root.handleMargins + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2 + root.handleMargins)
height: root.height
active: root.wavy
sourceComponent: WavyLine {
id: wavyFill
frequency: root.waveFrequency
fullLength: root.width
color: root.highlightColor
amplitudeMultiplier: root.wavy ? 0.5 : 0
width: root.handleMargins + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2 + root.handleMargins)
height: root.trackWidth
Connections {
target: root
function onValueChanged() { wavyFill.requestPaint(); }
function onHighlightColorChanged() { wavyFill.requestPaint(); }
}
FrameAnimation {
running: root.animateWave
onTriggered: {
wavyFill.requestPaint()
Repeater {
model: background.leftWidths.length
Loader {
required property int index
anchors.verticalCenter: background.verticalCenter
property real leftMargin: index > 0 ? root.dividerMargins : 0
property real rightMargin: index < background.leftWidths.length - 1 ? root.dividerMargins : root.handleMargins
x: background.leftValues[index] * root.effectiveDraggingWidth + leftMargin + (index > 0 ? leftPadding : 0)
width: background.leftWidths[index] * root.effectiveDraggingWidth - leftMargin - rightMargin - (index === background.leftWidths.length - 1 ? handleWidth / 2 : 0) + (index === 0 ? leftPadding : 0)
height: root.height
active: root.wavy
sourceComponent: WavyLine {
id: wavyFill
frequency: root.waveFrequency
fullLength: root.width
color: root.highlightColor
amplitudeMultiplier: root.wavy ? 0.5 : 0
width: parent.width
height: root.trackWidth
Connections {
target: root
function onValueChanged() { wavyFill.requestPaint(); }
function onHighlightColorChanged() { wavyFill.requestPaint(); }
}
FrameAnimation {
running: root.animateWave
onTriggered: {
wavyFill.requestPaint()
}
}
}
}
}
}
// Fill right
Rectangle {
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
Repeater {
model: background.rightWidths.length
Rectangle {
required property int index
anchors.verticalCenter: background.verticalCenter
property real leftMargin: index > 0 ? root.dividerMargins : root.handleMargins
property real rightMargin: index < background.rightWidths.length - 1 ? root.dividerMargins : 0
x: background.rightValues[index] * root.effectiveDraggingWidth + leftMargin + (index === 0 ? handleWidth / 2 : 0) + leftPadding
width: background.rightWidths[index] * root.effectiveDraggingWidth - leftMargin - rightMargin - (index === 0 ? handleWidth / 2 : 0) + (index === background.rightWidths.length - 1 ? rightPadding : 0)
height: trackWidth
color: root.trackColor
topRightRadius: index === background.rightWidths.length - 1 ? root.trackRadius : root.unsharpenRadius
bottomRightRadius: index === background.rightWidths.length - 1 ? root.trackRadius : root.unsharpenRadius
topLeftRadius: root.unsharpenRadius
bottomLeftRadius: root.unsharpenRadius
}
width: root.handleMargins + ((1 - root.visualPosition) * root.effectiveDraggingWidth) - (root.handleWidth / 2 + root.handleMargins)
height: trackWidth
color: root.trackColor
topRightRadius: root.trackRadius
bottomRightRadius: root.trackRadius
topLeftRadius: root.unsharpenRadius
bottomLeftRadius: root.unsharpenRadius
}
// Stop indicators
@@ -177,7 +202,7 @@ Slider {
implicitWidth: root.handleWidth
implicitHeight: root.handleHeight
x: root.handleMargins + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2)
x: root.leftPadding + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2)
anchors.verticalCenter: parent.verticalCenter
radius: Appearance.rounding.full
color: root.handleColor
@@ -59,8 +59,8 @@ Item { // Bar content region
implicitWidth: leftSectionRowLayout.implicitWidth
implicitHeight: Appearance.sizes.baseBarHeight
onScrollDown: root.brightnessMonitor.setBrightness(root.brightnessMonitor.brightness - 0.05)
onScrollUp: root.brightnessMonitor.setBrightness(root.brightnessMonitor.brightness + 0.05)
onScrollDown: Brightness.decreaseBrightness()
onScrollUp: Brightness.increaseBrightness()
onMovedAway: GlobalStates.osdBrightnessOpen = false
onPressed: event => {
if (event.button === Qt.LeftButton)
@@ -70,7 +70,7 @@ Item { // Bar content region
// Visual content
ScrollHint {
reveal: barLeftSideMouseArea.hovered
icon: "light_mode"
icon: Hyprsunset.gamma === 100 ? "light_mode" : "wb_twilight"
tooltipText: Translation.tr("Scroll to change brightness")
side: "left"
anchors.left: parent.left
@@ -25,6 +25,10 @@ Scope {
id: "brightness",
sourceUrl: "indicators/BrightnessIndicator.qml"
},
{
id: "gamma",
sourceUrl: "indicators/GammaIndicator.qml"
},
]
function triggerOsd() {
@@ -52,6 +56,15 @@ Scope {
}
}
Connections {
target: Hyprsunset
function onGammaChangeAttempt() {
root.protectionMessage = "";
root.currentIndicator = "gamma";
root.triggerOsd();
}
}
Connections {
// Listen to volume changes
target: Audio.sink?.audio ?? null
@@ -11,6 +11,8 @@ Item {
required property string name
property bool rotateIcon: false
property bool scaleIcon: false
property alias from: valueProgressBar.from
property alias to: valueProgressBar.to
property real valueIndicatorVerticalPadding: 9
property real valueIndicatorLeftPadding: 10
@@ -9,7 +9,7 @@ OsdValueIndicator {
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen)
icon: Hyprsunset.active ? "routine" : "light_mode"
icon: Hyprsunset.temperatureActive ? "routine" : "light_mode"
rotateIcon: true
scaleIcon: true
name: Translation.tr("Brightness")
@@ -0,0 +1,14 @@
import qs.services
import QtQuick
import Quickshell
import Quickshell.Hyprland
import qs.modules.ii.onScreenDisplay
OsdValueIndicator {
id: rotateIcon
icon: "wb_twilight"
name: Translation.tr("Gamma")
from: Hyprsunset.gammaLowerLimit / 100
value: Hyprsunset.gamma / 100 ?? 0.5
}
@@ -240,7 +240,7 @@ Item {
}
}
z: Drag.active ? root.windowDraggingZ : (root.windowZ + windowData?.floating)
z: Drag.active ? root.windowDraggingZ : (root.windowZ + windowData?.floating + windowData?.fullscreen * 2)
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
MouseArea {
@@ -98,7 +98,7 @@ Scope {
if (!Config.options.sidebar.cornerOpen.valueScroll)
return;
if (cornerWidget.isLeft)
cornerPanelWindow.brightnessMonitor.setBrightness(cornerPanelWindow.brightnessMonitor.brightness - 0.05);
Brightness.decreaseBrightness()
else {
const currentVolume = Audio.value;
const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2;
@@ -109,7 +109,7 @@ Scope {
if (!Config.options.sidebar.cornerOpen.valueScroll)
return;
if (cornerWidget.isLeft)
cornerPanelWindow.brightnessMonitor.setBrightness(cornerPanelWindow.brightnessMonitor.brightness + 0.05);
Brightness.increaseBrightness()
else {
const currentVolume = Audio.value;
const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2;
@@ -497,199 +497,206 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
RowLayout { // Input field and send button
id: inputFieldRowLayout
anchors {
top: attachedFileIndicator.bottom
bottom: commandButtonsRow.top
left: parent.left
right: parent.right
topMargin: 5
bottomMargin: 5
}
spacing: 0
StyledTextArea { // The actual TextArea
id: messageInputField
wrapMode: TextArea.Wrap
ScrollView {
id: inputScrollView
Layout.fillWidth: true
padding: 10
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
placeholderText: Translation.tr('Message the model... "%1" for commands').arg(root.commandPrefix)
Layout.preferredHeight: Math.min(root.height * 3/5, messageInputField.height)
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
background: null
StyledTextArea { // The actual TextArea (inside ScrollView to enable scrolling)
id: messageInputField
anchors.fill: parent
wrapMode: TextArea.Wrap
padding: 10
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
placeholderText: Translation.tr('Message the model... "%1" for commands').arg(root.commandPrefix)
onTextChanged: {
// Handle suggestions
if (messageInputField.text.length === 0) {
root.suggestionQuery = "";
root.suggestionList = [];
return;
} else if (messageInputField.text.startsWith(`${root.commandPrefix}model`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const modelResults = Fuzzy.go(root.suggestionQuery, Ai.modelList.map(model => {
return {
name: Fuzzy.prepare(model),
obj: model
};
}), {
all: true,
key: "name"
});
root.suggestionList = modelResults.map(model => {
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "model ") : ""}${model.target}`,
displayName: `${Ai.models[model.target].name}`,
description: `${Ai.models[model.target].description}`
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}prompt`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.promptFiles.map(file => {
return {
name: Fuzzy.prepare(file),
obj: file
};
}), {
all: true,
key: "name"
});
root.suggestionList = promptFileResults.map(file => {
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "prompt ") : ""}${file.target}`,
displayName: `${FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target))}`,
description: Translation.tr("Load prompt from %1").arg(file.target)
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}save`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => {
return {
name: Fuzzy.prepare(file),
obj: file
};
}), {
all: true,
key: "name"
});
root.suggestionList = promptFileResults.map(file => {
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim();
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "save ") : ""}${chatName}`,
displayName: `${chatName}`,
description: Translation.tr("Save chat to %1").arg(chatName)
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}load`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => {
return {
name: Fuzzy.prepare(file),
obj: file
};
}), {
all: true,
key: "name"
});
root.suggestionList = promptFileResults.map(file => {
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim();
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "load ") : ""}${chatName}`,
displayName: `${chatName}`,
description: Translation.tr(`Load chat from %1`).arg(file.target)
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}tool`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const toolResults = Fuzzy.go(root.suggestionQuery, Ai.availableTools.map(tool => {
return {
name: Fuzzy.prepare(tool),
obj: tool
};
}), {
all: true,
key: "name"
});
root.suggestionList = toolResults.map(tool => {
const toolName = tool.target;
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "tool ") : ""}${tool.target}`,
displayName: toolName,
description: Ai.toolDescriptions[toolName]
};
});
} else if (messageInputField.text.startsWith(root.commandPrefix)) {
root.suggestionQuery = messageInputField.text;
root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(messageInputField.text.substring(1))).map(cmd => {
return {
name: `${root.commandPrefix}${cmd.name}`,
description: `${cmd.description}`
};
});
background: null
onTextChanged: {
// Handle suggestions
if (messageInputField.text.length === 0) {
root.suggestionQuery = "";
root.suggestionList = [];
return;
} else if (messageInputField.text.startsWith(`${root.commandPrefix}model`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const modelResults = Fuzzy.go(root.suggestionQuery, Ai.modelList.map(model => {
return {
name: Fuzzy.prepare(model),
obj: model
};
}), {
all: true,
key: "name"
});
root.suggestionList = modelResults.map(model => {
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "model ") : ""}${model.target}`,
displayName: `${Ai.models[model.target].name}`,
description: `${Ai.models[model.target].description}`
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}prompt`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.promptFiles.map(file => {
return {
name: Fuzzy.prepare(file),
obj: file
};
}), {
all: true,
key: "name"
});
root.suggestionList = promptFileResults.map(file => {
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "prompt ") : ""}${file.target}`,
displayName: `${FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target))}`,
description: Translation.tr("Load prompt from %1").arg(file.target)
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}save`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => {
return {
name: Fuzzy.prepare(file),
obj: file
};
}), {
all: true,
key: "name"
});
root.suggestionList = promptFileResults.map(file => {
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim();
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "save ") : ""}${chatName}`,
displayName: `${chatName}`,
description: Translation.tr("Save chat to %1").arg(chatName)
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}load`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => {
return {
name: Fuzzy.prepare(file),
obj: file
};
}), {
all: true,
key: "name"
});
root.suggestionList = promptFileResults.map(file => {
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim();
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "load ") : ""}${chatName}`,
displayName: `${chatName}`,
description: Translation.tr(`Load chat from %1`).arg(file.target)
};
});
} else if (messageInputField.text.startsWith(`${root.commandPrefix}tool`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
const toolResults = Fuzzy.go(root.suggestionQuery, Ai.availableTools.map(tool => {
return {
name: Fuzzy.prepare(tool),
obj: tool
};
}), {
all: true,
key: "name"
});
root.suggestionList = toolResults.map(tool => {
const toolName = tool.target;
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "tool ") : ""}${tool.target}`,
displayName: toolName,
description: Ai.toolDescriptions[toolName]
};
});
} else if (messageInputField.text.startsWith(root.commandPrefix)) {
root.suggestionQuery = messageInputField.text;
root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(messageInputField.text.substring(1))).map(cmd => {
return {
name: `${root.commandPrefix}${cmd.name}`,
description: `${cmd.description}`
};
});
}
}
}
function accept() {
root.handleInput(text);
text = "";
}
function accept() {
root.handleInput(text);
text = "";
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Tab) {
suggestions.acceptSelectedWord();
event.accepted = true;
} else if (event.key === Qt.Key_Up && suggestions.visible) {
suggestions.selectedIndex = Math.max(0, suggestions.selectedIndex - 1);
event.accepted = true;
} else if (event.key === Qt.Key_Down && suggestions.visible) {
suggestions.selectedIndex = Math.min(root.suggestionList.length - 1, suggestions.selectedIndex + 1);
event.accepted = true;
} else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {
if (event.modifiers & Qt.ShiftModifier) {
// Insert newline
messageInputField.insert(messageInputField.cursorPosition, "\n");
Keys.onPressed: event => {
if (event.key === Qt.Key_Tab) {
suggestions.acceptSelectedWord();
event.accepted = true;
} else {
// Accept text
const inputText = messageInputField.text;
messageInputField.clear();
root.handleInput(inputText);
} else if (event.key === Qt.Key_Up && suggestions.visible) {
suggestions.selectedIndex = Math.max(0, suggestions.selectedIndex - 1);
event.accepted = true;
}
} else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) {
// Intercept Ctrl+V to handle image/file pasting
if (event.modifiers & Qt.ShiftModifier) {
// Let Shift+Ctrl+V = plain paste
messageInputField.text += Quickshell.clipboardText;
} else if (event.key === Qt.Key_Down && suggestions.visible) {
suggestions.selectedIndex = Math.min(root.suggestionList.length - 1, suggestions.selectedIndex + 1);
event.accepted = true;
return;
}
// Try image paste first
const currentClipboardEntry = Cliphist.entries[0];
const cleanCliphistEntry = StringUtils.cleanCliphistEntry(currentClipboardEntry);
if (/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(currentClipboardEntry)) {
// First entry = currently copied entry = image?
decodeImageAndAttachProc.handleEntry(currentClipboardEntry);
event.accepted = true;
return;
} else if (cleanCliphistEntry.startsWith("file://")) {
// First entry = currently copied entry = image?
const fileName = decodeURIComponent(cleanCliphistEntry);
Ai.attachFile(fileName);
event.accepted = true;
return;
}
event.accepted = false; // No image, let text pasting proceed
} else if (event.key === Qt.Key_Escape) {
// Esc to detach file
if (Ai.pendingFilePath.length > 0) {
Ai.attachFile("");
event.accepted = true;
} else {
event.accepted = false;
} else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {
if (event.modifiers & Qt.ShiftModifier) {
// Insert newline
messageInputField.insert(messageInputField.cursorPosition, "\n");
event.accepted = true;
} else {
// Accept text
const inputText = messageInputField.text;
messageInputField.clear();
root.handleInput(inputText);
event.accepted = true;
}
} else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) {
// Intercept Ctrl+V to handle image/file pasting
if (event.modifiers & Qt.ShiftModifier) {
// Let Shift+Ctrl+V = plain paste
messageInputField.text += Quickshell.clipboardText;
event.accepted = true;
return;
}
// Try image paste first
const currentClipboardEntry = Cliphist.entries[0];
const cleanCliphistEntry = StringUtils.cleanCliphistEntry(currentClipboardEntry);
if (/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(currentClipboardEntry)) {
// First entry = currently copied entry = image?
decodeImageAndAttachProc.handleEntry(currentClipboardEntry);
event.accepted = true;
return;
} else if (cleanCliphistEntry.startsWith("file://")) {
// First entry = currently copied entry = image?
const fileName = decodeURIComponent(cleanCliphistEntry);
Ai.attachFile(fileName);
event.accepted = true;
return;
}
event.accepted = false; // No image, let text pasting proceed
} else if (event.key === Qt.Key_Escape) {
// Esc to detach file
if (Ai.pendingFilePath.length > 0) {
Ai.attachFile("");
event.accepted = true;
} else {
event.accepted = false;
}
}
}
}
}
RippleButton { // Send button
id: sendButton
Layout.alignment: Qt.AlignTop
Layout.alignment: Qt.AlignBottom
Layout.rightMargin: 5
implicitWidth: 40
implicitHeight: 40
@@ -40,10 +40,25 @@ Rectangle {
visible: active
active: Config.options.sidebar.quickSliders.showBrightness
sourceComponent: QuickSlider {
materialSymbol: "brightness_6"
value: root.brightnessMonitor.brightness
materialSymbol: "light_mode"
secondaryMaterialSymbol: "wb_twilight"
stopIndicatorValues: Hyprsunset.gamma !== 100 && root.brightnessMonitor?.brightness !== 0 ? [0.3 + root.brightnessMonitor?.brightness * 0.7] : []
value: Hyprsunset.gamma === 100? 0.3 + root.brightnessMonitor?.brightness * 0.7 : (Hyprsunset.gamma - Hyprsunset.gammaLowerLimit) / (100 - Hyprsunset.gammaLowerLimit) * 0.3
tooltipContent: Hyprsunset.gamma === 100 ? `${Math.round(root.brightnessMonitor?.brightness * 100)}%` : `${Translation.tr("Gamma")} ${Hyprsunset.gamma}%`
onMoved: {
root.brightnessMonitor.setBrightness(value)
if (value >= 0.3) {
// 0.3 - 1.0 brightness
root.brightnessMonitor.setBrightness((value - 0.3) / 0.7);
if (Hyprsunset.gamma !== 100) {
Hyprsunset.setGamma(100);
}
} else {
// 0 - 0.3 gamma
if (root.brightnessMonitor.brightness !== 0) {
root.brightnessMonitor.setBrightness(0);
}
Hyprsunset.setGamma((value / 0.3 * (100 - Hyprsunset.gammaLowerLimit) + Hyprsunset.gammaLowerLimit));
}
}
}
}
@@ -84,16 +99,18 @@ Rectangle {
component QuickSlider: StyledSlider {
id: quickSlider
required property string materialSymbol
property string secondaryMaterialSymbol
configuration: StyledSlider.Configuration.M
stopIndicatorValues: []
dividerValues: secondaryMaterialSymbol.length > 0 ? [secondaryIcon.iconLocation] : []
MaterialSymbol {
id: icon
property bool nearFull: quickSlider.value >= 0.9
anchors {
verticalCenter: parent.verticalCenter
right: nearFull ? quickSlider.handle.right : parent.right
rightMargin: quickSlider.nearFull ? 14 : 8
verticalCenter: quickSlider.verticalCenter
right: nearFull ? quickSlider.handle.right : quickSlider.right
rightMargin: nearFull ? 14 : 8
}
iconSize: 20
color: nearFull ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer
@@ -105,7 +122,25 @@ Rectangle {
Behavior on anchors.rightMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
MaterialSymbol {
id: secondaryIcon
visible: secondaryMaterialSymbol.length > 0
property real iconLocation: 0.3
property bool nearIcon: iconLocation - quickSlider.value <= 0.1 && iconLocation - quickSlider.value > (quickSlider.handleWidth + 8 - 14) / quickSlider.effectiveDraggingWidth
anchors {
verticalCenter: quickSlider.verticalCenter
right: nearIcon ? quickSlider.handle.right : quickSlider.right
rightMargin: nearIcon ? 14 : (1 - iconLocation) * quickSlider.effectiveDraggingWidth + quickSlider.rightPadding + 8
}
iconSize: 20
color: quickSlider.value >= iconLocation - 0.1 ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer
text: secondaryMaterialSymbol
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
}
@@ -1,4 +1,5 @@
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.services
import QtQuick
@@ -82,6 +83,22 @@ DialogListItem {
Item {
Layout.fillWidth: true
}
ActionButton {
readonly property bool p: root.device?.paired ?? false
colBackground: p ? Appearance.colors.colError : ColorUtils.transparentize(Appearance.colors.colLayer3, 1)
colBackgroundHover: p ? Appearance.colors.colErrorHover : ColorUtils.transparentize(Appearance.colors.colLayer3, 1)
colRipple: p ? Appearance.colors.colErrorActive : Appearance.colors.colLayer3Hover
colText: p ? Appearance.colors.colOnError : Appearance.colors.colPrimary
buttonText: p ? Translation.tr("Forget") : Translation.tr("Always connect")
onClicked: {
if (root.device?.paired) {
root.device?.forget();
} else {
root.device?.pair();
}
}
}
ActionButton {
buttonText: root.device?.connected ? Translation.tr("Disconnect") : Translation.tr("Connect")
@@ -93,18 +110,6 @@ DialogListItem {
}
}
}
ActionButton {
visible: root.device?.paired ?? false
colBackground: Appearance.colors.colError
colBackgroundHover: Appearance.colors.colErrorHover
colRipple: Appearance.colors.colErrorActive
colText: Appearance.colors.colOnError
buttonText: Translation.tr("Forget")
onClicked: {
root.device?.forget();
}
}
}
Item {
Layout.fillHeight: true
@@ -15,7 +15,7 @@ WindowDialog {
id: root
property var screen: root.QsWindow.window?.screen
property var brightnessMonitor: Brightness.getMonitorForScreen(screen)
backgroundHeight: 600
backgroundHeight: 700
WindowDialogTitle {
text: Translation.tr("Eye protection")
@@ -44,9 +44,9 @@ WindowDialog {
iconSize: Appearance.font.pixelSize.larger
buttonIcon: "check"
text: Translation.tr("Enable now")
checked: Hyprsunset.active
checked: Hyprsunset.temperatureActive
onCheckedChanged: {
Hyprsunset.toggle(checked)
Hyprsunset.toggleTemperature(checked)
}
}
@@ -146,6 +146,33 @@ WindowDialog {
id: brightnessColumn
Layout.topMargin: -16
Layout.fillWidth: true
WindowDialogSlider {
anchors {
left: parent.left
right: parent.right
leftMargin: 4
rightMargin: 4
}
value: root.brightnessMonitor.brightness
onMoved: root.brightnessMonitor.setBrightness(value)
}
}
WindowDialogSectionHeader {
text: Translation.tr("Gamma")
}
WindowDialogSeparator {
Layout.topMargin: -22
Layout.leftMargin: 0
Layout.rightMargin: 0
}
Column {
id: gammaColumn
Layout.topMargin: -16
Layout.fillWidth: true
Layout.fillHeight: true
WindowDialogSlider {
@@ -155,9 +182,10 @@ WindowDialog {
leftMargin: 4
rightMargin: 4
}
// text: Translation.tr("Brightness")
value: root.brightnessMonitor.brightness
onMoved: root.brightnessMonitor.setBrightness(value)
from: Hyprsunset.gammaLowerLimit / 100
value: Hyprsunset.gamma / 100
onMoved: Hyprsunset.setGamma(value * 100)
tooltipContent: `${Math.round(value * 100)}%`
}
}
@@ -6,10 +6,10 @@ import Quickshell.Io
QuickToggleButton {
id: nightLightButton
toggled: Hyprsunset.active
toggled: Hyprsunset.temperatureActive
buttonIcon: Config.options.light.night.automatic ? "night_sight_auto" : "bedtime"
onClicked: {
Hyprsunset.toggle()
Hyprsunset.toggleTemperature()
}
altAction: () => {
@@ -54,8 +54,8 @@ Item { // Bar content region
height: (root.height - middleSection.height) / 2
width: Appearance.sizes.verticalBarWidth
onScrollDown: root.brightnessMonitor.setBrightness(root.brightnessMonitor.brightness - 0.05)
onScrollUp: root.brightnessMonitor.setBrightness(root.brightnessMonitor.brightness + 0.05)
onScrollDown: Brightness.decreaseBrightness()
onScrollUp: Brightness.increaseBrightness()
onMovedAway: GlobalStates.osdBrightnessOpen = false
onPressed: event => {
if (event.button === Qt.LeftButton)
@@ -303,7 +303,7 @@ ContentPage {
Config.options.background.widgets.clock.cookie.useSineCookie = checked;
}
StyledToolTip {
text: "Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing"
text: Translation.tr("Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing")
}
}
@@ -327,7 +327,7 @@ ContentPage {
Config.options.background.widgets.clock.cookie.constantlyRotate = checked;
}
StyledToolTip {
text: "Makes the clock always rotate. This is extremely expensive\n(expect 50% usage on Intel UHD Graphics) and thus impractical."
text: Translation.tr("Makes the clock always rotate. This is extremely expensive\n(expect 50% usage on Intel UHD Graphics) and thus impractical.")
}
}
@@ -345,7 +345,7 @@ ContentPage {
Config.options.background.widgets.clock.cookie.hourMarks = checked;
}
StyledToolTip {
text: "Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons"
text: Translation.tr("Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons")
}
}
@@ -361,7 +361,7 @@ ContentPage {
Config.options.background.widgets.clock.cookie.timeIndicators = checked;
}
StyledToolTip {
text: "Can't be turned on when using 'Numbers' dial style for aesthetic reasons"
text: Translation.tr("Can't be turned on when using 'Numbers' dial style for aesthetic reasons")
}
}
}
@@ -87,9 +87,9 @@ Item {
name: Translation.tr("Enable now")
description: Translation.tr("More comfortable viewing at night")
iconName: WIcons.nightLightIcon
checked: Hyprsunset.active
checked: Hyprsunset.temperatureActive
onCheckedChanged: {
Hyprsunset.toggle(checked);
Hyprsunset.toggleTemperature(checked);
}
}
@@ -71,7 +71,7 @@ Singleton {
property string bluetoothIcon: BluetoothStatus.connected ? "bluetooth-connected" : BluetoothStatus.enabled ? "bluetooth" : "bluetooth-disabled"
property string nightLightIcon: Hyprsunset.active ? "weather-moon" : "weather-moon-off"
property string nightLightIcon: Hyprsunset.temperatureActive ? "weather-moon" : "weather-moon-off"
property string notificationsIcon: Notifications.silent ? "alert-snooze" : "alert"