Merge branch 'ii-qs' into ii-qs-patch-1

This commit is contained in:
_xB
2025-06-12 09:23:30 +03:00
committed by GitHub
94 changed files with 2060 additions and 701 deletions
@@ -0,0 +1,135 @@
import "root:/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Services.UPower
Scope {
id: root
property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json`
property real centerX: 0
property real centerY: 0
property color dominantColor: Appearance.colors.colPrimary
property bool dominantColorIsDark: dominantColor.hslLightness < 0.5
property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer), 1)
property color colText: ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (root.dominantColorIsDark ? 0.8 : 0.12))
function updateWidgetPosition(fileContent) {
// console.log("[BackgroundWidgets] Updating widget position with content:", fileContent)
const parsedContent = JSON.parse(fileContent)
root.centerX = parsedContent.center_x
root.centerY = parsedContent.center_y
root.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary
}
Timer {
id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
running: false
onTriggered: {
root.updateWidgetPosition(leastBusyRegionFileView.text())
}
}
FileView {
id: leastBusyRegionFileView
path: Qt.resolvedUrl(root.filePath)
watchChanges: true
onFileChanged: {
this.reload()
delayedFileRead.start()
}
onLoadedChanged: {
const fileContent = leastBusyRegionFileView.text()
root.updateWidgetPosition(fileContent)
}
}
Variants { // For each monitor
model: Quickshell.screens
Loader {
required property var modelData
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
active: !ToplevelManager.activeToplevel?.activated
sourceComponent: PanelWindow { // Window
id: windowRoot
screen: modelData
property var textHorizontalAlignment: root.centerX / monitor.scale < windowRoot.width / 3 ? Text.AlignLeft :
(root.centerX / monitor.scale > windowRoot.width * 2 / 3 ? Text.AlignRight : Text.AlignHCenter)
WlrLayershell.layer: WlrLayer.Bottom
WlrLayershell.namespace: "quickshell:backgroundWidgets"
anchors {
top: true
bottom:true
left: true
right: true
}
color: "transparent"
HyprlandWindow.visibleMask: Region {
item: widgetBackground
}
Rectangle {
id: widgetBackground
property real verticalPadding: 20
property real horizontalPadding: 30
radius: 40
color: root.colBackground
implicitHeight: columnLayout.implicitHeight + verticalPadding * 2
implicitWidth: columnLayout.implicitWidth + horizontalPadding * 2
anchors {
left: parent.left
top: parent.top
leftMargin: (root.centerX / monitor.scale - implicitWidth / 2)
topMargin: (root.centerY / monitor.scale - implicitHeight / 2)
Behavior on leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on topMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
ColumnLayout {
id: columnLayout
anchors.centerIn: parent
spacing: -5
StyledText {
Layout.fillWidth: true
horizontalAlignment: windowRoot.textHorizontalAlignment
font.pixelSize: 95
color: root.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: DateTime.time
}
StyledText {
Layout.fillWidth: true
horizontalAlignment: windowRoot.textHorizontalAlignment
font.pixelSize: 25
color: root.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: DateTime.date
}
}
}
}
}
}
}
@@ -12,7 +12,6 @@ Item {
height: parent.height
width: colLayout.width
ColumnLayout {
id: colLayout
+15 -5
View File
@@ -19,6 +19,13 @@ Scope {
readonly property int osdHideMouseMoveThreshold: 20
property bool showBarBackground: ConfigOptions.bar.showBackground
component VerticalBarSeparator: Rectangle {
Layout.topMargin: barHeight / 3
Layout.bottomMargin: barHeight / 3
Layout.fillHeight: true
implicitWidth: 1
color: Appearance.colors.colOutlineVariant
// Check screensList from config, If no screens are specified, show on all screens
property var filteredScreens: {
@@ -150,7 +157,7 @@ Scope {
colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
colBackgroundHover: Appearance.colors.colLayer1Hover
colRipple: Appearance.colors.colLayer1Active
colBackgroundToggled: Appearance.m3colors.m3secondaryContainer
colBackgroundToggled: Appearance.colors.colSecondaryContainer
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
colRippleToggled: Appearance.colors.colSecondaryContainerActive
toggled: GlobalStates.sidebarLeftOpen
@@ -177,7 +184,7 @@ Scope {
}
ActiveWindow {
visible: barRoot.useShortenedForm === 0
visible: barRoot.useShortenedForm === 0 && width > 0 && height > 0
Layout.rightMargin: Appearance.rounding.screenRounding
Layout.fillWidth: true
bar: barRoot
@@ -189,7 +196,7 @@ Scope {
RowLayout { // Middle section
id: middleSection
anchors.centerIn: parent
spacing: 8
spacing: ConfigOptions?.bar.borderless ? 4 : 8
RowLayout {
id: leftCenterGroup
@@ -210,9 +217,10 @@ Scope {
}
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless}
RowLayout {
id: middleCenterGroup
Layout.fillWidth: true
Layout.fillHeight: true
Workspaces {
@@ -231,6 +239,8 @@ Scope {
}
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless}
RowLayout {
id: rightCenterGroup
Layout.preferredWidth: leftCenterGroup.width
@@ -344,7 +354,7 @@ Scope {
colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
colBackgroundHover: Appearance.colors.colLayer1Hover
colRipple: Appearance.colors.colLayer1Active
colBackgroundToggled: Appearance.m3colors.m3secondaryContainer
colBackgroundToggled: Appearance.colors.colSecondaryContainer
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
colRippleToggled: Appearance.colors.colSecondaryContainerActive
toggled: GlobalStates.sidebarRightOpen
@@ -48,7 +48,7 @@ Rectangle {
lineWidth: 2
value: percentage
size: 26
secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.m3colors.m3secondaryContainer
secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.colors.colSecondaryContainer
primaryColor: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer
fill: (isLow && !isCharging)
+2 -2
View File
@@ -62,13 +62,13 @@ Item {
lineWidth: 2
value: activePlayer?.position / activePlayer?.length
size: 26
secondaryColor: Appearance.m3colors.m3secondaryContainer
secondaryColor: Appearance.colors.colSecondaryContainer
primaryColor: Appearance.m3colors.m3onSecondaryContainer
MaterialSymbol {
anchors.centerIn: parent
fill: 1
text: activePlayer?.isPlaying ? "pause" : "play_arrow"
text: activePlayer?.isPlaying ? "pause" : "music_note"
iconSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSecondaryContainer
}
+1 -1
View File
@@ -23,7 +23,7 @@ Item {
lineWidth: 2
value: percentage
size: 26
secondaryColor: Appearance.m3colors.m3secondaryContainer
secondaryColor: Appearance.colors.colSecondaryContainer
primaryColor: Appearance.m3colors.m3onSecondaryContainer
MaterialSymbol {
@@ -2,6 +2,7 @@ import "root:/"
import "root:/services/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -98,15 +99,17 @@ Item {
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
radius: Appearance.rounding.full
property var radiusLeft: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index)) ? 0 : Appearance.rounding.full
property var radiusRight: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2)) ? 0 : Appearance.rounding.full
property var leftOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index))
property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2))
property var radiusLeft: leftOccupied ? 0 : Appearance.rounding.full
property var radiusRight: rightOccupied ? 0 : Appearance.rounding.full
topLeftRadius: radiusLeft
bottomLeftRadius: radiusLeft
topRightRadius: radiusRight
bottomRightRadius: radiusRight
color: Appearance.colors.colLayer2
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0
Behavior on opacity {
@@ -144,13 +147,13 @@ Item {
Behavior on activeWorkspaceMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on idx1 {
Behavior on idx1 { // Leading anim
NumberAnimation {
duration: 100
easing.type: Easing.OutSine
}
}
Behavior on idx2 {
Behavior on idx2 { // Following anim
NumberAnimation {
duration: 300
easing.type: Easing.OutSine
@@ -203,7 +206,7 @@ Item {
elide: Text.ElideRight
color: (monitor.activeWorkspace?.id == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.colors.colOnLayer1 :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
Behavior on opacity {
@@ -15,11 +15,11 @@ Singleton {
property QtObject sizes
property string syntaxHighlightingTheme
// [!] Enabling transparency can affect readability when using light theme.
// Extremely conservative transparency values for consistency and readability
property real transparency: 0
property real contentTransparency: 0
// property real transparency: 0.15
// property real contentTransparency: 0.5
// property real transparency: m3colors.darkmode ? 0.05 : 0
// property real contentTransparency: m3colors.darkmode ? 0.18 : 0
m3colors: QtObject {
property bool darkmode: false
@@ -106,10 +106,10 @@ Singleton {
property color colOnLayer0: m3colors.m3onBackground
property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency))
property color colLayer0Active: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.8, root.contentTransparency))
property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.7), root.contentTransparency);
property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.8), root.contentTransparency);
property color colOnLayer1: m3colors.m3onSurfaceVariant;
property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45);
property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55), root.contentTransparency)
property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.7), root.contentTransparency)
property color colOnLayer2: m3colors.m3onSurface;
property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4);
property color colLayer3: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96), root.contentTransparency)
@@ -122,21 +122,30 @@ Singleton {
property color colLayer3Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.90), root.contentTransparency)
property color colLayer3Active: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.80), root.contentTransparency);
property color colPrimary: m3colors.m3primary
property color colOnPrimary: m3colors.m3onPrimary
property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87)
property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7)
property color colPrimaryContainer: m3colors.m3primaryContainer
property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Hover, 0.7)
property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Active, 0.6)
property color colSecondary: m3colors.m3secondary
property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)
property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4)
property color colSecondaryContainer: ColorUtils.transparentize(m3colors.m3secondaryContainer, root.contentTransparency)
property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6)
property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54)
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency)
property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
property color colSurfaceContainerHigh: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency)
property color colSurfaceContainerHighest: ColorUtils.transparentize(m3colors.m3surfaceContainerHighest, root.contentTransparency)
property color colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95)
property color colSurfaceContainerHighestActive: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85)
property color colTooltip: "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses this color
property color colTooltip: m3colors.darkmode ? ColorUtils.mix(m3colors.m3background, "#3C4043", 0.5) : "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses #3C4043
property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color
property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.85)
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
property color colOutlineVariant: m3colors.m3outlineVariant
}
rounding: QtObject {
@@ -176,6 +185,7 @@ Singleton {
animationCurves: QtObject {
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] // Default, 350ms
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] // Default, 500ms
readonly property list<real> expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] // Default, 650ms
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] // Default, 200ms
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
@@ -287,7 +297,7 @@ Singleton {
property real searchWidthCollapsed: 260
property real searchWidth: 450
property real hyprlandGapsOut: 5
property real elevationMargin: 8
property real elevationMargin: 10
property real fabShadowRadius: 5
property real fabHoveredShadowRadius: 7
}
@@ -9,14 +9,23 @@ Singleton {
}
property QtObject appearance: QtObject {
property int fakeScreenRounding: 1 // 0: None | 1: Always | 2: When not fullscreen
property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen
}
property QtObject audio: QtObject { // Values in %
property QtObject protection: QtObject { // Prevent sudden bangs
property bool enable: true
property real maxAllowedIncrease: 10
property real maxAllowed: 90 // Realistically should already provide some protection when it's 99...
}
}
property QtObject apps: QtObject {
property string bluetooth: "better-control --bluetooth"
property string bluetooth: "kcmshell6 kcm_bluetooth"
property string imageViewer: "loupe"
property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi"
property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center"
property string network: "plasmawindowed org.kde.plasma.networkmanagement"
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
property string settings: "systemsettings"
property string taskManager: "plasma-systemmonitor --page-name Processes"
property string terminal: "kitty -1" // This is only for shell actions
}
@@ -29,7 +38,7 @@ Singleton {
property QtObject bar: QtObject {
property bool bottom: false // Instead of top
property bool borderless: true
property bool borderless: false // true for no grouping of items
property string topLeftIcon: "spark" // Options: distro, spark
property bool showBackground: true
property QtObject resources: QtObject {
@@ -48,15 +57,24 @@ Singleton {
}
property QtObject dock: QtObject {
property bool enable: false
property real height: 60
property real hoverRegionHeight: 3
property bool pinnedOnStartup: false
property bool hoverToReveal: false // When false, only reveals on empty workspace
property list<string> pinnedApps: [ // IDs of pinned entries
"org.kde.dolphin",
"kitty",
]
}
property QtObject language: QtObject {
property QtObject translator: QtObject {
property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google
property string targetLanguage: "auto" // Run `trans -list-all` for available languages
property string sourceLanguage: "auto"
}
}
property QtObject networking: QtObject {
property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
}
@@ -21,7 +21,7 @@ Singleton {
property string booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`)
property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework")
property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️")
property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/latex`)
property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/media/latex`)
property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`)
property string shellConfigName: "config.json"
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
@@ -32,6 +32,7 @@ Singleton {
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/switchwall.sh`)
// Cleanup on init
Component.onCompleted: {
Hyprland.dispatch(`exec mkdir -p '${shellConfig}'`)
Hyprland.dispatch(`exec mkdir -p '${favicons}'`)
Hyprland.dispatch(`exec rm -rf '${coverArt}'; mkdir -p '${coverArt}'`)
Hyprland.dispatch(`exec rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`)
@@ -39,6 +39,30 @@ function colorWithSaturationOf(color1, color2) {
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL).
*
* @param {string} color - The base color (any Qt.color-compatible string).
* @param {number} lightness - The lightness value to use (0-1).
* @returns {Qt.rgba} The resulting color.
*/
function colorWithLightness(color, lightness) {
var c = Qt.color(color);
return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);
}
/**
* Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL).
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take lightness from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithLightnessOf(color1, color2) {
var c2 = Qt.color(color2);
return colorWithLightness(color1, c2.hslLightness);
}
/**
* Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.
*
@@ -66,7 +90,7 @@ function adaptToAccent(color1, color2) {
* @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.
* @returns {Qt.rgba} The resulting mixed color.
*/
function mix(color1, color2, percentage) {
function mix(color1, color2, percentage = 0.5) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);
@@ -11,7 +11,7 @@ import QtQuick.Layouts
*/
Rectangle {
id: root
default property alias content: rowLayout.data
default property alias data: rowLayout.data
property real spacing: 5
property real padding: 0
property int clickIndex: rowLayout.clickIndex
@@ -14,8 +14,8 @@ Item {
property int lineWidth: 2
property real value: 0
property color primaryColor: Appearance.m3colors.m3onSecondaryContainer
property color secondaryColor: Appearance.m3colors.m3secondaryContainer
property real gapAngle: Math.PI / 10
property color secondaryColor: Appearance.colors.colSecondaryContainer
property real gapAngle: Math.PI / 9
property bool fill: false
property int fillOverflow: 2
property int animationDuration: 1000
@@ -15,7 +15,7 @@ Rectangle {
property real extraBottomBorderWidth: 2
property color borderColor: Appearance.colors.colOnLayer0
property real borderRadius: 5
property color keyColor: Appearance.m3colors.m3surfaceContainerLow
property color keyColor: Appearance.colors.colSurfaceContainerLow
implicitWidth: keyFace.implicitWidth + borderWidth * 2
implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth
radius: borderRadius
@@ -6,23 +6,26 @@ Text {
id: root
property real iconSize: Appearance?.font.pixelSize.small ?? 16
property real fill: 0
renderType: Text.NativeRendering
font.hintingPreference: Font.PreferFullHinting
property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping
renderType: Text.CurveRendering
font {
hintingPreference: Font.PreferFullHinting
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
pixelSize: iconSize
}
verticalAlignment: Text.AlignVCenter
font.family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
font.pixelSize: iconSize
color: Appearance.m3colors.m3onBackground
Behavior on fill {
NumberAnimation {
duration: Appearance?.animation.elementMoveFast.duration ?? 200
easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
}
}
// Behavior on fill {
// NumberAnimation {
// duration: Appearance?.animation.elementMoveFast.duration ?? 200
// easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
// easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
// }
// }
font.variableAxes: {
"FILL": fill,
"FILL": truncatedFill,
// "wght": font.weight,
// "GRAD": 0,
"opsz": iconSize,
@@ -30,7 +30,7 @@ Button {
Layout.alignment: Qt.AlignHCenter
radius: Appearance.rounding.full
color: toggled ?
(button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.m3colors.m3secondaryContainer) :
(button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer) :
(button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1))
Behavior on color {
@@ -15,7 +15,7 @@ RippleButton {
leftPadding: 15
rightPadding: 15
buttonRadius: Appearance.rounding.small
colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.m3colors.m3secondaryContainer : Appearance.m3colors.m3surfaceContainerHighest
colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainer : Appearance.colors.colSurfaceContainerHighest
colBackgroundHover: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSurfaceContainerHighestHover
colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colSurfaceContainerHighestActive
@@ -26,7 +26,7 @@ Rectangle { // App icon
implicitWidth: size
implicitHeight: size
radius: Appearance.rounding.full
color: Appearance.m3colors.m3secondaryContainer
color: Appearance.colors.colSecondaryContainer
Loader {
id: materialSymbolLoader
active: root.appIcon == ""
@@ -113,7 +113,7 @@ Item { // Notification group area
id: background
anchors.left: parent.left
width: parent.width
color: Appearance.m3colors.m3surfaceContainer
color: Appearance.colors.colSurfaceContainer
radius: Appearance.rounding.normal
anchors.leftMargin: root.xOffset
@@ -154,42 +154,56 @@ Item { // Notification group area
ColumnLayout { // Content
Layout.fillWidth: true
spacing: expanded ?
((root.multipleNotifications &&
notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 :
5) : 0
spacing: expanded ? (root.multipleNotifications ?
(notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 :
5 : 0) : 0
// spacing: 00
Behavior on spacing {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
RowLayout { // App name (or summary when there's only 1 notif) and time
Item { // App name (or summary when there's only 1 notif) and time
id: topRow
spacing: 0
// spacing: 0
Layout.fillWidth: true
property real fontSize: Appearance.font.pixelSize.smaller
property bool showAppName: root.multipleNotifications
implicitHeight: Math.max(topTextRow.implicitHeight, expandButton.implicitHeight)
StyledText {
id: appName
text: (topRow.showAppName ?
notificationGroup?.appName :
notificationGroup?.notifications[0]?.summary) || ""
font.pixelSize: topRow.showAppName ?
topRow.fontSize :
Appearance.font.pixelSize.small
color: topRow.showAppName ?
Appearance.colors.colSubtext :
Appearance.colors.colOnLayer2
RowLayout {
id: topTextRow
anchors.left: parent.left
anchors.right: expandButton.left
anchors.verticalCenter: parent.verticalCenter
spacing: 5
StyledText {
id: appName
elide: Text.ElideRight
Layout.fillWidth: true
text: (topRow.showAppName ?
notificationGroup?.appName :
notificationGroup?.notifications[0]?.summary) || ""
font.pixelSize: topRow.showAppName ?
topRow.fontSize :
Appearance.font.pixelSize.small
color: topRow.showAppName ?
Appearance.colors.colSubtext :
Appearance.colors.colOnLayer2
}
StyledText {
id: timeText
// Layout.fillWidth: true
Layout.rightMargin: 10
horizontalAlignment: Text.AlignLeft
text: NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time)
font.pixelSize: topRow.fontSize
color: Appearance.colors.colSubtext
}
}
StyledText {
id: timeText
text: " • " + NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time)
font.pixelSize: topRow.fontSize
color: Appearance.colors.colSubtext
Layout.alignment: Qt.AlignRight
Layout.fillWidth: true
}
Item { Layout.fillWidth: true }
NotificationGroupExpandButton {
id: expandButton
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
count: root.notificationCount
expanded: root.expanded
fontSize: topRow.fontSize
@@ -129,9 +129,9 @@ Item { // Notification item area
color: (expanded && !onlyNotification) ?
(notificationObject.urgency == NotificationUrgency.Critical) ?
ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) :
(Appearance.m3colors.m3surfaceContainerHigh) :
ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHighest)
ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer2, 0.35) :
(Appearance.colors.colSurfaceContainerHigh) :
ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHighest)
implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight
Behavior on implicitHeight {
@@ -110,13 +110,29 @@ TabButton {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
}
Rectangle {
Item {
id: ripple
radius: Appearance?.rounding.full ?? 9999
color: button.colRipple
width: ripple.implicitWidth
height: ripple.implicitHeight
opacity: 0
property real implicitWidth: 0
property real implicitHeight: 0
visible: width > 0 && height > 0
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
}
RadialGradient {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: button.colRipple }
GradientStop { position: 0.3; color: button.colRipple }
GradientStop { position: 0.5 ; color: Qt.rgba(button.colRipple.r, button.colRipple.g, button.colRipple.b, 0) }
}
}
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
@@ -13,6 +13,7 @@ Item {
implicitWidth: (reveal || vertical) ? childrenRect.width : 0
implicitHeight: (reveal || !vertical) ? childrenRect.height : 0
visible: width > 0 && height > 0
Behavior on implicitWidth {
enabled: !vertical
@@ -150,16 +150,29 @@ Button {
}
}
Rectangle {
Item {
id: ripple
radius: Appearance?.rounding.full ?? 9999
width: ripple.implicitWidth
height: ripple.implicitHeight
opacity: 0
color: root.rippleColor
Behavior on color {
visible: width > 0 && height > 0
property real implicitWidth: 0
property real implicitHeight: 0
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
}
RadialGradient {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: root.rippleColor }
GradientStop { position: 0.3; color: root.rippleColor }
GradientStop { position: 0.5; color: Qt.rgba(root.rippleColor.r, root.rippleColor.g, root.rippleColor.b, 0) }
}
}
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
@@ -0,0 +1,129 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
Item {
id: root
property real dialogPadding: 15
property real dialogMargin: 30
property string titleText: "Selection Dialog"
property alias items: choiceModel.values
property int selectedId: choiceListView.currentIndex
property var defaultChoice
signal canceled();
signal selected(var result);
Rectangle { // Scrim
id: scrimOverlay
anchors.fill: parent
radius: Appearance.rounding.small
color: Appearance.colors.colScrim
MouseArea {
hoverEnabled: true
anchors.fill: parent
preventStealing: true
propagateComposedEvents: false
}
}
Rectangle { // The dialog
id: dialog
color: Appearance.colors.colSurfaceContainerHigh
radius: Appearance.rounding.normal
anchors.fill: parent
anchors.margins: dialogMargin
implicitHeight: dialogColumnLayout.implicitHeight
ColumnLayout {
id: dialogColumnLayout
anchors.fill: parent
spacing: 16
StyledText {
id: dialogTitle
Layout.topMargin: dialogPadding
Layout.leftMargin: dialogPadding
Layout.rightMargin: dialogPadding
Layout.alignment: Qt.AlignLeft
color: Appearance.m3colors.m3onSurface
font.pixelSize: Appearance.font.pixelSize.larger
text: root.titleText
}
Rectangle {
color: Appearance.m3colors.m3outline
implicitHeight: 1
Layout.fillWidth: true
Layout.leftMargin: dialogPadding
Layout.rightMargin: dialogPadding
}
ListView {
id: choiceListView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
currentIndex: root.defaultChoice !== undefined ? root.items.indexOf(root.defaultChoice) : -1
model: ScriptModel {
id: choiceModel
}
delegate: StyledRadioButton {
id: radioButton
required property var modelData
required property int index
anchors {
left: parent?.left
right: parent?.right
leftMargin: root.dialogPadding
rightMargin: root.dialogPadding
}
description: modelData.toString()
checked: index === choiceListView.currentIndex
onCheckedChanged: {
if (checked) {
choiceListView.currentIndex = index;
}
}
}
}
Rectangle {
color: Appearance.m3colors.m3outline
implicitHeight: 1
Layout.fillWidth: true
Layout.leftMargin: dialogPadding
Layout.rightMargin: dialogPadding
}
RowLayout {
id: dialogButtonsRowLayout
Layout.bottomMargin: dialogPadding
Layout.leftMargin: dialogPadding
Layout.rightMargin: dialogPadding
Layout.alignment: Qt.AlignRight
DialogButton {
buttonText: qsTr("Cancel")
onClicked: root.canceled()
}
DialogButton {
buttonText: qsTr("OK")
onClicked: root.selected(
root.selectedId === -1 ? null :
root.items[root.selectedId]
)
}
}
}
}
}
@@ -0,0 +1,16 @@
import "root:/modules/common"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Label {
renderType: Text.NativeRendering
verticalAlignment: Text.AlignVCenter
font {
hintingPreference: Font.PreferFullHinting
family: Appearance?.font.family.main ?? "sans-serif"
pixelSize: Appearance?.font.pixelSize.small ?? 15
}
color: Appearance?.m3colors.m3onBackground ?? "black"
linkColor: Appearance?.m3colors.m3primary
}
@@ -18,6 +18,7 @@ ListView {
property real removeOvershoot: 20 // Account for gaps and bouncy animations
property int dragIndex: -1
property real dragDistance: 0
property bool popin: true
function resetDrag() {
root.dragIndex = -1
@@ -27,7 +28,7 @@ ListView {
add: Transition {
animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
properties: popin ? "opacity,scale" : "opacity",
from: 0,
to: 1,
}),
@@ -40,46 +41,46 @@ ListView {
property: "y",
}),
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
properties: popin ? "opacity,scale" : "opacity",
to: 1,
}),
]
}
displaced: Transition {
animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y",
}),
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
to: 1,
}),
]
}
// displaced: Transition {
// animations: [
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// property: "y",
// }),
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// properties: "opacity,scale",
// to: 1,
// }),
// ]
// }
move: Transition {
animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y",
}),
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
to: 1,
}),
]
}
moveDisplaced: Transition {
animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y",
}),
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
to: 1,
}),
]
}
// move: Transition {
// animations: [
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// property: "y",
// }),
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// properties: "opacity,scale",
// to: 1,
// }),
// ]
// }
// moveDisplaced: Transition {
// animations: [
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// property: "y",
// }),
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// properties: "opacity,scale",
// to: 1,
// }),
// ]
// }
remove: Transition {
animations: [
@@ -19,7 +19,7 @@ Slider {
property real handleLimit: root.backgroundDotMargins
property real trackHeight: 30 * scale
property color highlightColor: Appearance.colors.colPrimary
property color trackColor: Appearance.m3colors.m3secondaryContainer
property color trackColor: Appearance.colors.colSecondaryContainer
property color handleColor: Appearance.m3colors.m3onSecondaryContainer
property real trackRadius: Appearance.rounding.verysmall * scale
property real unsharpenRadius: Appearance.rounding.unsharpen
@@ -13,7 +13,7 @@ Switch {
implicitHeight: 32 * root.scale
implicitWidth: 52 * root.scale
property color activeColor: Appearance?.colors.colPrimary ?? "#685496"
property color inactiveColor: Appearance?.m3colors.m3surfaceContainerHighest ?? "#45464F"
property color inactiveColor: Appearance?.colors.colSurfaceContainerHighest ?? "#45464F"
PointingHandInteraction {}
@@ -11,4 +11,5 @@ Text {
pixelSize: Appearance?.font.pixelSize.small ?? 15
}
color: Appearance?.m3colors.m3onBackground ?? "black"
linkColor: Appearance?.m3colors.m3primary
}
@@ -5,7 +5,7 @@ import QtQuick.Controls
TextArea {
renderType: Text.NativeRendering
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
placeholderTextColor: Appearance.m3colors.m3outline
font {
family: Appearance?.font.family.main ?? "sans-serif"
@@ -0,0 +1,78 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Io
Canvas { // Visualizer
id: root
property list<var> points
property list<var> smoothPoints
property real maxVisualizerValue: 1000
property int smoothing: 2
property bool live: true
property color color: Appearance.m3colors.m3primary
onPointsChanged: () => {
root.requestPaint()
}
anchors.fill: parent
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
var points = root.points;
var maxVal = root.maxVisualizerValue || 1;
var h = height;
var w = width;
var n = points.length;
if (n < 2) return;
// Smoothing: simple moving average (optional)
var smoothWindow = root.smoothing; // adjust for more/less smoothing
root.smoothPoints = [];
for (var i = 0; i < n; ++i) {
var sum = 0, count = 0;
for (var j = -smoothWindow; j <= smoothWindow; ++j) {
var idx = Math.max(0, Math.min(n - 1, i + j));
sum += points[idx];
count++;
}
root.smoothPoints.push(sum / count);
}
if (!root.live) smoothedPoints.fill(0); // If not playing, show no points
ctx.beginPath();
ctx.moveTo(0, h);
for (var i = 0; i < n; ++i) {
var x = i * w / (n - 1);
var y = h - (root.smoothPoints[i] / maxVal) * h;
ctx.lineTo(x, y);
}
ctx.lineTo(w, h);
ctx.closePath();
ctx.fillStyle = Qt.rgba(
root.color.r,
root.color.g,
root.color.b,
0.15
);
ctx.fill();
}
layer.enabled: true
layer.effect: MultiEffect { // Blur a bit to obscure away the points
source: root
saturation: 0.2
blurEnabled: true
blurMax: 7
blur: 1
}
}
+107 -92
View File
@@ -18,115 +18,130 @@ Scope { // Scope
Variants { // For each monitor
model: Quickshell.screens
PanelWindow { // Window
Loader {
id: dockLoader
required property var modelData
id: dockRoot
screen: modelData
property bool reveal: root.pinned || dockMouseArea.containsMouse || dockApps.requestDockShow
active: ConfigOptions?.dock.hoverToReveal || (!ToplevelManager.activeToplevel?.activated)
anchors {
bottom: true
left: true
right: true
}
sourceComponent: PanelWindow { // Window
id: dockRoot
screen: dockLoader.modelData
property bool reveal: root.pinned
|| (ConfigOptions?.dock.hoverToReveal && dockMouseArea.containsMouse)
|| dockApps.requestDockShow
|| (!ToplevelManager.activeToplevel?.activated)
function hide() {
cheatsheetLoader.active = false
}
exclusiveZone: root.pinned ? implicitHeight - Appearance.sizes.hyprlandGapsOut : 0
implicitWidth: dockBackground.implicitWidth
WlrLayershell.namespace: "quickshell:dock"
color: "transparent"
implicitHeight: (ConfigOptions?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut
mask: Region {
item: dockMouseArea
}
MouseArea {
id: dockMouseArea
anchors.top: parent.top
height: parent.height
anchors.topMargin: dockRoot.reveal ? 0 : dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight
anchors.left: parent.left
anchors.right: parent.right
hoverEnabled: true
Behavior on anchors.topMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
anchors {
bottom: true
left: true
right: true
}
Item {
id: dockHoverRegion
anchors.fill: parent
exclusiveZone: root.pinned ? implicitHeight
- (Appearance.sizes.hyprlandGapsOut)
- (Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut) : 0
implicitWidth: dockBackground.implicitWidth
WlrLayershell.namespace: "quickshell:dock"
color: "transparent"
implicitHeight: (ConfigOptions?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut
mask: Region {
item: dockMouseArea
}
MouseArea {
id: dockMouseArea
anchors.top: parent.top
height: parent.height
anchors.topMargin: dockRoot.reveal ? 0 :
ConfigOptions?.dock.hoverToReveal ? (dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight) :
(dockRoot.implicitHeight + 1)
anchors.left: parent.left
anchors.right: parent.right
hoverEnabled: true
Behavior on anchors.topMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Item {
id: dockBackground
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
id: dockHoverRegion
anchors.fill: parent
implicitWidth: dockRow.implicitWidth + 5 * 2
height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
Item { // Wrapper for the dock background
id: dockBackground
anchors {
top: parent.top
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
StyledRectangularShadow {
target: dockVisualBackground
}
Rectangle {
id: dockVisualBackground
property real margin: Appearance.sizes.elevationMargin
anchors.fill: parent
anchors.topMargin: margin
anchors.bottomMargin: margin
color: Appearance.colors.colLayer0
radius: Appearance.rounding.large
}
implicitWidth: dockRow.implicitWidth + 5 * 2
height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
RowLayout {
id: dockRow
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: 3
property real padding: 5
StyledRectangularShadow {
target: dockVisualBackground
}
Rectangle { // The real rectangle that is visible
id: dockVisualBackground
property real margin: Appearance.sizes.elevationMargin
anchors.fill: parent
anchors.topMargin: Appearance.sizes.elevationMargin
anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut
color: Appearance.colors.colLayer0
radius: Appearance.rounding.large
}
VerticalButtonGroup {
GroupButton { // Pin button
baseWidth: 35
baseHeight: 35
clickedWidth: baseWidth
clickedHeight: baseHeight + 20
buttonRadius: Appearance.rounding.normal
toggled: root.pinned
onClicked: root.pinned = !root.pinned
RowLayout {
id: dockRow
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: 3
property real padding: 5
VerticalButtonGroup {
Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work
GroupButton { // Pin button
baseWidth: 35
baseHeight: 35
clickedWidth: baseWidth
clickedHeight: baseHeight + 20
buttonRadius: Appearance.rounding.normal
toggled: root.pinned
onClicked: root.pinned = !root.pinned
contentItem: MaterialSymbol {
text: "keep"
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0
}
}
}
DockSeparator {}
DockApps { id: dockApps; }
DockSeparator {}
DockButton {
Layout.fillHeight: true
onClicked: Hyprland.dispatch("global quickshell:overviewToggle")
contentItem: MaterialSymbol {
text: "keep"
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0
font.pixelSize: parent.width / 2
text: "apps"
color: Appearance.colors.colOnLayer0
}
}
}
DockSeparator {}
DockApps { id: dockApps }
DockSeparator {}
DockButton {
onClicked: Hyprland.dispatch("global quickshell:overviewToggle")
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
font.pixelSize: parent.width / 2
text: "apps"
color: Appearance.colors.colOnLayer0
}
}
}
}
}
}
}
}
}
}
}
@@ -2,6 +2,7 @@ import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
@@ -13,32 +14,102 @@ import Quickshell.Wayland
import Quickshell.Hyprland
DockButton {
id: appButton
required property var appToplevel
id: root
property var appToplevel
property var appListRoot
property int lastFocused: -1
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onEntered: {
appListRoot.lastHoveredButton = appButton
appListRoot.buttonHovered = true
lastFocused = appToplevel.toplevels.length - 1
property real iconSize: 35
property real countDotWidth: 10
property real countDotHeight: 4
property bool appIsActive: appToplevel.toplevels.find(t => (t.activated == true)) !== undefined
property bool isSeparator: appToplevel.appId === "SEPARATOR"
property var desktopEntry: DesktopEntries.byId(appToplevel.appId)
enabled: !isSeparator
implicitWidth: isSeparator ? 1 : implicitHeight - topInset - bottomInset
Loader {
active: isSeparator
anchors {
fill: parent
topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
}
onExited: {
if (appListRoot.lastHoveredButton === appButton) {
appListRoot.buttonHovered = false
sourceComponent: DockSeparator {}
}
Loader {
anchors.fill: parent
active: appToplevel.toplevels.length > 0
sourceComponent: MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onEntered: {
appListRoot.lastHoveredButton = root
appListRoot.buttonHovered = true
lastFocused = appToplevel.toplevels.length - 1
}
onExited: {
if (appListRoot.lastHoveredButton === root) {
appListRoot.buttonHovered = false
}
}
}
}
onClicked: {
if (appToplevel.toplevels.length === 0) {
root.desktopEntry?.execute();
return;
}
lastFocused = (lastFocused + 1) % appToplevel.toplevels.length
appToplevel.toplevels[lastFocused].activate()
}
contentItem: IconImage {
id: iconImage
source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing")
middleClickAction: () => {
root.desktopEntry?.execute();
}
contentItem: Loader {
active: !isSeparator
sourceComponent: Item {
anchors.centerIn: parent
Loader {
id: iconImageLoader
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
active: !root.isSeparator
sourceComponent: IconImage {
source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing")
implicitSize: root.iconSize
}
}
RowLayout {
spacing: 3
anchors {
top: iconImageLoader.bottom
topMargin: 2
horizontalCenter: parent.horizontalCenter
}
Repeater {
model: Math.min(appToplevel.toplevels.length, 3)
delegate: Rectangle {
required property int index
radius: Appearance.rounding.full
implicitWidth: (appToplevel.toplevels.length <= 3) ?
root.countDotWidth : root.countDotHeight // Circles when too many
implicitHeight: root.countDotHeight
color: appIsActive ? Appearance.colors.colPrimary : ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.4)
}
}
}
}
}
}
+60 -37
View File
@@ -23,40 +23,66 @@ Item {
property Item lastHoveredButton
property bool buttonHovered: false
property bool requestDockShow: previewPopup.show
property real popupX: parentWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, root.lastHoveredButton.height / 2).x - implicitWidth / 2
implicitWidth: rowLayout.implicitWidth
implicitHeight: rowLayout.implicitHeight
RowLayout {
id: rowLayout
Layout.fillHeight: true
Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work
implicitWidth: listView.implicitWidth
StyledListView {
id: listView
spacing: 2
orientation: ListView.Horizontal
anchors {
top: parent.top
bottom: parent.bottom
}
implicitWidth: contentWidth
Repeater {
model: ScriptModel {
objectProp: "appId"
values: {
var map = new Map();
Behavior on implicitWidth {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
for (const toplevel of ToplevelManager.toplevels.values) {
if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), []);
map.get(toplevel.appId.toLowerCase()).push(toplevel);
}
model: ScriptModel {
objectProp: "appId"
values: {
var map = new Map();
var values = [];
for (const [key, value] of map) {
values.push({ appId: key, toplevels: value });
}
return values;
// Pinned apps
const pinnedApps = ConfigOptions?.dock.pinnedApps ?? [];
for (const appId of pinnedApps) {
if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({
pinned: true,
toplevels: []
}));
}
// Separator
if (pinnedApps.length > 0) {
map.set("SEPARATOR", { pinned: false, toplevels: [] });
}
// Open windows
for (const toplevel of ToplevelManager.toplevels.values) {
if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), ({
pinned: false,
toplevels: []
}));
map.get(toplevel.appId.toLowerCase()).toplevels.push(toplevel);
}
var values = [];
for (const [key, value] of map) {
values.push({ appId: key, toplevels: value.toplevels, pinned: value.pinned });
}
return values;
}
delegate: DockAppButton {
required property var modelData
appToplevel: modelData
appListRoot: root
}
}
delegate: DockAppButton {
required property var modelData
appToplevel: modelData
appListRoot: root
}
}
@@ -118,14 +144,9 @@ Item {
anchors.bottom: parent.bottom
implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
implicitHeight: root.maxWindowPreviewHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2
// anchors.horizontalCenter: parent.horizontalCenter
hoverEnabled: true
// x: previewPopup.width / 2 + root.popupX
// Behavior on x {
// animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
// }
x: {
const itemCenter = root.QsWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, 0);
const itemCenter = root.QsWindow?.mapFromItem(root.lastHoveredButton, root.lastHoveredButton?.width / 2, 0);
return itemCenter.x - width / 2
}
StyledRectangularShadow {
@@ -145,7 +166,7 @@ Item {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
clip: true
color: Appearance.m3colors.m3surfaceContainer
color: Appearance.colors.colSurfaceContainer
radius: Appearance.rounding.normal
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.sizes.elevationMargin
@@ -163,7 +184,9 @@ Item {
id: previewRowLayout
anchors.centerIn: parent
Repeater {
model: previewPopup.appTopLevel?.toplevels ?? []
model: ScriptModel {
values: previewPopup.appTopLevel?.toplevels ?? []
}
RippleButton {
id: windowButton
required property var modelData
@@ -182,7 +205,7 @@ Item {
contentWidth: parent.width - anchors.margins * 2
WrapperRectangle {
Layout.fillWidth: true
color: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer)
color: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
radius: Appearance.rounding.small
margin: 5
StyledText {
@@ -195,7 +218,7 @@ Item {
}
GroupButton {
id: closeButton
colBackground: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer)
colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
baseWidth: windowControlsHeight
baseHeight: windowControlsHeight
buttonRadius: Appearance.rounding.full
@@ -7,9 +7,10 @@ import QtQuick.Layouts
RippleButton {
Layout.fillHeight: true
Layout.topMargin: Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
implicitWidth: implicitHeight - topInset - bottomInset
buttonRadius: Appearance.rounding.normal
topInset: dockVisualBackground.margin + dockRow.padding
bottomInset: dockVisualBackground.margin + dockRow.padding
topInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding
bottomInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding
}
@@ -5,9 +5,9 @@ import QtQuick.Controls
import QtQuick.Layouts
Rectangle {
Layout.topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
Layout.bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
Layout.topMargin: Appearance.sizes.elevationMargin + dockRow.padding + Appearance.rounding.normal
Layout.bottomMargin: Appearance.sizes.hyprlandGapsOut + dockRow.padding + Appearance.rounding.normal
Layout.fillHeight: true
implicitWidth: 1
color: Appearance.m3colors.m3outlineVariant
color: Appearance.colors.colOutlineVariant
}
@@ -26,6 +26,7 @@ Scope {
property real contentPadding: 13
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
property real artRounding: Appearance.rounding.verysmall
property list<real> visualizerPoints: []
property bool hasPlasmaIntegration: false
function isRealPlayer(player) {
@@ -53,7 +54,7 @@ Scope {
for (let j = i + 1; j < players.length; ++j) {
let p2 = players[j];
if (p1.trackTitle && p2.trackTitle &&
(p1.trackTitle.startsWith(p2.trackTitle) || p2.trackTitle.startsWith(p1.trackTitle))) {
(p1.trackTitle.includes(p2.trackTitle) || p2.trackTitle.includes(p1.trackTitle))) {
group.push(j);
}
}
@@ -68,13 +69,31 @@ Scope {
return filtered;
}
Process {
id: cavaProc
running: mediaControlsLoader.active
onRunningChanged: {
if (!cavaProc.running) {
root.visualizerPoints = [];
}
}
command: ["cava", "-p", `${FileUtils.trimFileProtocol(Directories.config)}/quickshell/scripts/cava/raw_output_config.txt`]
stdout: SplitParser {
onRead: data => {
// Parse `;`-separated values into the visualizerPoints array
let points = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p));
root.visualizerPoints = points;
}
}
}
Loader {
id: mediaControlsLoader
active: false
sourceComponent: PanelWindow {
id: mediaControlsRoot
visible: mediaControlsLoader.active
visible: true
exclusiveZone: 0
implicitWidth: (
@@ -112,6 +131,7 @@ Scope {
delegate: PlayerControl {
required property MprisPlayer modelData
player: modelData
visualizerPoints: root.visualizerPoints
}
}
}
@@ -25,6 +25,9 @@ Item { // Player instance
property string artFilePath: `${artDownloadLocation}/${artFileName}`
property color artDominantColor: colorQuantizer?.colors[0] || Appearance.m3colors.m3secondaryContainer
property bool downloaded: false
property list<real> visualizerPoints: []
property real maxVisualizerValue: 1000 // Max value in the data points
property int visualizerSmoothing: 2 // Number of points to average for smoothing
implicitWidth: widgetWidth
implicitHeight: widgetHeight
@@ -150,6 +153,16 @@ Item { // Player instance
}
}
WaveVisualizer {
id: visualizerCanvas
anchors.fill: parent
live: playerController.player?.isPlaying
points: playerController.visualizerPoints
maxVisualizerValue: playerController.maxVisualizerValue
smoothing: playerController.visualizerSmoothing
color: blendedColors.colPrimary
}
RowLayout {
anchors.fill: parent
anchors.margins: root.contentPadding
@@ -160,7 +173,7 @@ Item { // Player instance
Layout.fillHeight: true
implicitWidth: height
radius: root.artRounding
color: blendedColors.colLayer1
color: ColorUtils.transparentize(blendedColors.colLayer1, 0.5)
layer.enabled: true
layer.effect: OpacityMask {
@@ -235,12 +248,18 @@ Item { // Player instance
iconName: "skip_previous"
onClicked: playerController.player?.previous()
}
StyledProgressBar {
id: slider
Item {
id: progressBarContainer
Layout.fillWidth: true
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length
implicitHeight: progressBar.implicitHeight
StyledProgressBar {
id: progressBar
anchors.fill: parent
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length
}
}
TrackChangeButton {
iconName: "skip_next"
@@ -15,7 +15,7 @@ Scope {
PanelWindow {
id: root
visible: (Notifications.popupList.length > 0)
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? null
WlrLayershell.namespace: "quickshell:notificationPopup"
WlrLayershell.layer: WlrLayer.Overlay
@@ -73,7 +73,7 @@ Scope {
item: osdValuesWrapper
}
implicitWidth: Appearance.sizes.osdWidth
implicitWidth: columnLayout.implicitWidth
implicitHeight: columnLayout.implicitHeight
visible: osdLoader.active
@@ -12,6 +12,7 @@ import Quickshell.Hyprland
Scope {
id: root
property bool showOsdValues: false
property string protectionMessage: ""
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
function triggerOsd() {
@@ -25,7 +26,8 @@ Scope {
repeat: false
running: false
onTriggered: {
showOsdValues = false
root.showOsdValues = false
root.protectionMessage = ""
}
}
@@ -36,7 +38,7 @@ Scope {
}
}
Connections {
Connections { // Listen to volume changes
target: Audio.sink?.audio ?? null
function onVolumeChanged() {
if (!Audio.ready) return
@@ -48,6 +50,14 @@ Scope {
}
}
Connections { // Listen to protection triggers
target: Audio
function onSinkProtectionTriggered(reason) {
root.protectionMessage = reason;
root.triggerOsd()
}
}
Loader {
id: osdLoader
active: showOsdValues
@@ -75,7 +85,7 @@ Scope {
item: osdValuesWrapper
}
implicitWidth: Appearance.sizes.osdWidth
implicitWidth: columnLayout.implicitWidth
implicitHeight: columnLayout.implicitHeight
visible: osdLoader.active
@@ -85,8 +95,8 @@ Scope {
Item {
id: osdValuesWrapper
// Extra space for shadow
implicitHeight: osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2
implicitWidth: osdValues.implicitWidth
implicitHeight: contentColumnLayout.implicitHeight + Appearance.sizes.elevationMargin * 2
implicitWidth: contentColumnLayout.implicitWidth
clip: true
MouseArea {
@@ -95,20 +105,63 @@ Scope {
onEntered: root.showOsdValues = false
}
Behavior on implicitHeight {
NumberAnimation {
duration: Appearance.animation.menuDecel.duration
easing.type: Appearance.animation.menuDecel.type
ColumnLayout {
id: contentColumnLayout
anchors {
top: parent.top
left: parent.left
right: parent.right
leftMargin: Appearance.sizes.elevationMargin
rightMargin: Appearance.sizes.elevationMargin
}
}
spacing: 0
OsdValueIndicator {
id: osdValues
anchors.fill: parent
anchors.margins: Appearance.sizes.elevationMargin
value: Audio.sink?.audio.volume ?? 0
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
name: qsTr("Volume")
OsdValueIndicator {
id: osdValues
Layout.fillWidth: true
value: Audio.sink?.audio.volume ?? 0
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
name: qsTr("Volume")
}
Item {
id: protectionMessageWrapper
implicitHeight: protectionMessageBackground.implicitHeight
implicitWidth: protectionMessageBackground.implicitWidth
Layout.alignment: Qt.AlignHCenter
opacity: root.protectionMessage !== "" ? 1 : 0
StyledRectangularShadow {
target: protectionMessageBackground
}
Rectangle {
id: protectionMessageBackground
anchors.centerIn: parent
color: Appearance.m3colors.m3error
property real padding: 10
implicitHeight: protectionMessageRowLayout.implicitHeight + padding * 2
implicitWidth: protectionMessageRowLayout.implicitWidth + padding * 2
radius: Appearance.rounding.normal
RowLayout {
id: protectionMessageRowLayout
anchors.centerIn: parent
MaterialSymbol {
id: protectionMessageIcon
text: "dangerous"
iconSize: Appearance.font.pixelSize.hugeass
color: Appearance.m3colors.m3onError
}
StyledText {
id: protectionMessageTextWidget
horizontalAlignment: Text.AlignHCenter
color: Appearance.m3colors.m3onError
wrapMode: Text.Wrap
text: root.protectionMessage
}
}
}
}
}
}
}
@@ -111,7 +111,7 @@ Scope { // Scope
Layout.bottomMargin: 20
Layout.fillHeight: true
implicitWidth: 1
color: Appearance.m3colors.m3outlineVariant
color: Appearance.colors.colOutlineVariant
}
OskContent {
id: oskContent
@@ -92,8 +92,8 @@ Scope {
visible: GlobalStates.overviewOpen
anchors {
horizontalCenter: parent.horizontalCenter
top: !ConfigOptions.bar.bottom ? parent.top : null
bottom: ConfigOptions.bar.bottom ? parent.bottom : null
top: !ConfigOptions.bar.bottom ? parent.top : undefined
bottom: ConfigOptions.bar.bottom ? parent.bottom : undefined
}
Keys.onPressed: (event) => {
@@ -25,7 +25,7 @@ Item {
property var windowAddresses: HyprlandData.addresses
property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id)
property real scale: ConfigOptions.overview.scale
property color activeBorderColor: Appearance.m3colors.m3secondary
property color activeBorderColor: Appearance.colors.colSecondary
property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ?
((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) :
@@ -144,25 +144,38 @@ Item {
implicitHeight: workspaceColumnLayout.implicitHeight
Repeater { // Window repeater
model: windowAddresses.filter((address) => {
var win = windowByAddress[address]
return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
})
model: ScriptModel {
values: {
// console.log(JSON.stringify(ToplevelManager.toplevels.values.map(t => t), null, 2))
return ToplevelManager.toplevels.values.filter((toplevel) => {
const address = `0x${toplevel.HyprlandToplevel.address}`
// console.log(`Checking window with address: ${address}`)
var win = windowByAddress[address]
return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
})
}
}
delegate: OverviewWindow {
id: window
windowData: windowByAddress[modelData]
required property var modelData
property var address: `0x${modelData.HyprlandToplevel.address}`
windowData: windowByAddress[address]
toplevel: modelData
monitorData: root.monitorData
scale: root.scale
availableWorkspaceWidth: root.workspaceImplicitWidth
availableWorkspaceHeight: root.workspaceImplicitHeight
property int monitorId: windowData?.monitor
property var monitor: HyprlandData.monitors[monitorId]
property bool atInitPosition: (initX == x && initY == y)
restrictToWorkspace: Drag.active || atInitPosition
property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols)
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - (monitor?.x * root.scale)
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - (monitor?.y * root.scale)
Timer {
id: updateWindowPosition
@@ -170,8 +183,9 @@ Item {
repeat: false
running: false
onTriggered: {
window.x = Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset
window.y = Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset
window.x = Math.round(Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset)
window.y = Math.round(Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset)
console.log(`[OverviewWindow] Updated position for window ${windowData?.address} to (${window.x}, ${window.y})`)
}
}
@@ -191,6 +205,7 @@ Item {
window.pressed = true
window.Drag.active = true
window.Drag.source = window
console.log(`[OverviewWindow] Dragging window ${windowData?.address} from position (${window.x}, ${window.y})`)
}
onReleased: {
const targetWorkspace = root.draggingTargetWorkspace
@@ -1,17 +1,21 @@
import "root:/"
import "root:/services/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
Rectangle { // Window
Item { // Window
id: root
property var toplevel
property var windowData
property var monitorData
property var scale
@@ -38,14 +42,17 @@ Rectangle { // Window
x: initX
y: initY
width: Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset))
height: Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset))
width: Math.round(Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset)))
height: Math.round(Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset)))
radius: Appearance.rounding.windowRounding * root.scale
color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2
border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9)
border.pixelAligned : false
border.width : 1
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: root.width
height: root.height
radius: Appearance.rounding.windowRounding * root.scale
}
}
Behavior on x {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
@@ -60,34 +67,45 @@ Rectangle { // Window
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
ColumnLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.font.pixelSize.smaller * 0.5
ScreencopyView {
id: windowPreview
anchors.fill: parent
captureSource: GlobalStates.overviewOpen ? root.toplevel : null
live: true
IconImage {
id: windowIcon
Layout.alignment: Qt.AlignHCenter
source: root.iconPath
implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
Behavior on implicitSize {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Rectangle {
anchors.fill: parent
radius: Appearance.rounding.windowRounding * root.scale
color: pressed ? ColorUtils.transparentize(Appearance.colors.colLayer2Active, 0.5) :
hovered ? ColorUtils.transparentize(Appearance.colors.colLayer2Hover, 0.7) :
ColorUtils.transparentize(Appearance.colors.colLayer2)
border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.7)
border.width : 1
}
StyledText {
Layout.leftMargin: 10
Layout.rightMargin: 10
visible: !compactMode
Layout.fillWidth: true
Layout.fillHeight: true
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Appearance.font.pixelSize.smaller
font.italic: indicateXWayland ? true : false
elide: Text.ElideRight
text: windowData?.title ?? ""
ColumnLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.font.pixelSize.smaller * 0.5
Image {
id: windowIcon
property var iconSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
// mipmap: true
Layout.alignment: Qt.AlignHCenter
source: root.iconPath
width: iconSize
height: iconSize
sourceSize: Qt.size(iconSize, iconSize)
Behavior on width {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Behavior on height {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
}
}
}
}
@@ -80,7 +80,7 @@ RippleButton {
buttonRadius: Appearance.rounding.normal
colBackground: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active :
((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover :
ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1))
ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHigh, 1))
colBackgroundHover: Appearance.colors.colLayer1Hover
colRipple: Appearance.colors.colLayer1Active
@@ -219,7 +219,7 @@ Item { // Wrapper
}
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
placeholderText: qsTr("Search, calculate or run")
placeholderTextColor: Appearance.m3colors.m3outline
implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth
@@ -260,7 +260,7 @@ Item { // Wrapper
visible: root.showResults
Layout.fillWidth: true
height: 1
color: Appearance.m3colors.m3outlineVariant
color: Appearance.colors.colOutlineVariant
}
ListView { // App results
@@ -5,6 +5,7 @@ import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
Scope {
id: screenCorners
@@ -14,7 +15,9 @@ Scope {
model: Quickshell.screens
PanelWindow {
visible: (ConfigOptions.appearance.fakeScreenRounding === 1 || (ConfigOptions.appearance.fakeScreenRounding === 2 && !activeWindow?.fullscreen))
visible: (ConfigOptions.appearance.fakeScreenRounding === 1
|| (ConfigOptions.appearance.fakeScreenRounding === 2
&& !activeWindow?.fullscreen))
property var modelData
@@ -23,6 +26,20 @@ Scope {
mask: Region {
item: null
}
HyprlandWindow.visibleMask: Region {
Region {
item: topLeftCorner
}
Region {
item: topRightCorner
}
Region {
item: bottomLeftCorner
}
Region {
item: bottomRightCorner
}
}
WlrLayershell.namespace: "quickshell:screenCorners"
WlrLayershell.layer: WlrLayer.Overlay
color: "transparent"
@@ -35,24 +52,28 @@ Scope {
}
RoundCorner {
id: topLeftCorner
anchors.top: parent.top
anchors.left: parent.left
size: Appearance.rounding.screenRounding
corner: cornerEnum.topLeft
}
RoundCorner {
id: topRightCorner
anchors.top: parent.top
anchors.right: parent.right
size: Appearance.rounding.screenRounding
corner: cornerEnum.topRight
}
RoundCorner {
id: bottomLeftCorner
anchors.bottom: parent.bottom
anchors.left: parent.left
size: Appearance.rounding.screenRounding
corner: cornerEnum.bottomLeft
}
RoundCorner {
id: bottomRightCorner
anchors.bottom: parent.bottom
anchors.right: parent.right
size: Appearance.rounding.screenRounding
@@ -18,7 +18,7 @@ RippleButton {
buttonRadius: (button.focus || button.down) ? size / 2 : Appearance.rounding.verylarge
colBackground: button.keyboardDown ? Appearance.colors.colSecondaryContainerActive :
button.focus ? Appearance.colors.colPrimary :
Appearance.m3colors.m3secondaryContainer
Appearance.colors.colSecondaryContainer
colBackgroundHover: Appearance.colors.colPrimary
colRipple: Appearance.colors.colPrimaryActive
property color colText: (button.down || button.keyboardDown || button.focus || button.hovered) ?
@@ -125,9 +125,14 @@ int main(int argc, char* argv[]) {
### LaTeX
- Simple inline: $\\frac{1}{2} = \\frac{2}{4}$
- Complex inline: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$
- Another complex inline: \\\\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\\\]
Inline w/ dollar signs: $\\frac{1}{2} = \\frac{2}{4}$
Inline w/ double dollar signs: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$
Inline w/ backslash and square brackets \\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\]
Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
`,
Ai.interfaceRole);
}
@@ -162,6 +167,7 @@ int main(int argc, char* argv[]) {
id: messageListView
anchors.fill: parent
spacing: 10
popin: false
property int lastResponseLength: 0
@@ -175,6 +181,8 @@ int main(int argc, char* argv[]) {
}
}
add: null // Prevent function calls from being janky
Behavior on contentY {
NumberAnimation {
id: scrollAnim
@@ -337,7 +345,7 @@ int main(int argc, char* argv[]) {
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
clip: true
border.color: Appearance.m3colors.m3outlineVariant
border.color: Appearance.colors.colOutlineVariant
border.width: 1
Behavior on implicitHeight {
@@ -359,7 +359,7 @@ Item {
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
clip: true
border.color: Appearance.m3colors.m3outlineVariant
border.color: Appearance.colors.colOutlineVariant
border.width: 1
Behavior on implicitHeight {
@@ -562,8 +562,10 @@ Item {
text: "•"
}
Rectangle { // NSFW toggle
Item { // NSFW toggle
visible: width > 0
implicitWidth: switchesRow.implicitWidth
Layout.fillHeight: true
RowLayout {
id: switchesRow
@@ -97,7 +97,7 @@ Scope { // Scope
anchors.left: parent.left
anchors.topMargin: Appearance.sizes.hyprlandGapsOut
anchors.leftMargin: Appearance.sizes.hyprlandGapsOut
width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
color: Appearance.colors.colLayer0
radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
@@ -3,6 +3,7 @@ import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "./translator/"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -15,11 +16,24 @@ import Quickshell.Hyprland
*/
Item {
id: root
property var inputField: inputTextArea
property var outputField: outputTextArea
// Widgets
property var inputField: inputCanvas.inputTextArea
// Widget variables
property bool translationFor: false // Indicates if the translation is for an autocorrected text
property string translatedText: ""
property list<string> languages: []
// Options
property string targetLanguage: ConfigOptions.language.translator.targetLanguage
property string sourceLanguage: ConfigOptions.language.translator.sourceLanguage
property string hostLanguage: targetLanguage
property bool showLanguageSelector: false
property bool languageSelectorTarget: false // true for target language, false for source language
function showLanguageSelectorDialog(isTargetLang: bool) {
root.languageSelectorTarget = isTargetLang;
root.showLanguageSelector = true
}
onFocusChanged: (focus) => {
if (focus) {
@@ -32,19 +46,23 @@ Item {
interval: ConfigOptions.sidebar.translator.delay
repeat: false
onTriggered: () => {
if (inputTextArea.text.trim().length > 0) {
if (root.inputField.text.trim().length > 0) {
// console.log("Translating with command:", translateProc.command);
translateProc.running = false;
translateProc.buffer = ""; // Clear the buffer
translateProc.running = true; // Restart the process
} else {
outputTextArea.text = "";
root.translatedText = "";
}
}
}
Process {
id: translateProc
command: ["bash", "-c", `trans -no-theme -no-ansi '${StringUtils.shellSingleQuoteEscape(inputTextArea.text.trim())}'`]
command: ["bash", "-c", `trans -no-theme`
+ ` -source '${StringUtils.shellSingleQuoteEscape(root.sourceLanguage)}'`
+ ` -target '${StringUtils.shellSingleQuoteEscape(root.targetLanguage)}'`
+ ` -no-ansi '${StringUtils.shellSingleQuoteEscape(root.inputField.text.trim())}'`]
property string buffer: ""
stdout: SplitParser {
onRead: data => {
@@ -59,171 +77,172 @@ Item {
// 2. Extract relevant data
root.translatedText = sections.length > 1 ? sections[1].trim() : "";
root.outputField.text = root.translatedText;
}
}
Flickable {
Process {
id: getLanguagesProc
command: ["trans", "-list-languages"]
property list<string> bufferList: ["auto"]
running: true
stdout: SplitParser {
onRead: data => {
getLanguagesProc.bufferList.push(data.trim());
}
}
onExited: (exitCode, exitStatus) => {
// Ensure "auto" is always the first language
let langs = getLanguagesProc.bufferList
.filter(lang => lang.trim().length > 0 && lang !== "auto")
.sort((a, b) => a.localeCompare(b));
langs.unshift("auto");
root.languages = langs;
getLanguagesProc.bufferList = []; // Clear the buffer
}
}
ColumnLayout {
anchors.fill: parent
contentHeight: contentColumn.implicitHeight
Flickable {
Layout.fillWidth: true
Layout.fillHeight: true
contentHeight: contentColumn.implicitHeight
ColumnLayout {
id: contentColumn
anchors.fill: parent
ColumnLayout {
id: contentColumn
anchors.fill: parent
Rectangle { // INPUT
id: inputCanvas
Layout.fillWidth: true
implicitHeight: Math.max(150, inputColumn.implicitHeight)
color: Appearance.colors.colLayer1
radius: Appearance.rounding.normal
border.color: Appearance.m3colors.m3outlineVariant
border.width: 1
LanguageSelectorButton { // Target language button
id: targetLanguageButton
displayText: root.targetLanguage
onClicked: {
root.showLanguageSelectorDialog(true);
}
}
ColumnLayout {
id: inputColumn
anchors.fill: parent
spacing: 0
StyledTextArea { // Input area
id: inputTextArea
Layout.fillWidth: true
placeholderText: qsTr("Enter text to translate...")
wrapMode: TextEdit.Wrap
textFormat: TextEdit.PlainText
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnLayer1
padding: 15
background: null
onTextChanged: {
if (inputTextArea.text.trim().length > 0) {
translateTimer.restart();
} else {
outputTextArea.text = "";
}
TextCanvas { // Content translation
id: outputCanvas
isInput: false
placeholderText: qsTr("Translation goes here...")
property bool hasTranslation: (root.translatedText.trim().length > 0)
text: hasTranslation ? root.translatedText : ""
GroupButton {
id: copyButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: outputCanvas.displayedText.trim().length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "content_copy"
color: copyButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
Quickshell.clipboardText = outputCanvas.displayedText
}
}
Item { Layout.fillHeight: true }
RowLayout { // Status row
Layout.fillWidth: true
Layout.margins: 10
spacing: 10
Text {
Layout.leftMargin: 10
text: qsTr("%1 characters").arg(inputTextArea.text.length)
color: Appearance.colors.colOnLayer1
font.pixelSize: Appearance.font.pixelSize.smaller
GroupButton {
id: searchButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: outputCanvas.displayedText.trim().length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "travel_explore"
color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
Item { Layout.fillWidth: true }
ButtonGroup {
GroupButton {
id: pasteButton
baseWidth: height
buttonRadius: Appearance.rounding.small
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "content_paste"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
root.inputField.text = Quickshell.clipboardText
}
}
GroupButton {
id: deleteButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: inputTextArea.text.length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "close"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
root.inputField.text = ""
}
onClicked: {
let url = ConfigOptions.search.engineBaseUrl + outputCanvas.displayedText;
for (let site of ConfigOptions.search.excludedSites) {
url += ` -site:${site}`;
}
Qt.openUrlExternally(url);
}
}
}
}
}
LanguageSelectorButton { // Source language button
id: sourceLanguageButton
displayText: root.sourceLanguage
onClicked: {
root.showLanguageSelectorDialog(false);
}
}
Rectangle { // OUTPUT
id: outputCanvas
Layout.fillWidth: true
implicitHeight: Math.max(150, outputColumn.implicitHeight)
color: Appearance.m3colors.m3surfaceContainer
radius: Appearance.rounding.normal
ColumnLayout { // Output column
id: outputColumn
anchors.fill: parent
spacing: 0
StyledText { // Output area
id: outputTextArea
Layout.fillWidth: true
property bool hasTranslation: (root.translatedText.trim().length > 0)
wrapMode: TextEdit.Wrap
font.pixelSize: Appearance.font.pixelSize.small
color: hasTranslation ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
padding: 15
text: hasTranslation ? root.translatedText : ""
}
Item { Layout.fillHeight: true }
RowLayout { // Status row
Layout.fillWidth: true
Layout.margins: 10
spacing: 10
Item { Layout.fillWidth: true }
ButtonGroup {
GroupButton {
id: copyButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: root.outputField.text.trim().length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "content_copy"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
Quickshell.clipboardText = root.outputField.text
}
}
GroupButton {
id: searchButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: root.outputField.text.trim().length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "travel_explore"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
let url = ConfigOptions.search.engineBaseUrl + root.outputField.text;
for (let site of ConfigOptions.search.excludedSites) {
url += ` -site:${site}`;
}
Qt.openUrlExternally(url);
}
}
}
}
TextCanvas { // Content input
id: inputCanvas
isInput: true
placeholderText: qsTr("Enter text to translate...")
onInputTextChanged: {
translateTimer.restart();
}
GroupButton {
id: pasteButton
baseWidth: height
buttonRadius: Appearance.rounding.small
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "content_paste"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
root.inputField.text = Quickshell.clipboardText
}
}
}
GroupButton {
id: deleteButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: inputCanvas.inputTextArea.text.length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "close"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
root.inputField.text = ""
}
}
}
}
Loader {
anchors.fill: parent
active: root.showLanguageSelector
visible: root.showLanguageSelector
z: 9999
sourceComponent: SelectionDialog {
id: languageSelectorDialog
titleText: qsTr("Select Language")
items: root.languages
defaultChoice: root.languageSelectorTarget ? root.targetLanguage : root.sourceLanguage
onCanceled: () => {
root.showLanguageSelector = false;
}
onSelected: (result) => {
root.showLanguageSelector = false;
if (!result || result.length === 0) return; // No selection made
if (root.languageSelectorTarget) {
root.targetLanguage = result;
ConfigLoader.setConfigValueAndSave("language.translator.targetLanguage", result); // Save to config
} else {
root.sourceLanguage = result;
ConfigLoader.setConfigValueAndSave("language.translator.sourceLanguage", result); // Save to config
}
translateTimer.restart(); // Restart translation after language change
}
}
}
}
@@ -28,6 +28,8 @@ Rectangle {
property bool renderMarkdown: true
property bool editing: false
property list<var> messageBlocks: StringUtils.splitMarkdownBlocks(root.messageData?.content)
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2
@@ -89,7 +91,7 @@ Rectangle {
Rectangle { // Name
id: nameWrapper
color: Appearance.m3colors.m3secondaryContainer
color: Appearance.colors.colSecondaryContainer
// color: "transparent"
radius: Appearance.rounding.small
implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30)
@@ -246,23 +248,25 @@ Rectangle {
spacing: 0
Repeater {
model: ScriptModel {
values: StringUtils.splitMarkdownBlocks(root.messageData?.content)
values: root.messageBlocks.map((block, index) => index)
}
delegate: Loader {
required property int index
property var thisBlock: root.messageBlocks[index]
Layout.fillWidth: true
// property var segment: modelData
property var segmentContent: modelData.content
property var segmentLang: modelData.lang
// property var segment: thisBlock
property var segmentContent: thisBlock.content
property var segmentLang: thisBlock.lang
property var messageData: root.messageData
property var editing: root.editing
property var renderMarkdown: root.renderMarkdown
property var enableMouseSelection: root.enableMouseSelection
property bool thinking: root.messageData?.thinking ?? true
property bool done: root.messageData?.done ?? false
property bool completed: modelData.completed ?? false
property bool completed: thisBlock.completed ?? false
source: modelData.type === "code" ? "MessageCodeBlock.qml" :
modelData.type === "think" ? "MessageThinkBlock.qml" :
source: thisBlock.type === "code" ? "MessageCodeBlock.qml" :
thisBlock.type === "think" ? "MessageThinkBlock.qml" :
"MessageTextBlock.qml"
}
@@ -277,7 +281,9 @@ Rectangle {
Layout.alignment: Qt.AlignLeft
Repeater {
model: root.messageData?.annotationSources
model: ScriptModel {
values: root.messageData?.annotationSources || []
}
delegate: AnnotationSourceButton {
id: annotationButton
displayText: modelData.text
@@ -21,7 +21,7 @@ RippleButton {
leftPadding: (implicitHeight - faviconSize) / 2
rightPadding: 10
buttonRadius: Appearance.rounding.full
colBackground: Appearance.m3colors.m3surfaceContainerHighest
colBackground: Appearance.colors.colSurfaceContainerHighest
colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover
colRipple: Appearance.colors.colSurfaceContainerHighestActive
@@ -40,7 +40,7 @@ ColumnLayout {
topRightRadius: codeBlockBackgroundRounding
bottomLeftRadius: Appearance.rounding.unsharpen
bottomRightRadius: Appearance.rounding.unsharpen
color: Appearance.m3colors.m3surfaceContainerHighest
color: Appearance.colors.colSurfaceContainerHighest
implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2
RowLayout { // Language and buttons
@@ -97,7 +97,7 @@ ColumnLayout {
onClicked: {
const downloadPath = FileUtils.trimFileProtocol(Directories.downloads)
Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(segmentContent)}' > '${downloadPath}/code.${segmentLang || "txt"}'`)
Hyprland.dispatch(`exec notify-send 'Code saved to file' '${downloadPath}/code.${segmentLang || "txt"}'`)
Hyprland.dispatch(`exec notify-send 'Code saved to file' '${downloadPath}/code.${segmentLang || "txt"}' -a Shell`)
saveCodeButton.activated = true
saveIconTimer.restart()
}
@@ -209,7 +209,7 @@ ColumnLayout {
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
font.pixelSize: Appearance.font.pixelSize.small
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
// wrapMode: TextEdit.Wrap
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
@@ -30,20 +30,32 @@ ColumnLayout {
Layout.fillWidth: true
Timer {
id: renderTimer
interval: 1000
repeat: false
onTriggered: {
renderLatex()
for (const hash of renderedLatexHashes) {
handleRenderedLatex(hash, true);
}
}
}
function renderLatex() {
// Regex for $...$, $$...$$, \[...\]
// Note: This is a simple approach and may need refinement for edge cases
let regex = /(\$\$([\s\S]+?)\$\$)|(\$([^\$]+?)\$)|(\\\[((?:.|\n)+?)\\\])/g;
let regex = /(\$\$([\s\S]+?)\$\$)|(\$([^\$]+?)\$)|(\\\[((?:.|\n)+?)\\\])|(\\\(([\s\S]+?)\\\))/g;
let match;
while ((match = regex.exec(segmentContent)) !== null) {
let expression = match[1] || match[2] || match[3];
let expression = match[1] || match[2] || match[3] || match[4] || match[5] || match[6] || match[7] || match[8];
if (expression) {
// Qt.callLater(() => {
// });
Qt.callLater(() => {
const [renderHash, isNew] = LatexRenderer.requestRender(expression.trim());
if (!renderedLatexHashes.includes(renderHash)) {
renderedLatexHashes.push(renderHash);
}
});
}
}
}
@@ -53,16 +65,13 @@ ColumnLayout {
const imagePath = LatexRenderer.renderedImagePaths[hash];
const markdownImage = `![latex](${imagePath})`;
const expression = StringUtils.escapeBackslashes(LatexRenderer.processedExpressions[hash]);
const expression = LatexRenderer.processedExpressions[hash];
renderedSegmentContent = renderedSegmentContent.replace(expression, markdownImage);
}
}
onDoneChanged: {
renderLatex()
for (const hash of renderedLatexHashes) {
handleRenderedLatex(hash, true);
}
renderTimer.restart();
}
onEditingChanged: {
if (!editing) {
@@ -111,7 +120,7 @@ ColumnLayout {
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
font.pixelSize: Appearance.font.pixelSize.small
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
wrapMode: TextEdit.Wrap
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText
@@ -64,7 +64,7 @@ Item {
Rectangle { // Header background
id: header
color: Appearance.m3colors.m3surfaceContainerHighest
color: Appearance.colors.colSurfaceContainerHighest
Layout.fillWidth: true
implicitHeight: thinkBlockTitleBarRowLayout.implicitHeight + thinkBlockHeaderPaddingVertical * 2
@@ -133,7 +133,7 @@ Button {
opacity: root.showActions ? 1 : 0
visible: opacity > 0
radius: Appearance.rounding.small
color: Appearance.m3colors.m3surfaceContainer
color: Appearance.colors.colSurfaceContainer
implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2
implicitWidth: contextMenuColumnLayout.implicitWidth
@@ -67,7 +67,7 @@ Rectangle {
RowLayout { // Header
Rectangle { // Provider name
id: providerNameWrapper
color: Appearance.m3colors.m3secondaryContainer
color: Appearance.colors.colSecondaryContainer
radius: Appearance.rounding.small
implicitWidth: providerName.implicitWidth + 10 * 2
implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30)
@@ -269,7 +269,7 @@ Rectangle {
}
buttonRadius: Appearance.rounding.small
colBackground: Appearance.m3colors.m3surfaceContainerHighest
colBackground: Appearance.colors.colSurfaceContainerHighest
colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover
colRipple: Appearance.colors.colSurfaceContainerHighestActive
@@ -0,0 +1,45 @@
import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
RippleButton {
id: root
property string displayText: ""
colBackground: Appearance.colors.colLayer2
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
implicitHeight: contentItem.implicitHeight + verticalPadding * 2
contentItem: Item {
anchors.centerIn: parent
implicitWidth: languageRow.implicitWidth
implicitHeight: languageText.implicitHeight
RowLayout {
id: languageRow
anchors.centerIn: parent
spacing: 0
StyledText {
id: languageText
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: 5
text: root.displayText
color: Appearance.colors.colOnLayer2
font.pixelSize: Appearance.font.pixelSize.small
}
MaterialSymbol {
Layout.alignment: Qt.AlignVCenter
iconSize: Appearance.font.pixelSize.hugeass
text: "arrow_drop_down"
color: Appearance.colors.colOnLayer2
}
}
}
}
@@ -0,0 +1,92 @@
import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
Rectangle {
id: root
property bool isInput: true // true for input, false for output
property string placeholderText
property string text: ""
property var inputTextArea: isInput ? inputLoader.item : undefined
readonly property string displayedText: isInput ? inputLoader.item.text :
root.text.length > 0 ? outputLoader.item.text : ""
default property alias actionButtons: actions.data
Layout.fillWidth: true
implicitHeight: Math.max(150, inputColumn.implicitHeight)
color: isInput ? Appearance.colors.colLayer1 : Appearance.colors.colSurfaceContainer
radius: Appearance.rounding.normal
border.color: isInput ? Appearance.colors.colOutlineVariant : "transparent"
border.width: isInput ? 1 : 0
signal inputTextChanged(); // Signal emitted when text changes
ColumnLayout {
id: inputColumn
anchors.fill: parent
spacing: 0
Loader {
id: inputLoader
active: root.isInput
visible: root.isInput
Layout.fillWidth: true
sourceComponent: StyledTextArea { // Input area
id: inputTextArea
placeholderText: root.placeholderText
wrapMode: TextEdit.Wrap
textFormat: TextEdit.PlainText
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnLayer1
padding: 15
background: null
onTextChanged: root.inputTextChanged()
}
}
Loader {
id: outputLoader
active: !root.isInput
visible: !root.isInput
Layout.fillWidth: true
sourceComponent: StyledText { // Output area
id: outputTextArea
padding: 15
wrapMode: Text.Wrap
font.pixelSize: Appearance.font.pixelSize.small
color: root.text.length > 0 ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
text: root.text.length > 0 ? root.text : root.placeholderText
}
}
Item { Layout.fillHeight: true }
RowLayout { // Status row
Layout.fillWidth: true
Layout.margins: 10
spacing: 10
Loader {
active: root.isInput
visible: root.isInput
Layout.leftMargin: 10
sourceComponent: Text {
text: qsTr("%1 characters").arg(inputLoader.item.text.length)
color: Appearance.colors.colOnLayer1
font.pixelSize: Appearance.font.pixelSize.smaller
}
}
Item { Layout.fillWidth: true }
ButtonGroup {
id: actions
}
}
}
}
@@ -52,8 +52,17 @@ Scope {
Loader {
id: sidebarContentLoader
active: GlobalStates.sidebarRightOpen
anchors.centerIn: parent
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
anchors {
top: parent.top
bottom: parent.bottom
right: parent.right
left: parent.left
topMargin: Appearance.sizes.hyprlandGapsOut
rightMargin: Appearance.sizes.hyprlandGapsOut
bottomMargin: Appearance.sizes.hyprlandGapsOut
leftMargin: Appearance.sizes.elevationMargin
}
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
sourceComponent: Item {
@@ -118,14 +127,38 @@ Scope {
Layout.fillWidth: true
}
QuickToggleButton {
toggled: false
buttonIcon: "power_settings_new"
onClicked: {
Hyprland.dispatch("global quickshell:sessionOpen")
ButtonGroup {
QuickToggleButton {
toggled: false
buttonIcon: "restart_alt"
onClicked: {
Hyprland.dispatch("reload")
Quickshell.reload(true)
}
StyledToolTip {
content: qsTr("Reload Hyprland & Quickshell")
}
}
StyledToolTip {
content: qsTr("Session")
QuickToggleButton {
toggled: false
buttonIcon: "settings"
onClicked: {
Hyprland.dispatch(`exec ${ConfigOptions.apps.settings}`)
Hyprland.dispatch(`global quickshell:sidebarRightClose`)
}
StyledToolTip {
content: qsTr("Plasma Settings")
}
}
QuickToggleButton {
toggled: false
buttonIcon: "power_settings_new"
onClicked: {
Hyprland.dispatch("global quickshell:sessionOpen")
}
StyledToolTip {
content: qsTr("Session")
}
}
}
}
@@ -26,7 +26,7 @@ RippleButton {
font.weight: bold ? Font.DemiBold : Font.Normal
color: (isToday == 1) ? Appearance.m3colors.m3onPrimary :
(isToday == 0) ? Appearance.colors.colOnLayer1 :
Appearance.m3colors.m3outlineVariant
Appearance.colors.colOutlineVariant
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
@@ -3,16 +3,28 @@ import "root:/modules/common/widgets"
import "../"
import Quickshell.Io
import Quickshell
import Quickshell.Hyprland
QuickToggleButton {
toggled: idleInhibitor.running
id: root
toggled: false
buttonIcon: "coffee"
onClicked: {
idleInhibitor.running = !idleInhibitor.running
if (toggled) {
root.toggled = false
Hyprland.dispatch("exec pkill wayland-idle") // pkill doesn't accept too long names
} else {
root.toggled = true
Hyprland.dispatch('exec ${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py')
}
}
Process {
id: idleInhibitor
command: ["bash", "-c", "${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py"]
id: fetchActiveState
running: true
command: ["bash", "-c", "pidof wayland-idle-inhibitor.py"]
onExited: (exitCode, exitStatus) => {
root.toggled = exitCode === 0
}
}
StyledToolTip {
content: qsTr("Keep system awake")
@@ -15,7 +15,7 @@ QuickToggleButton {
toggleNetwork.running = true
}
altAction: () => {
Hyprland.dispatch(`exec ${ConfigOptions.apps.network}`)
Hyprland.dispatch(`exec ${Network.ethernet ? ConfigOptions.apps.networkEthernet : ConfigOptions.apps.network}`)
Hyprland.dispatch("global quickshell:sidebarRightClose")
}
Process {
@@ -114,7 +114,7 @@ Item {
id: tabBarBottomBorder
Layout.fillWidth: true
height: 1
color: Appearance.m3colors.m3outlineVariant
color: Appearance.colors.colOutlineVariant
}
SwipeView {
@@ -228,7 +228,7 @@ Item {
anchors.margins: root.dialogMargins
implicitHeight: dialogColumnLayout.implicitHeight
color: Appearance.m3colors.m3surfaceContainerHigh
color: Appearance.colors.colSurfaceContainerHigh
radius: Appearance.rounding.normal
function addTask() {
@@ -264,7 +264,7 @@ Item {
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
renderType: Text.NativeRendering
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
placeholderText: qsTr("Task description")
placeholderTextColor: Appearance.m3colors.m3outline
focus: root.showAddDialog
@@ -5,6 +5,7 @@ import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Pipewire
@@ -16,7 +17,7 @@ Item {
property int dialogMargins: 16
property PwNode selectedDevice
function showDeviceSelectorDialog(input) {
function showDeviceSelectorDialog(input: bool) {
root.selectedDevice = null
root.showDeviceSelector = true
root.deviceSelectorInput = input
@@ -160,7 +161,7 @@ Item {
Rectangle { // The dialog
id: dialog
color: Appearance.m3colors.m3surfaceContainerHigh
color: Appearance.colors.colSurfaceContainerHigh
radius: Appearance.rounding.normal
anchors.left: parent.left
anchors.right: parent.right
@@ -207,9 +208,11 @@ Item {
spacing: 0
Repeater {
model: Pipewire.nodes.values.filter(node => {
return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio
})
model: ScriptModel {
values: Pipewire.nodes.values.filter(node => {
return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio
})
}
// This could and should be refractored, but all data becomes null when passed wtf
delegate: StyledRadioButton {