Merge branch 'end-4:main' into main

This commit is contained in:
jwihardi
2025-10-12 17:16:03 -04:00
committed by GitHub
42 changed files with 450 additions and 270 deletions
+3
View File
@@ -74,6 +74,9 @@ windowrulev2 = immediate, title:.*\.exe
windowrulev2 = immediate, title:.*minecraft.*
windowrulev2 = immediate, class:^(steam_app).*
# Fix Jetbrain IDEs focus/rerendering problem
windowrulev2=noinitialfocus,class:^jetbrains-.*$,floating:1,title:^$|^\s$|^win\d+$
# No shadow for tiled windows (matches windows that are not floating).
windowrulev2 = noshadow, floating:0
+9 -1
View File
@@ -1,5 +1,6 @@
import qs.modules.common
import qs
import qs.modules.common
import qs.services
import QtQuick
import Quickshell
import Quickshell.Hyprland
@@ -44,6 +45,13 @@ Singleton {
}
}
onSidebarRightOpenChanged: {
if (GlobalStates.sidebarRightOpen) {
Notifications.timeoutAll();
Notifications.markAllRead();
}
}
property real screenZoom: 1
onScreenZoomChanged: {
Quickshell.execDetached(["hyprctl", "keyword", "cursor:zoom_factor", root.screenZoom.toString()]);
+1 -1
View File
@@ -58,7 +58,7 @@ Scope {
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: {
onPressed: {
popupLoader.active = false
}
@@ -19,7 +19,10 @@ Item {
Behavior on rotation {
enabled: Config.options.background.clock.cookie.constantlyRotate // Animating every second is expensive...
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
animation: NumberAnimation {
duration: 1000 // 1 second
easing.type: Easing.InOutQuad
}
}
Rectangle {
@@ -295,6 +295,48 @@ Item { // Bar content region
Layout.rightMargin: indicatorsRowLayout.realSpacing
color: rightSidebarButton.colText
}
Revealer {
reveal: Notifications.silent || Notifications.unread > 0
Layout.fillHeight: true
Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0
implicitHeight: reveal ? notificationIcon.implicitHeight : 0
implicitWidth: reveal ? notificationIcon.implicitWidth : 0
Behavior on Layout.rightMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Rectangle {
id: notifPing
visible: !Notifications.silent && Notifications.unread > 0
property bool showUnreadCount: Config.options.bar.indicators.notifications.showUnreadCount
anchors {
right: parent.right
top: parent.top
rightMargin: showUnreadCount ? 0 : 1
topMargin: showUnreadCount ? 0 : 3
}
radius: Appearance.rounding.full
color: Appearance.colors.colOnLayer0
z: 1
implicitHeight: showUnreadCount ? Math.max(notificationCounterText.implicitWidth, notificationCounterText.implicitHeight) : 8
implicitWidth: implicitHeight
StyledText {
id: notificationCounterText
visible: notifPing.showUnreadCount
anchors.centerIn: parent
font.pixelSize: Appearance.font.pixelSize.smallest
color: Appearance.colors.colLayer0
text: Notifications.unread
}
}
MaterialSymbol {
id: notificationIcon
text: Notifications.silent ? "notifications_paused" : "notifications"
iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText
}
}
MaterialSymbol {
Layout.rightMargin: indicatorsRowLayout.realSpacing
text: Network.materialSymbol
@@ -302,6 +344,7 @@ Item { // Bar content region
color: rightSidebarButton.colText
}
MaterialSymbol {
visible: BluetoothStatus.available
text: BluetoothStatus.connected ? "bluetooth_connected" : BluetoothStatus.enabled ? "bluetooth" : "bluetooth_disabled"
iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText
@@ -75,7 +75,7 @@ Item {
toggled: root.trayOverflowOpen
property bool containsMouse: hovered
onClicked: root.trayOverflowOpen = !root.trayOverflowOpen
downAction: () => root.trayOverflowOpen = !root.trayOverflowOpen
Layout.fillHeight: !root.vertical
Layout.fillWidth: root.vertical
@@ -48,7 +48,7 @@ PopupWindow {
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.BackButton | Qt.RightButton
onClicked: event => {
onPressed: event => {
if ((event.button === Qt.BackButton || event.button === Qt.RightButton) && stackView.depth > 1)
stackView.pop();
}
@@ -152,7 +152,7 @@ PopupWindow {
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
implicitHeight: 36
onClicked: stackView.pop()
downAction: () => stackView.pop()
contentItem: RowLayout {
anchors {
@@ -15,7 +15,7 @@ MouseArea {
hoverEnabled: true
onClicked: {
onPressed: {
Weather.getData();
Quickshell.execDetached(["notify-send",
Translation.tr("Weather"),
@@ -18,7 +18,9 @@ Singleton {
// Transparency. The quadratic functions were derived from analysis of hand-picked transparency values.
ColorQuantizer {
id: wallColorQuant
source: Qt.resolvedUrl(Config.options.background.wallpaperPath)
property string wallpaperPath: Config.options.background.wallpaperPath
property bool wallpaperIsVideo: wallpaperPath.endsWith(".mp4") || wallpaperPath.endsWith(".webm") || wallpaperPath.endsWith(".mkv") || wallpaperPath.endsWith(".avi") || wallpaperPath.endsWith(".mov")
source: Qt.resolvedUrl(wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath)
depth: 0 // 2^0 = 1 color
rescaleSize: 10
}
@@ -303,7 +305,7 @@ Singleton {
}
property QtObject elementResize: QtObject {
property int duration: 400
property int duration: 300
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.emphasized
property int velocity: 650
@@ -137,7 +137,7 @@ Singleton {
property string secondHandStyle: "dot" // Options: "dot", "line" , "hide"
property string dateStyle: "bubble" // Options: "border", "rect", "bubble" , "hide"
property bool timeIndicators: true
property bool hourMarks: true
property bool hourMarks: false
property bool dateInClock: true
property bool constantlyRotate: false
}
@@ -212,6 +212,11 @@ Singleton {
property bool useUSCS: false // Instead of metric (SI) units
property int fetchInterval: 10 // minutes
}
property JsonObject indicators: JsonObject {
property JsonObject notifications: JsonObject {
property bool showUnreadCount: false
}
}
}
property JsonObject battery: JsonObject {
@@ -341,8 +346,12 @@ Singleton {
property JsonObject sidebar: JsonObject {
property bool keepRightSidebarLoaded: true
property JsonObject translator: JsonObject {
property bool enable: false
property int delay: 300 // Delay before sending request. Reduces (potential) rate limits and lag.
}
property JsonObject ai: JsonObject {
property bool textFadeIn: true
}
property JsonObject booru: JsonObject {
property bool allowNsfw: false
property string defaultProvider: "yandere"
@@ -35,7 +35,7 @@ Rectangle {
RippleButton {
id: parentDirButton
onClicked: root.navigateToDirectory(FileUtils.parentDirectory(root.directory))
downAction: () => root.navigateToDirectory(FileUtils.parentDirectory(root.directory))
contentItem: MaterialSymbol {
text: "drive_folder_upload"
iconSize: Appearance.font.pixelSize.larger
@@ -105,7 +105,7 @@ Rectangle {
RippleButton {
id: dirEditButton
toggled: !root.showBreadcrumb
onClicked: root.showBreadcrumb = !root.showBreadcrumb
downAction: () => root.showBreadcrumb = !root.showBreadcrumb
contentItem: MaterialSymbol {
text: "edit"
iconSize: Appearance.font.pixelSize.larger
@@ -23,6 +23,7 @@ ColumnLayout {
text: root.title
font.pixelSize: Appearance.font.pixelSize.larger
font.weight: Font.Medium
color: Appearance.colors.colOnSecondaryContainer
}
}
@@ -0,0 +1,25 @@
import QtQuick
import qs.modules.common
import qs.modules.common.widgets
MaterialCookie {
id: root
property alias text: symbol.text
property alias iconSize: symbol.iconSize
property alias font: symbol.font
property alias colSymbol: symbol.color
property real padding: 6
color: Appearance.colors.colSecondaryContainer
colSymbol: Appearance.colors.colOnSecondaryContainer
sides: 5
implicitSize: Math.max(symbol.implicitWidth, symbol.implicitHeight) + padding * 2
MaterialSymbol {
id: symbol
anchors.centerIn: parent
}
}
@@ -32,7 +32,7 @@ Button {
implicitWidth: (root.down && bounce) ? clickedWidth : baseWidth
implicitHeight: (root.down && bounce) ? clickedHeight : baseHeight
property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent"
property color colBackground: ColorUtils.transparentize(colBackgroundHover, 1) || "transparent"
property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED"
property color colBackgroundActive: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F"
@@ -9,7 +9,7 @@ RippleButton {
implicitWidth: 40
implicitHeight: 40
Layout.leftMargin: 8
onClicked: {
downAction: () => {
parent.expanded = !parent.expanded;
}
buttonRadius: Appearance.rounding.full
@@ -6,25 +6,25 @@ import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications
Rectangle { // App icon
MaterialCookie { // App icon
id: root
property var appIcon: ""
property var summary: ""
property var urgency: NotificationUrgency.Normal
property bool isUrgent: urgency === NotificationUrgency.Critical
property var image: ""
property real scale: 1
property real size: 38 * scale
property real materialIconScale: 0.57
property real appIconScale: 0.8
property real smallAppIconScale: 0.49
property real materialIconSize: size * materialIconScale
property real appIconSize: size * appIconScale
property real smallAppIconSize: size * smallAppIconScale
property real materialIconSize: implicitSize * materialIconScale
property real appIconSize: implicitSize * appIconScale
property real smallAppIconSize: implicitSize * smallAppIconScale
implicitWidth: size
implicitHeight: size
radius: Appearance.rounding.full
color: Appearance.colors.colSecondaryContainer
implicitSize: 38 * scale
sides: isUrgent ? 10 : 0
amplitude: implicitSize / 24
color: isUrgent ? Appearance.colors.colPrimary : Appearance.colors.colSecondaryContainer
Loader {
id: materialSymbolLoader
active: root.appIcon == ""
@@ -34,12 +34,10 @@ Rectangle { // App icon
const defaultIcon = NotificationUtils.findSuitableMaterialSymbol("")
const guessedIcon = NotificationUtils.findSuitableMaterialSymbol(root.summary)
return (root.urgency == NotificationUrgency.Critical && guessedIcon === defaultIcon) ?
"release_alert" : guessedIcon
"priority_high" : guessedIcon
}
anchors.fill: parent
color: (root.urgency == NotificationUrgency.Critical) ?
ColorUtils.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) :
Appearance.m3colors.m3onSecondaryContainer
color: isUrgent ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer
iconSize: root.materialIconSize
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
@@ -6,6 +6,7 @@ import "./notification_utils.js" as NotificationUtils
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Notifications
/**
* A group of notifications from the same app.
@@ -154,6 +155,8 @@ MouseArea { // Notification group area
image: root?.multipleNotifications ? "" : notificationGroup?.notifications[0]?.image ?? ""
appIcon: notificationGroup?.appIcon
summary: notificationGroup?.notifications[root.notificationCount - 1]?.summary
urgency: root.notifications.some(n => n.urgency === NotificationUrgency.Critical.toString()) ?
NotificationUrgency.Critical : NotificationUrgency.Normal
}
ColumnLayout { // Content
@@ -32,7 +32,8 @@ TabButton {
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: (event) => {
onPressed: (event) => {
button.click() // Because the MouseArea already consumed the event
const {x,y} = event
const stateY = buttonBackground.y;
rippleAnim.x = x;
@@ -46,7 +47,6 @@ TabButton {
rippleAnim.restart();
}
onReleased: (event) => {
button.click() // Because the MouseArea already consumed the event
rippleFadeAnim.restart();
}
}
@@ -30,7 +30,8 @@ TabButton {
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: (event) => {
onPressed: (event) => {
root.click() // Because the MouseArea already consumed the event
const {x,y} = event
const stateY = buttonBackground.y;
rippleAnim.x = x;
@@ -44,7 +45,6 @@ TabButton {
rippleAnim.restart();
}
onReleased: (event) => {
root.click() // Because the MouseArea already consumed the event
rippleFadeAnim.restart();
}
}
@@ -234,7 +234,7 @@ Item { // Player instance
}
TrackChangeButton {
iconName: "skip_previous"
onClicked: root.player?.previous()
downAction: () => root.player?.previous()
}
Item {
id: progressBarContainer
@@ -277,7 +277,7 @@ Item { // Player instance
}
TrackChangeButton {
iconName: "skip_next"
onClicked: root.player?.next()
downAction: () => root.player?.next()
}
}
@@ -289,7 +289,7 @@ Item { // Player instance
property real size: 44
implicitWidth: size
implicitHeight: size
onClicked: root.player.togglePlaying();
downAction: () => root.player.togglePlaying();
buttonRadius: root.player?.isPlaying ? Appearance?.rounding.normal : size / 2
colBackground: root.player?.isPlaying ? blendedColors.colPrimary : blendedColors.colSecondaryContainer
@@ -18,7 +18,7 @@ Scope { // Scope
baseWidth: 40
baseHeight: 40
clickedWidth: baseWidth
clickedHeight: baseHeight + 20
clickedHeight: baseHeight + 10
buttonRadius: Appearance.rounding.normal
}
@@ -84,7 +84,7 @@ Scope { // Scope
VerticalButtonGroup {
OskControlButton { // Pin button
toggled: root.pinned
onClicked: root.pinned = !root.pinned
downAction: () => root.pinned = !root.pinned
contentItem: MaterialSymbol {
text: "keep"
horizontalAlignment: Text.AlignHCenter
@@ -111,7 +111,7 @@ Item {
id: workspaceArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
onPressed: {
if (root.draggingTargetWorkspace === -1) {
GlobalStates.overviewOpen = false
Hyprland.dispatch(`workspace ${workspaceValue}`)
@@ -144,41 +144,13 @@ ContentPage {
title: Translation.tr("Policies")
ConfigRow {
ColumnLayout {
// Weeb policy
ContentSubsectionLabel {
text: Translation.tr("Weeb")
}
ConfigSelectionArray {
currentValue: Config.options.policies.weeb
onSelected: newValue => {
Config.options.policies.weeb = newValue;
}
options: [
{
displayName: Translation.tr("No"),
icon: "close",
value: 0
},
{
displayName: Translation.tr("Yes"),
icon: "check",
value: 1
},
{
displayName: Translation.tr("Closet"),
icon: "ev_shadow",
value: 2
}
]
}
}
// AI policy
ColumnLayout {
// AI policy
ContentSubsectionLabel {
text: Translation.tr("AI")
}
ConfigSelectionArray {
currentValue: Config.options.policies.ai
onSelected: newValue => {
@@ -203,6 +175,38 @@ ContentPage {
]
}
}
// Weeb policy
ColumnLayout {
ContentSubsectionLabel {
text: Translation.tr("Weeb")
}
ConfigSelectionArray {
currentValue: Config.options.policies.weeb
onSelected: newValue => {
Config.options.policies.weeb = newValue;
}
options: [
{
displayName: Translation.tr("No"),
icon: "close",
value: 0
},
{
displayName: Translation.tr("Yes"),
icon: "check",
value: 1
},
{
displayName: Translation.tr("Closet"),
icon: "ev_shadow",
value: 2
}
]
}
}
}
}
@@ -64,8 +64,8 @@ ContentPage {
icon: "add_triangle"
text: Translation.tr("Sides")
value: Config.options.background.clock.cookie.sides
from: 1
to: 36
from: 0
to: 40
stepSize: 1
onValueChanged: {
Config.options.background.clock.cookie.sides = value;
@@ -569,6 +569,15 @@ ContentPage {
}
}
ConfigSwitch {
buttonIcon: "translate"
text: Translation.tr('Enable translator')
checked: Config.options.sidebar.translator.enable
onCheckedChanged: {
Config.options.sidebar.translator.enable = checked;
}
}
ContentSubsection {
title: Translation.tr("Corner open")
tooltip: Translation.tr("Allows you to open sidebars by clicking or hovering screen corners regardless of bar position")
@@ -336,10 +336,10 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
property int lastResponseLength: 0
onContentHeightChanged: {
if (atYEnd) positionViewAtEnd();
if (atYEnd) Qt.callLater(positionViewAtEnd);
}
onCountChanged: { // Auto-scroll when new messages are added
if (atYEnd) positionViewAtEnd();
if (atYEnd) Qt.callLater(positionViewAtEnd);
}
add: null // Prevent function calls from being janky
@@ -374,10 +374,9 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
anchors.centerIn: parent
spacing: 5
MaterialSymbol {
CookieWrappedMaterialSymbol {
Layout.alignment: Qt.AlignHCenter
iconSize: 60
color: Appearance.m3colors.m3outline
text: "neurology"
}
StyledText {
@@ -757,8 +756,8 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
delegate: ApiCommandButton {
property string commandRepresentation: `${root.commandPrefix}${modelData.name}`
buttonText: commandRepresentation
onClicked: {
if(modelData.sendDirectly) {
downAction: () => {
if (modelData.sendDirectly) {
root.handleInput(commandRepresentation)
} else {
messageInputField.text = commandRepresentation + (modelData.dontAddSpace ? "" : " ")
@@ -778,4 +777,4 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
}
}
}
@@ -198,10 +198,9 @@ Item {
anchors.centerIn: parent
spacing: 5
MaterialSymbol {
CookieWrappedMaterialSymbol {
Layout.alignment: Qt.AlignHCenter
iconSize: 60
color: Appearance.m3colors.m3outline
text: "bookmark_heart"
}
StyledText {
@@ -525,9 +524,10 @@ Item {
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
hoverEnabled: true
PointingHandInteraction {}
onClicked: {
onPressed: {
nsfwSwitch.checked = !nsfwSwitch.checked
}
}
@@ -566,8 +566,8 @@ Item {
buttonText: commandRepresentation
colBackground: Appearance.colors.colLayer2
onClicked: {
if(modelData.sendDirectly) {
downAction: () => {
if (modelData.sendDirectly) {
root.handleInput(commandRepresentation)
} else {
tagInputField.text = commandRepresentation + " "
@@ -10,7 +10,7 @@ GroupButton {
verticalPadding: 6
baseWidth: contentItem.implicitWidth + horizontalPadding * 2
clickedWidth: baseWidth + 20
clickedWidth: baseWidth + 14
baseHeight: contentItem.implicitHeight + verticalPadding * 2
buttonRadius: down ? Appearance.rounding.verysmall : Appearance.rounding.small
@@ -12,12 +12,17 @@ Item {
id: root
required property var scopeRoot
anchors.fill: parent
property bool aiChatEnabled: Config.options.policies.ai !== 0
property bool translatorEnabled: Config.options.sidebar.translator.enable
property bool animeEnabled: Config.options.policies.weeb !== 0
property bool animeCloset: Config.options.policies.weeb === 2
property var tabButtonList: [
...(Config.options.policies.ai !== 0 ? [{"icon": "neurology", "name": Translation.tr("Intelligence")}] : []),
{"icon": "translate", "name": Translation.tr("Translator")},
...(Config.options.policies.weeb === 1 ? [{"icon": "bookmark_heart", "name": Translation.tr("Anime")}] : [])
...(root.aiChatEnabled ? [{"icon": "neurology", "name": Translation.tr("Intelligence")}] : []),
...(root.translatorEnabled ? [{"icon": "translate", "name": Translation.tr("Translator")}] : []),
...((root.animeEnabled && !root.animeCloset) ? [{"icon": "bookmark_heart", "name": Translation.tr("Anime")}] : [])
]
property int selectedTab: 0
property int tabCount: swipeView.count
function focusActiveItem() {
swipeView.currentItem.forceActiveFocus()
@@ -26,7 +31,7 @@ Item {
Keys.onPressed: (event) => {
if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) {
root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1)
root.selectedTab = Math.min(root.selectedTab + 1, root.tabCount - 1)
event.accepted = true;
}
else if (event.key === Qt.Key_PageUp) {
@@ -34,11 +39,11 @@ Item {
event.accepted = true;
}
else if (event.key === Qt.Key_Tab) {
root.selectedTab = (root.selectedTab + 1) % root.tabButtonList.length;
root.selectedTab = (root.selectedTab + 1) % root.tabCount;
event.accepted = true;
}
else if (event.key === Qt.Key_Backtab) {
root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
root.selectedTab = (root.selectedTab - 1 + root.tabCount) % root.tabCount;
event.accepted = true;
}
}
@@ -52,6 +57,7 @@ Item {
PrimaryTabBar { // Tab strip
id: tabBar
visible: root.tabButtonList.length > 1
tabButtonList: root.tabButtonList
externalTrackedTab: root.selectedTab
function onCurrentIndexChanged(currentIndex) {
@@ -83,9 +89,9 @@ Item {
}
contentChildren: [
...(Config.options.policies.ai !== 0 ? [aiChat.createObject()] : []),
translator.createObject(),
...(Config.options.policies.weeb === 0 ? [] : [anime.createObject()])
...((root.aiChatEnabled || (!root.translatorEnabled && !root.animeEnabled)) ? [aiChat.createObject()] : []),
...(root.translatorEnabled ? [translator.createObject()] : []),
...(root.animeEnabled ? [anime.createObject()] : [])
]
}
@@ -78,156 +78,165 @@ Rectangle {
anchors.top: parent.top
anchors.margins: messagePadding
spacing: root.contentSpacing
RowLayout { // Header
spacing: 15
Rectangle {
Layout.fillWidth: true
implicitWidth: headerRowLayout.implicitWidth + 4 * 2
implicitHeight: headerRowLayout.implicitHeight + 4 * 2
color: Appearance.colors.colSecondaryContainer
radius: Appearance.rounding.small
RowLayout { // Header
id: headerRowLayout
anchors {
fill: parent
margins: 4
}
spacing: 18
Rectangle { // Name
id: nameWrapper
color: Appearance.colors.colSecondaryContainer
// color: "transparent"
radius: Appearance.rounding.small
implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30)
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Item { // Name
id: nameWrapper
implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30)
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
RowLayout {
id: nameRowLayout
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 7
RowLayout {
id: nameRowLayout
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 7
Item {
Layout.alignment: Qt.AlignVCenter
Layout.fillHeight: true
implicitWidth: messageData?.role == 'assistant' ? modelIcon.width : roleIcon.implicitWidth
implicitHeight: messageData?.role == 'assistant' ? modelIcon.height : roleIcon.implicitHeight
Item {
Layout.alignment: Qt.AlignVCenter
Layout.fillHeight: true
implicitWidth: messageData?.role == 'assistant' ? modelIcon.width : roleIcon.implicitWidth
implicitHeight: messageData?.role == 'assistant' ? modelIcon.height : roleIcon.implicitHeight
CustomIcon {
id: modelIcon
anchors.centerIn: parent
visible: messageData?.role == 'assistant' && Ai.models[messageData?.model].icon
width: Appearance.font.pixelSize.large
height: Appearance.font.pixelSize.large
source: messageData?.role == 'assistant' ? Ai.models[messageData?.model].icon :
messageData?.role == 'user' ? 'linux-symbolic' : 'desktop-symbolic'
CustomIcon {
id: modelIcon
anchors.centerIn: parent
visible: messageData?.role == 'assistant' && Ai.models[messageData?.model].icon
width: Appearance.font.pixelSize.large
height: Appearance.font.pixelSize.large
source: messageData?.role == 'assistant' ? Ai.models[messageData?.model].icon :
messageData?.role == 'user' ? 'linux-symbolic' : 'desktop-symbolic'
colorize: true
colorize: true
color: Appearance.m3colors.m3onSecondaryContainer
}
MaterialSymbol {
id: roleIcon
anchors.centerIn: parent
visible: !modelIcon.visible
iconSize: Appearance.font.pixelSize.larger
color: Appearance.m3colors.m3onSecondaryContainer
text: messageData?.role == 'user' ? 'person' :
messageData?.role == 'interface' ? 'settings' :
messageData?.role == 'assistant' ? 'neurology' :
'computer'
}
}
StyledText {
id: providerName
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
elide: Text.ElideRight
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSecondaryContainer
}
MaterialSymbol {
id: roleIcon
anchors.centerIn: parent
visible: !modelIcon.visible
iconSize: Appearance.font.pixelSize.larger
color: Appearance.m3colors.m3onSecondaryContainer
text: messageData?.role == 'user' ? 'person' :
messageData?.role == 'interface' ? 'settings' :
messageData?.role == 'assistant' ? 'neurology' :
'computer'
text: messageData?.role == 'assistant' ? Ai.models[messageData?.model].name :
(messageData?.role == 'user' && SystemInfo.username) ? SystemInfo.username :
Translation.tr("Interface")
}
}
}
StyledText {
id: providerName
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
elide: Text.ElideRight
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSecondaryContainer
text: messageData?.role == 'assistant' ? Ai.models[messageData?.model].name :
(messageData?.role == 'user' && SystemInfo.username) ? SystemInfo.username :
Translation.tr("Interface")
Button { // Not visible to model
id: modelVisibilityIndicator
visible: messageData?.role == 'interface'
implicitWidth: 16
implicitHeight: 30
Layout.alignment: Qt.AlignVCenter
background: Item
MaterialSymbol {
id: notVisibleToModelText
anchors.centerIn: parent
iconSize: Appearance.font.pixelSize.small
color: Appearance.colors.colSubtext
text: "visibility_off"
}
StyledToolTip {
text: Translation.tr("Not visible to model")
}
}
}
Button { // Not visible to model
id: modelVisibilityIndicator
visible: messageData?.role == 'interface'
implicitWidth: 16
implicitHeight: 30
Layout.alignment: Qt.AlignVCenter
ButtonGroup {
spacing: 5
background: Item
AiMessageControlButton {
id: copyButton
buttonIcon: activated ? "inventory" : "content_copy"
MaterialSymbol {
id: notVisibleToModelText
anchors.centerIn: parent
iconSize: Appearance.font.pixelSize.small
color: Appearance.colors.colSubtext
text: "visibility_off"
}
StyledToolTip {
text: Translation.tr("Not visible to model")
}
}
onClicked: {
Quickshell.clipboardText = root.messageData?.content
copyButton.activated = true
copyIconTimer.restart()
}
ButtonGroup {
spacing: 5
AiMessageControlButton {
id: copyButton
buttonIcon: activated ? "inventory" : "content_copy"
onClicked: {
Quickshell.clipboardText = root.messageData?.content
copyButton.activated = true
copyIconTimer.restart()
}
Timer {
id: copyIconTimer
interval: 1500
repeat: false
onTriggered: {
copyButton.activated = false
Timer {
id: copyIconTimer
interval: 1500
repeat: false
onTriggered: {
copyButton.activated = false
}
}
StyledToolTip {
text: Translation.tr("Copy")
}
}
StyledToolTip {
text: Translation.tr("Copy")
}
}
AiMessageControlButton {
id: editButton
activated: root.editing
enabled: root.messageData?.done ?? false
buttonIcon: "edit"
onClicked: {
root.editing = !root.editing
if (!root.editing) { // Save changes
root.saveMessage()
AiMessageControlButton {
id: editButton
activated: root.editing
enabled: root.messageData?.done ?? false
buttonIcon: "edit"
onClicked: {
root.editing = !root.editing
if (!root.editing) { // Save changes
root.saveMessage()
}
}
StyledToolTip {
text: root.editing ? Translation.tr("Save") : Translation.tr("Edit")
}
}
StyledToolTip {
text: root.editing ? Translation.tr("Save") : Translation.tr("Edit")
AiMessageControlButton {
id: toggleMarkdownButton
activated: !root.renderMarkdown
buttonIcon: "code"
onClicked: {
root.renderMarkdown = !root.renderMarkdown
}
StyledToolTip {
text: Translation.tr("View Markdown source")
}
}
}
AiMessageControlButton {
id: toggleMarkdownButton
activated: !root.renderMarkdown
buttonIcon: "code"
onClicked: {
root.renderMarkdown = !root.renderMarkdown
}
StyledToolTip {
text: Translation.tr("View Markdown source")
}
}
AiMessageControlButton {
id: deleteButton
buttonIcon: "close"
onClicked: {
Ai.removeMessage(root.messageIndex)
}
StyledToolTip {
text: Translation.tr("Delete")
AiMessageControlButton {
id: deleteButton
buttonIcon: "close"
onClicked: {
Ai.removeMessage(root.messageIndex)
}
StyledToolTip {
text: Translation.tr("Delete")
}
}
}
}
@@ -262,6 +271,8 @@ Rectangle {
property bool thinking: root.messageData?.thinking ?? true
property bool done: root.messageData?.done ?? false
property bool completed: thisBlock.completed ?? false
property bool forceDisableChunkSplitting: root.messageData.content.includes("```")
source: thisBlock.type === "code" ? "MessageCodeBlock.qml" :
thisBlock.type === "think" ? "MessageThinkBlock.qml" :
@@ -8,8 +8,9 @@ GroupButton {
property string buttonIcon
property bool activated: false
toggled: activated
baseWidth: height
colBackgroundHover: Appearance.colors.colSecondaryContainerHover
colBackgroundActive: Appearance.colors.colSecondaryContainerActive
contentItem: MaterialSymbol {
horizontalAlignment: Text.AlignHCenter
@@ -8,6 +8,7 @@ import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
ColumnLayout {
@@ -22,6 +23,9 @@ ColumnLayout {
property list<string> renderedLatexHashes: []
property string renderedSegmentContent: ""
property string shownText: ""
property bool forceDisableChunkSplitting: parent?.forceDisableChunkSplitting ?? false
property bool fadeChunkSplitting: !forceDisableChunkSplitting && !editing && !/\n\|/.test(shownText) && Config.options.sidebar.ai.textFadeIn
Layout.fillWidth: true
@@ -73,7 +77,7 @@ ColumnLayout {
renderLatex()
} else {
// console.log("Editing mode enabled", segmentContent)
textArea.text = segmentContent
root.shownText = segmentContent
}
}
@@ -88,7 +92,7 @@ ColumnLayout {
onRenderedSegmentContentChanged: {
// console.log("Rendered segment content changed: " + renderedSegmentContent);
if (renderedSegmentContent) {
textArea.text = renderedSegmentContent;
root.shownText = renderedSegmentContent;
}
}
@@ -104,39 +108,85 @@ ColumnLayout {
}
}
TextArea {
id: textArea
Layout.fillWidth: true
readOnly: !editing
selectByMouse: enableMouseSelection || editing
renderType: Text.NativeRendering
font.family: Appearance.font.family.reading
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
font.pixelSize: Appearance.font.pixelSize.small
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
wrapMode: TextEdit.Wrap
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText
text: Translation.tr("Waiting for response...")
onTextChanged: {
if (!root.editing) return
segmentContent = text
spacing: 0
Repeater {
id: textLinesRepeater
property list<real> textLineOpacities: []
model: ScriptModel {
// Split by either double newlines or single newlines in a list
values: root.fadeChunkSplitting ? root.shownText.split(/\n\n(?= {0,2})|\n(?= {0,2}[-\*])/g).filter(line => line.trim() !== "") : [root.shownText]
onValuesChanged: {
while (textLinesRepeater.textLineOpacities.length < values.length) {
textLinesRepeater.textLineOpacities.push(root.messageData.done ? 1 : 0);
}
}
}
delegate: TextArea {
id: textArea
required property int index
required property string modelData
onLinkActivated: (link) => {
Qt.openUrlExternally(link)
GlobalStates.sidebarLeftOpen = false
}
// Fade in animation
visible: opacity > 0
opacity: fadeChunkSplitting ? (textLinesRepeater.textLineOpacities[index] ?? (root.messageData.done ? 1 : 0)) : 1
Connections {
target: root.messageData
function onDoneChanged() {
if (root.messageData.done) {
textLinesRepeater.textLineOpacities[textArea.index] = 1
}
}
}
Connections {
target: textLinesRepeater.model
function onValuesChanged() {
if (textLinesRepeater.model.values.length > textArea.index + 1) {
textLinesRepeater.textLineOpacities[textArea.index] = 1
}
}
}
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
MouseArea { // Pointing hand for links
anchors.fill: parent
acceptedButtons: Qt.NoButton // Only for hover
hoverEnabled: true
cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor :
(enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor
Layout.fillWidth: true
readOnly: !editing
selectByMouse: enableMouseSelection || editing
renderType: Text.NativeRendering
font.family: Appearance.font.family.reading
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
font.pixelSize: Appearance.font.pixelSize.small
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
wrapMode: TextEdit.Wrap
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText
text: modelData
onTextChanged: {
if (!root.editing) return
segmentContent = text
}
onLinkActivated: (link) => {
Qt.openUrlExternally(link)
GlobalStates.sidebarLeftOpen = false
}
MouseArea { // Pointing hand for links
anchors.fill: parent
acceptedButtons: Qt.NoButton // Only for hover
hoverEnabled: true
cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor :
(enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor
}
// Rectangle {
// anchors.fill: parent
// color: "#22786378"
// border.width: 1
// border.color: "#7E7E7E"
// }
}
}
}
@@ -83,7 +83,7 @@ Rectangle {
Layout.margins: 10
Layout.rightMargin: 0
forceCircle: true
onClicked: {
downAction: () => {
root.setCollapsed(false)
}
contentItem: MaterialSymbol {
@@ -146,7 +146,7 @@ Rectangle {
toggled: root.selectedTab == index
buttonText: modelData.name
buttonIcon: modelData.icon
onClicked: {
onPressed: {
root.selectedTab = index
Persistent.states.sidebar.bottomGroup.tab = index
}
@@ -158,7 +158,7 @@ Rectangle {
anchors.left: parent.left
anchors.top: parent.top
forceCircle: true
onClicked: {
downAction: () => {
root.setCollapsed(true)
}
contentItem: MaterialSymbol {
@@ -70,7 +70,6 @@ Scope {
function toggle(): void {
GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;
if(GlobalStates.sidebarRightOpen) Notifications.timeoutAll();
}
function close(): void {
@@ -79,7 +78,6 @@ Scope {
function open(): void {
GlobalStates.sidebarRightOpen = true;
Notifications.timeoutAll();
}
}
@@ -89,7 +87,6 @@ Scope {
onPressed: {
GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;
if(GlobalStates.sidebarRightOpen) Notifications.timeoutAll();
}
}
GlobalShortcut {
@@ -98,7 +95,6 @@ Scope {
onPressed: {
GlobalStates.sidebarRightOpen = true;
Notifications.timeoutAll();
}
}
GlobalShortcut {
@@ -50,7 +50,7 @@ Item {
clip: true
buttonText: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}`
tooltipText: (monthShift === 0) ? "" : Translation.tr("Jump to current month")
onClicked: {
downAction: () => {
monthShift = 0;
}
}
@@ -60,7 +60,7 @@ Item {
}
CalendarHeaderButton {
forceCircle: true
onClicked: {
downAction: () => {
monthShift--;
}
contentItem: MaterialSymbol {
@@ -72,7 +72,7 @@ Item {
}
CalendarHeaderButton {
forceCircle: true
onClicked: {
downAction: () => {
monthShift++;
}
contentItem: MaterialSymbol {
@@ -11,6 +11,7 @@ import Quickshell.Hyprland
QuickToggleButton {
id: root
visible: BluetoothStatus.available
toggled: BluetoothStatus.enabled
buttonIcon: BluetoothStatus.connected ? "bluetooth_connected" : BluetoothStatus.enabled ? "bluetooth" : "bluetooth_disabled"
onClicked: {
@@ -109,12 +109,12 @@ Item {
AudioDeviceSelectorButton {
Layout.fillWidth: true
input: false
onClicked: root.showDeviceSelectorDialog(input)
downAction: () => root.showDeviceSelectorDialog(input)
}
AudioDeviceSelectorButton {
Layout.fillWidth: true
input: true
onClicked: root.showDeviceSelectorDialog(input)
downAction: () => root.showDeviceSelectorDialog(input)
}
}
}
@@ -280,6 +280,7 @@ Item { // Bar content region
color: rightSidebarButton.colText
}
MaterialSymbol {
visible: BluetoothStatus.available
text: BluetoothStatus.connected ? "bluetooth_connected" : BluetoothStatus.enabled ? "bluetooth" : "bluetooth_disabled"
iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText
@@ -12,6 +12,7 @@ import QtQuick
Singleton {
id: root
readonly property bool available: Bluetooth.adapters.values.length > 0
readonly property bool enabled: Bluetooth.defaultAdapter?.enabled ?? false
readonly property BluetoothDevice firstActiveDevice: Bluetooth.defaultAdapter?.devices.values.find(device => device.connected) ?? null
readonly property int activeDeviceCount: Bluetooth.defaultAdapter?.devices.values.filter(device => device.connected).length ?? 0
@@ -40,7 +40,7 @@ Singleton {
id: resetFilePathNextWallpaperChange
enabled: false
target: Config.options.background
onWallpaperPathChanged: {
function onWallpaperPathChanged() {
root.filePath = ""
root.filePath = Directories.generatedMaterialThemePath
resetFilePathNextWallpaperChange.enabled = false
@@ -69,6 +69,7 @@ Singleton {
}
property bool silent: false
property int unread: 0
property var filePath: Directories.notificationsPath
property list<Notif> list: []
property var popupList: list.filter((notif) => notif.popup);
@@ -173,12 +174,17 @@ Singleton {
}
}
root.unread++;
root.notify(newNotifObject);
// console.log(notifToString(newNotifObject));
notifFileView.setText(stringifyList(root.list));
}
}
function markAllRead() {
root.unread = 0;
}
function discardNotification(id) {
console.log("[Notifications] Discarding notification with ID: " + id);
const index = root.list.findIndex((notif) => notif.notificationId === id);
+2 -2
View File
@@ -172,7 +172,7 @@ ApplicationWindow {
iconText: "edit"
buttonText: Translation.tr("Config file")
expanded: navRail.expanded
onClicked: {
downAction: () => {
Qt.openUrlExternally(`${Directories.config}/illogical-impulse/config.json`);
}
@@ -190,7 +190,7 @@ ApplicationWindow {
required property var index
required property var modelData
toggled: root.currentPage === index
onClicked: root.currentPage = index;
onPressed: root.currentPage = index;
expanded: navRail.expanded
buttonIcon: modelData.icon
buttonIconRotation: modelData.iconRotation || 0
+1 -1
View File
@@ -63,7 +63,7 @@ install-local-pkgbuild() {
x pushd $location
source ./PKGBUILD
x yay -S $installflags --asdeps "${depends[@]}"
x yay -S --sudoloop $installflags --asdeps "${depends[@]}"
x makepkg -Asi --noconfirm
x popd