Merge remote-tracking branch 'upstream/main' into fix-dock-first-launch

This commit is contained in:
altrup
2026-03-04 15:01:36 -05:00
165 changed files with 5099 additions and 1437 deletions
@@ -28,15 +28,11 @@ Singleton {
property real autoBackgroundTransparency: { // y = 0.5768x^2 - 0.759x + 0.2896
let x = wallpaperVibrancy
let y = 0.5768 * (x * x) - 0.759 * (x) + 0.2896
return Math.max(0, Math.min(0.22, y))
}
property real autoContentTransparency: { // y = -10.1734x^2 + 3.4457x + 0.1872
let x = autoBackgroundTransparency
let y = -10.1734 * (x * x) + 3.4457 * (x) + 0.1872
return Math.max(0, Math.min(0.6, y))
return Math.max(0, Math.min(0.22, y)) - 0.12 * (m3colors.darkmode ? 0 : 1)
}
property real autoContentTransparency: 0.9
property real backgroundTransparency: Config?.options.appearance.transparency.enable ? Config?.options.appearance.transparency.automatic ? autoBackgroundTransparency : Config?.options.appearance.transparency.backgroundTransparency : 0
property real contentTransparency: Config?.options.appearance.transparency.enable ? Config?.options.appearance.transparency.automatic ? autoContentTransparency : Config?.options.appearance.transparency.contentTransparency : 0
property real contentTransparency: Config?.options.appearance.transparency.automatic ? autoContentTransparency : Config?.options.appearance.transparency.contentTransparency
m3colors: QtObject {
property bool darkmode: true
@@ -114,30 +110,41 @@ Singleton {
colors: QtObject {
property color colSubtext: m3colors.m3outline
property color colLayer0: ColorUtils.mix(ColorUtils.transparentize(m3colors.m3background, root.backgroundTransparency), m3colors.m3primary, Config.options.appearance.extraBackgroundTint ? 0.99 : 1)
// Layer 0
property color colLayer0Base: ColorUtils.mix(m3colors.m3background, m3colors.m3primary, Config.options.appearance.extraBackgroundTint ? 0.99 : 1)
property color colLayer0: ColorUtils.transparentize(colLayer0Base, root.backgroundTransparency)
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 colLayer0Border: ColorUtils.mix(root.m3colors.m3outlineVariant, colLayer0, 0.4)
property color colLayer1: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency);
// Layer 1
property color colLayer1Base: m3colors.m3surfaceContainerLow
property color colLayer1: ColorUtils.solveOverlayColor(colLayer0Base, colLayer1Base, 1 - root.contentTransparency);
property color colOnLayer1: m3colors.m3onSurfaceVariant;
property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45);
property color colLayer2: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
property color colOnLayer2: m3colors.m3onSurface;
property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4);
property color colLayer1Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.92), root.contentTransparency)
property color colLayer1Active: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.85), root.contentTransparency);
property color colLayer2Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.90), root.contentTransparency)
property color colLayer2Active: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.80), root.contentTransparency);
property color colLayer2Disabled: ColorUtils.transparentize(ColorUtils.mix(colLayer2, m3colors.m3background, 0.8), root.contentTransparency);
property color colLayer3: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency)
// Layer 2
property color colLayer2Base: m3colors.m3surfaceContainer
property color colLayer2: ColorUtils.solveOverlayColor(colLayer1Base, colLayer2Base, 1 - root.contentTransparency)
property color colLayer2Hover: ColorUtils.solveOverlayColor(colLayer1Base, ColorUtils.mix(colLayer2Base, colOnLayer2, 0.90), 1 - root.contentTransparency)
property color colLayer2Active: ColorUtils.solveOverlayColor(colLayer1Base, ColorUtils.mix(colLayer2Base, colOnLayer2, 0.80), 1 - root.contentTransparency);
property color colLayer2Disabled: ColorUtils.solveOverlayColor(colLayer1Base, ColorUtils.mix(colLayer2Base, m3colors.m3background, 0.8), 1 - root.contentTransparency);
property color colOnLayer2: m3colors.m3onSurface;
property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4);
// Layer 3
property color colLayer3Base: m3colors.m3surfaceContainerHigh
property color colLayer3: ColorUtils.solveOverlayColor(colLayer2Base, colLayer3Base, 1 - root.contentTransparency)
property color colLayer3Hover: ColorUtils.solveOverlayColor(colLayer2Base, ColorUtils.mix(colLayer3Base, colOnLayer3, 0.90), 1 - root.contentTransparency)
property color colLayer3Active: ColorUtils.solveOverlayColor(colLayer2Base, ColorUtils.mix(colLayer3Base, colOnLayer3, 0.80), 1 - root.contentTransparency);
property color colOnLayer3: m3colors.m3onSurface;
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 colLayer4: ColorUtils.transparentize(m3colors.m3surfaceContainerHighest, root.contentTransparency)
// Layer 4
property color colLayer4Base: m3colors.m3surfaceContainerHighest
property color colLayer4: ColorUtils.solveOverlayColor(colLayer3Base, colLayer4Base, 1 - root.contentTransparency)
property color colLayer4Hover: ColorUtils.solveOverlayColor(colLayer3Base, ColorUtils.mix(colLayer4Base, colOnLayer4, 0.90), 1 - root.contentTransparency)
property color colLayer4Active: ColorUtils.solveOverlayColor(colLayer3Base, ColorUtils.mix(colLayer4Base, colOnLayer4, 0.80), 1 - root.contentTransparency);
property color colOnLayer4: m3colors.m3onSurface;
property color colLayer4Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer4, colOnLayer4, 0.90), root.contentTransparency)
property color colLayer4Active: ColorUtils.transparentize(ColorUtils.mix(colLayer4, colOnLayer4, 0.80), root.contentTransparency);
// Primary
property color colPrimary: m3colors.m3primary
property color colOnPrimary: m3colors.m3onPrimary
property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87)
@@ -146,13 +153,16 @@ Singleton {
property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colors.colOnPrimaryContainer, 0.9)
property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colors.colOnPrimaryContainer, 0.8)
property color colOnPrimaryContainer: m3colors.m3onPrimaryContainer
// Secondary
property color colSecondary: m3colors.m3secondary
property color colOnSecondary: m3colors.m3onSecondary
property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)
property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4)
property color colOnSecondary: m3colors.m3onSecondary
property color colSecondaryContainer: m3colors.m3secondaryContainer
property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, m3colors.m3onSecondaryContainer, 0.90)
property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, m3colors.m3onSecondaryContainer, 0.54)
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
// Tertiary
property color colTertiary: m3colors.m3tertiary
property color colTertiaryHover: ColorUtils.mix(m3colors.m3tertiary, colLayer1Hover, 0.85)
property color colTertiaryActive: ColorUtils.mix(m3colors.m3tertiary, colLayer1Active, 0.4)
@@ -161,16 +171,17 @@ Singleton {
property color colTertiaryContainerActive: ColorUtils.mix(m3colors.m3tertiaryContainer, colLayer1Active, 0.54)
property color colOnTertiary: m3colors.m3onTertiary
property color colOnTertiaryContainer: m3colors.m3onTertiaryContainer
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency)
property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
// Surface
property color colBackgroundSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.backgroundTransparency)
property color colSurfaceContainerHigh: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency)
property color colSurfaceContainerHighest: ColorUtils.transparentize(m3colors.m3surfaceContainerHighest, root.contentTransparency)
property color colSurfaceContainerLow: ColorUtils.solveOverlayColor(m3colors.m3background, m3colors.m3surfaceContainerLow, 1 - root.contentTransparency)
property color colSurfaceContainer: ColorUtils.solveOverlayColor(m3colors.m3surfaceContainerLow, m3colors.m3surfaceContainer, 1 - root.contentTransparency)
property color colSurfaceContainerHigh: ColorUtils.solveOverlayColor(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 1 - root.contentTransparency)
property color colSurfaceContainerHighest: ColorUtils.solveOverlayColor(m3colors.m3surfaceContainerHigh, m3colors.m3surfaceContainerHighest, 1 - 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 colOnSurface: m3colors.m3onSurface
property color colOnSurfaceVariant: m3colors.m3onSurfaceVariant
// Misc
property color colTooltip: m3colors.m3inverseSurface
property color colOnTooltip: m3colors.m3inverseOnSurface
property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)
@@ -78,10 +78,7 @@ Singleton {
JsonAdapter {
id: configOptionsJsonAdapter
property list<string> enabledPanels: [
"iiBar", "iiBackground", "iiCheatsheet", "iiDock", "iiLock", "iiMediaControls", "iiNotificationPopup", "iiOnScreenDisplay", "iiOnScreenKeyboard", "iiOverlay", "iiOverview", "iiPolkit", "iiRegionSelector", "iiReloadPopup", "iiScreenCorners", "iiSessionScreen", "iiSidebarLeft", "iiSidebarRight", "iiVerticalBar", "iiWallpaperSelector"
]
property string panelFamily: "ii" // "ii", "w"
property string panelFamily: "ii" // "ii", "waffle"
property JsonObject policies: JsonObject {
property int ai: 1 // 0: No | 1: Yes | 2: Local
@@ -189,7 +186,17 @@ Singleton {
property bool useSineCookie: false
}
property JsonObject digital: JsonObject {
property bool adaptiveAlignment: true
property bool showDate: true
property bool animateChange: true
property bool vertical: false
property JsonObject font: JsonObject {
property string family: "Google Sans Flex"
property real weight: 350
property real width: 100
property real size: 90
property real roundness: 0
}
}
property JsonObject quote: JsonObject {
property bool enable: false
@@ -566,6 +573,7 @@ Singleton {
}
property JsonObject updates: JsonObject {
property bool enableCheck: true
property int checkInterval: 120 // minutes
property int adviseUpdateThreshold: 75 // packages
property int stronglyAdviseUpdateThreshold: 200 // packages
@@ -59,7 +59,7 @@ Singleton {
property string hyprlandInstanceSignature: ""
property JsonObject ai: JsonObject {
property string model
property string model: "gemini-2.5-flash"
property real temperature: 0.5
}
@@ -135,4 +135,38 @@ Singleton {
var c = Qt.color(color);
return c.hslLightness < 0.5;
}
/**
* Clamps a value to the inclusive range [0, 1].
*
* @param {number} x - The value to clamp.
* @returns {number} The clamped value in the range [0, 1].
*/
function clamp01(x) {
return Math.min(1, Math.max(0, x));
}
/**
* Solves for the solid overlay color that, when composited over a base color
* with a given opacity, yields the target color.
*
* The compositing equation is:
* result = overlay * overlayOpacity + base * (1 - overlayOpacity)
*
* This function algebraically inverts that equation per channel.
*
* @param {Qt.rgba} baseColor - The base (background) color.
* @param {Qt.rgba} targetColor - The resulting color after compositing.
* @param {number} overlayOpacity - The overlay opacity (0-1).
* @returns {Qt.rgba} The solved overlay color
*/
function solveOverlayColor(baseColor, targetColor, overlayOpacity) {
let invA = 1.0 - overlayOpacity;
let r = (targetColor.r - baseColor.r * invA) / overlayOpacity;
let g = (targetColor.g - baseColor.g * invA) / overlayOpacity;
let b = (targetColor.b - baseColor.b * invA) / overlayOpacity;
return Qt.rgba(clamp01(r), clamp01(g), clamp01(b), overlayOpacity);
}
}
@@ -92,7 +92,6 @@ Scope {
WlSessionLock {
id: lock
locked: GlobalStates.screenLocked
surface: root.sessionLockSurface
}
@@ -0,0 +1,81 @@
pragma ComponentBehavior: Bound
pragma Singleton
import qs.modules.common
import qs.modules.common.utils
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.services
import QtQuick
import QtQuick.Controls
import Qt.labs.synchronizer
import Quickshell
Singleton {
id: root
enum Action {
Copy,
Edit,
Search,
CharRecognition,
Record,
RecordWithSound
}
property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl
property string fileUploadApiEndpoint: "https://uguu.se/upload"
function getCommand(x, y, width, height, screenshotPath, action, saveDir = "") {
// Set command for action
const rx = Math.round(x);
const ry = Math.round(y);
const rw = Math.round(width);
const rh = Math.round(height);
const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(screenshotPath)} `
+ `-crop ${rw}x${rh}+${rx}+${ry}`
const cropToStdout = `${cropBase} -`
const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'`
const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'`
const slurpRegion = `${rx},${ry} ${rw}x${rh}`
const uploadAndGetUrl = (filePath) => {
return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'`
}
const annotationCommand = `${Config.options.regionSelector.annotation.useSatty ? "satty" : "swappy"} -f -`;
switch (action) {
case ScreenshotAction.Action.Copy:
if (saveDir === "") {
// not saving the screenshot, just copy to clipboard
return ["bash", "-c", `${cropToStdout} | wl-copy && ${cleanup}`]
break;
}
return [
"bash", "-c",
`mkdir -p '${StringUtils.shellSingleQuoteEscape(saveDir)}' && \
saveFileName="screenshot-$(date '+%Y-%m-%d_%H.%M.%S').png" && \
savePath="${saveDir}/$saveFileName" && \
${cropToStdout} | tee >(wl-copy) > "$savePath" && \
${cleanup}`
]
break;
case ScreenshotAction.Action.Edit:
return ["bash", "-c", `${cropToStdout} | ${annotationCommand} && ${cleanup}`]
break;
case ScreenshotAction.Action.Search:
return ["bash", "-c", `${cropInPlace} && xdg-open "${root.imageSearchEngineBaseUrl}$(${uploadAndGetUrl(screenshotPath)})" && ${cleanup}`]
break;
case ScreenshotAction.Action.CharRecognition:
return ["bash", "-c", `${cropInPlace} && tesseract '${StringUtils.shellSingleQuoteEscape(screenshotPath)}' stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\n' '+' | sed 's/\\+$/\\n/') | wl-copy && ${cleanup}`]
break;
case ScreenshotAction.Action.Record:
return ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}'`]
break;
case ScreenshotAction.Action.RecordWithSound:
return ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}' --sound`]
break;
default:
console.warn("[Region Selector] Unknown snip action, skipping snip.");
return;
}
}
}
@@ -0,0 +1,14 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.modules.common
import qs.modules.common.functions
Process {
id: screenshotProc
running: true
property string screenshotDir: Directories.screenshotTemp
required property ShellScreen screen
property string screenshotPath: `${screenshotDir}/image-${screen.name}`
command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(screen.name)}' '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'`]
}
@@ -0,0 +1,47 @@
import qs.modules.common.widgets
import qs.modules.common
import QtQuick
import QtQuick.Layouts
import qs.services
RowLayout {
id: root
spacing: 10
Layout.leftMargin: 8
Layout.rightMargin: 8
property string text: ""
property string buttonIcon: ""
property alias value: slider.value
property alias stopIndicatorValues: slider.stopIndicatorValues
property bool usePercentTooltip: true
property real from: slider.from
property real to: slider.to
property real textWidth: 120
RowLayout {
id: row
spacing: 10
OptionalMaterialSymbol {
id: iconWidget
icon: root.buttonIcon
iconSize: Appearance.font.pixelSize.larger
}
StyledText {
id: labelWidget
Layout.preferredWidth: root.textWidth
text: root.text
color: Appearance.colors.colOnSecondaryContainer
}
}
StyledSlider {
id: slider
configuration: StyledSlider.Configuration.XS
usePercentTooltip: root.usePercentTooltip
value: root.value
from: root.from
to: root.to
}
}
@@ -0,0 +1,28 @@
import QtQuick
import qs.modules.common
import qs.modules.common.functions
Canvas {
id: root
property color color: "#ffffff"
property int dashLength: 6
property int gapLength: 4
property int borderWidth: 1
onDashLengthChanged: requestPaint()
onGapLengthChanged: requestPaint()
onWidthChanged: requestPaint()
onHeightChanged: requestPaint()
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.strokeStyle = root.color;
ctx.lineWidth = root.borderWidth;
if (root.gapLength > 0) {
ctx.setLineDash([root.dashLength, root.gapLength]); // Set dash pattern
}
ctx.strokeRect(root.borderWidth / 2, root.borderWidth / 2, width - root.borderWidth, height - root.borderWidth); // Draw it
ctx.restore();
}
}
@@ -14,12 +14,16 @@ MouseArea {
property bool automaticallyReset: true
readonly property real dragDiffX: _dragDiffX
readonly property real dragDiffY: _dragDiffY
property real startX: 0
property real startY: 0
property real regionTopLeftX: Math.min(startX, startX + _dragDiffX)
property real regionTopLeftY: Math.min(startY, startY + _dragDiffY)
property real regionWidth: Math.abs(_dragDiffX)
property real regionHeight: Math.abs(_dragDiffY)
signal dragPressed(diffX: real, diffY: real)
signal dragReleased(diffX: real, diffY: real)
property real startX: 0
property real startY: 0
property bool dragging: false
property real _dragDiffX: 0
property real _dragDiffY: 0
@@ -121,7 +121,7 @@ MouseArea { // Notification group area
id: background
anchors.left: parent.left
width: parent.width
color: popup ? ColorUtils.applyAlpha(Appearance.colors.colLayer2, 1 - Appearance.backgroundTransparency) : Appearance.colors.colLayer2
color: popup ? Appearance.colors.colBackgroundSurfaceContainer : Appearance.colors.colLayer2
radius: Appearance.rounding.normal
anchors.leftMargin: root.xOffset
@@ -32,7 +32,7 @@ Item {
Rectangle { // The dialog
id: dialog
color: Appearance.colors.colSurfaceContainerHigh
color: Appearance.m3colors.m3surfaceContainerHigh
radius: Appearance.rounding.normal
anchors.fill: parent
anchors.margins: dialogMargin
@@ -192,7 +192,7 @@ ComboBox {
id: popupBackground
anchors.fill: parent
radius: Appearance.rounding.normal
color: Appearance.colors.colSurfaceContainerHigh
color: Appearance.m3colors.m3surfaceContainerHigh
}
}
@@ -46,7 +46,8 @@ Slider {
property real handleWidth: root.pressed ? handlePressedWidth : handleDefaultWidth
property real handleMargins: 4
property real trackDotSize: 3
property string tooltipContent: `${Math.round(value * 100)}%`
property bool usePercentTooltip: true
property string tooltipContent: usePercentTooltip ? `${Math.round(((value - from) / (to - from)) * 100)}%` : `${Math.round(value)}`
property bool wavy: configuration === StyledSlider.Configuration.Wavy // If true, the progress bar will have a wavy fill effect
property bool animateWave: true
property real waveAmplitudeMultiplier: wavy ? 0.5 : 0
@@ -12,19 +12,30 @@ Item {
required property var tabButtonList
function incrementCurrentIndex() {
tabBar.incrementCurrentIndex()
tabBar.incrementCurrentIndex();
}
function decrementCurrentIndex() {
tabBar.decrementCurrentIndex()
tabBar.decrementCurrentIndex();
}
function setCurrentIndex(index) {
tabBar.setCurrentIndex(index)
tabBar.setCurrentIndex(index);
}
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
implicitWidth: contentItem.implicitWidth
implicitHeight: 40
property Component delegate: ToolbarTabButton {
required property int index
required property var modelData
current: index == root.currentIndex
text: modelData.name
materialSymbol: modelData.icon
onClicked: {
root.setCurrentIndex(index);
}
}
Row {
id: contentItem
z: 1
@@ -33,16 +44,7 @@ Item {
Repeater {
model: root.tabButtonList
delegate: ToolbarTabButton {
required property int index
required property var modelData
current: index == root.currentIndex
text: modelData.name
materialSymbol: modelData.icon
onClicked: {
root.setCurrentIndex(index)
}
}
delegate: root.delegate
}
}
@@ -76,23 +78,23 @@ Item {
z: 2
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
onWheel: (event) => {
onWheel: event => {
if (event.angleDelta.y < 0) {
root.incrementCurrentIndex();
}
else {
} else {
root.decrementCurrentIndex();
}
}
}
// TabBar doesn't allow tabs to be of different sizes. Literally unusable.
// TabBar doesn't allow tabs to be of different sizes. That's what I thought...
// We use it only for the logic and draw stuff manually
TabBar {
id: tabBar
z: -1
background: null
Repeater { // This is to fool the TabBar that it has tabs so it does the indices properly
Repeater {
// This is to fool the TabBar that it has tabs so it does the indices properly
model: root.tabButtonList.length
delegate: TabButton {
background: null
@@ -59,7 +59,8 @@ AbstractWidget {
function onReadyChanged() { refreshPlacementIfNeeded() }
}
function refreshPlacementIfNeeded() {
if (!Config.ready || (root.placementStrategy === "free" && root.needsColText)) return;
if (!Config.ready) return;
if (root.placementStrategy === "free" && !root.needsColText) return;
leastBusyRegionProc.wallpaperPath = root.wallpaperPath;
leastBusyRegionProc.running = false;
leastBusyRegionProc.running = true;
@@ -0,0 +1,19 @@
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Layouts
StyledText {
Layout.fillWidth: true
font {
family: Appearance.font.family.expressive
pixelSize: 20
weight: 350
// Set empty to prevent conflicts, not meaningless
styleName: ""
variableAxes: ({})
}
style: Text.Raised
styleColor: Appearance.colors.colShadow
animateChange: Config.options.background.widgets.clock.digital.animateChange
}
@@ -26,7 +26,7 @@ AbstractBackgroundWidget {
visibleWhenLocked: true
property var textHorizontalAlignment: {
if (root.forceCenter)
if (!Config.options.background.widgets.clock.digital.adaptiveAlignment || root.forceCenter || Config.options.background.widgets.clock.digital.vertical)
return Text.AlignHCenter;
if (root.x < root.scaledScreenWidth / 3)
return Text.AlignLeft;
@@ -63,32 +63,9 @@ AbstractBackgroundWidget {
anchors.horizontalCenter: parent.horizontalCenter
shown: root.clockStyle === "digital" && (root.shouldShow)
fade: false
sourceComponent: ColumnLayout {
id: clockColumn
spacing: 6
ClockText {
font.pixelSize: 90
text: DateTime.time
}
ClockText {
Layout.topMargin: -5
text: DateTime.longDate
}
StyledText {
// Somehow gets fucked up if made a ClockText???
visible: Config.options.background.widgets.clock.quote.enable && Config.options.background.widgets.clock.quote.text.length > 0
Layout.fillWidth: true
horizontalAlignment: root.textHorizontalAlignment
font {
pixelSize: Appearance.font.pixelSize.normal
weight: 350
}
color: root.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: Config.options.background.widgets.clock.quote.text
}
sourceComponent: DigitalClock {
colText: root.colText
textHorizontalAlignment: root.textHorizontalAlignment
}
}
StatusRow {
@@ -154,19 +131,6 @@ AbstractBackgroundWidget {
}
}
component ClockText: StyledText {
Layout.fillWidth: true
horizontalAlignment: root.textHorizontalAlignment
font {
family: Appearance.font.family.expressive
pixelSize: 20
weight: Font.DemiBold
}
color: root.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
animateChange: Config.options.background.widgets.clock.digital.animateChange
}
component ClockStatusText: Row {
id: statusTextRow
property alias statusIcon: statusIconWidget.text
@@ -190,6 +154,7 @@ AbstractBackgroundWidget {
ClockText {
id: statusTextWidget
color: statusTextRow.textColor
horizontalAlignment: root.textHorizontalAlignment
anchors.verticalCenter: statusTextRow.verticalCenter
font {
pixelSize: Appearance.font.pixelSize.large
@@ -66,16 +66,9 @@ Item {
}
}
Connections {
target: Config
function onReadyChanged() {
categoryFileView.path = Directories.generatedWallpaperCategoryPath
}
}
FileView {
id: categoryFileView
path: ""
path: Config.ready ? Directories.generatedWallpaperCategoryPath : ""
watchChanges: true
onFileChanged: reload()
onLoaded: {
@@ -85,7 +78,7 @@ Item {
property bool useSineCookie: Config.options.background.widgets.clock.cookie.useSineCookie
StyledDropShadow {
target: useSineCookie ? sineCookieLoader : roundedPolygonCookieLoader
target: root.useSineCookie ? sineCookieLoader : roundedPolygonCookieLoader
RotationAnimation on rotation {
running: Config.options.background.widgets.clock.cookie.constantlyRotate
@@ -100,7 +93,7 @@ Item {
id: sineCookieLoader
z: 0
visible: false // The DropShadow already draws it
active: useSineCookie
active: root.useSineCookie
sourceComponent: SineCookie {
implicitSize: root.implicitSize
sides: Config.options.background.widgets.clock.cookie.sides
@@ -111,7 +104,7 @@ Item {
id: roundedPolygonCookieLoader
z: 0
visible: false // The DropShadow already draws it
active: !useSineCookie
active: !root.useSineCookie
sourceComponent: MaterialCookie {
implicitSize: root.implicitSize
sides: Config.options.background.widgets.clock.cookie.sides
@@ -0,0 +1,71 @@
pragma ComponentBehavior: Bound
import qs.services
import qs.modules.common
import QtQuick
import QtQuick.Layouts
ColumnLayout {
id: clockColumn
spacing: 4
property bool isVertical: Config.options.background.widgets.clock.digital.vertical
property color colText: Appearance.colors.colOnSecondaryContainer
property var textHorizontalAlignment: Text.AlignHCenter
// Time
ClockText {
id: timeTextTop
text: clockColumn.isVertical ? DateTime.time.split(":")[0].padStart(2, "0") : DateTime.time
color: clockColumn.colText
horizontalAlignment: Text.AlignHCenter
font {
pixelSize: Config.options.background.widgets.clock.digital.font.size
weight: Config.options.background.widgets.clock.digital.font.weight
family: Config.options.background.widgets.clock.digital.font.family
variableAxes: ({
"wdth": Config.options.background.widgets.clock.digital.font.width,
"ROND": Config.options.background.widgets.clock.digital.font.roundness
})
}
}
Loader {
Layout.topMargin: -40
Layout.fillWidth: true
active: clockColumn.isVertical
visible: active
sourceComponent: ClockText {
id: timeTextBottom
text: DateTime.time.split(":")[1].split(" ")[0].padStart(2, "0")
color: clockColumn.colText
horizontalAlignment: clockColumn.textHorizontalAlignment
font {
pixelSize: timeTextTop.font.pixelSize
weight: timeTextTop.font.weight
family: timeTextTop.font.family
variableAxes: timeTextTop.font.variableAxes
}
}
}
// Date
ClockText {
visible: Config.options.background.widgets.clock.digital.showDate
Layout.topMargin: -20
Layout.fillWidth: true
text: DateTime.longDate
color: clockColumn.colText
horizontalAlignment: clockColumn.textHorizontalAlignment
}
// Quote
ClockText {
visible: Config.options.background.widgets.clock.quote.enable && Config.options.background.widgets.clock.quote.text.length > 0
font.pixelSize: Appearance.font.pixelSize.normal
text: Config.options.background.widgets.clock.quote.text
animateChange: false
color: clockColumn.colText
horizontalAlignment: clockColumn.textHorizontalAlignment
}
}
@@ -1,3 +1,5 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
@@ -60,6 +62,7 @@ Scope {
}
color: "transparent"
// Positioning
anchors {
top: !Config.options.bar.bottom
bottom: Config.options.bar.bottom
@@ -72,6 +75,14 @@ Scope {
bottom: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.bottom) * -1
}
// Include in focus grab
Component.onCompleted: {
GlobalFocusGrab.addPersistent(barRoot);
}
Component.onDestruction: {
GlobalFocusGrab.removePersistent(barRoot);
}
MouseArea {
id: hoverRegion
hoverEnabled: true
@@ -80,19 +80,21 @@ Item { // Bar content region
RowLayout {
id: leftSectionRowLayout
anchors.fill: parent
spacing: 10
spacing: 0
LeftSidebarButton { // Left sidebar button
id: leftSidebarButton
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Appearance.rounding.screenRounding
colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
}
ActiveWindow {
visible: root.useShortenedForm === 0
Layout.leftMargin: 10 + (leftSidebarButton.visible ? 0 : Appearance.rounding.screenRounding)
Layout.rightMargin: Appearance.rounding.screenRounding
Layout.fillWidth: true
Layout.fillHeight: true
visible: root.useShortenedForm === 0
}
}
}
@@ -9,6 +9,11 @@ RippleButton {
property bool showPing: false
property bool aiChatEnabled: Config.options.policies.ai !== 0
property bool translatorEnabled: Config.options.sidebar.translator.enable
property bool animeEnabled: Config.options.policies.weeb !== 0
visible: aiChatEnabled || translatorEnabled || animeEnabled
property real buttonPadding: 5
implicitWidth: distroIcon.width + buttonPadding * 2
implicitHeight: distroIcon.height + buttonPadding * 2
@@ -18,9 +18,10 @@ Item {
property bool borderless: Config.options.bar.borderless
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property int effectiveActiveWorkspaceId: monitor?.activeWorkspace?.id ?? 1
readonly property int workspacesShown: Config.options.bar.workspaces.shown
readonly property int workspaceGroup: Math.floor((monitor?.activeWorkspace?.id - 1) / root.workspacesShown)
readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - 1) / root.workspacesShown)
property list<bool> workspaceOccupied: []
property int widgetPadding: 4
property int workspaceButtonWidth: 26
@@ -29,7 +30,7 @@ Item {
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1
property real workspaceIconMarginShrinked: -4
property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % root.workspacesShown
property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown
property bool showNumbers: false
Timer {
@@ -122,8 +123,8 @@ Item {
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
radius: (width / 2)
property var previousOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor?.activeWorkspace?.id === index))
property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor?.activeWorkspace?.id === index+2))
property var previousOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index))
property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+2))
property var radiusPrev: previousOccupied ? 0 : (width / 2)
property var radiusNext: rightOccupied ? 0 : (width / 2)
@@ -133,7 +134,7 @@ Item {
bottomRightRadius: radiusNext
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor?.activeWorkspace?.id === index+1)) ? 1 : 0
opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+1)) ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
@@ -225,7 +226,7 @@ Item {
}
text: Config.options?.bar.workspaces.numberMap[button.workspaceValue - 1] || button.workspaceValue
elide: Text.ElideRight
color: (monitor?.activeWorkspace?.id == button.workspaceValue) ?
color: (root.effectiveActiveWorkspaceId == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
@@ -245,7 +246,7 @@ Item {
width: workspaceButtonWidth * 0.18
height: width
radius: width / 2
color: (monitor?.activeWorkspace?.id == button.workspaceValue) ?
color: (root.effectiveActiveWorkspaceId == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
@@ -100,5 +100,16 @@ StyledPopup {
value: Weather.data.sunset
}
}
// Footer: last refresh
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Translation.tr("Last refresh: %1").arg(Weather.data.lastRefresh)
font {
weight: Font.Medium
pixelSize: Appearance.font.pixelSize.smaller
}
color: Appearance.colors.colOnSurfaceVariant
}
}
}
@@ -54,13 +54,16 @@ Scope { // Scope
item: cheatsheetBackground
}
HyprlandFocusGrab { // Click outside to close
id: grab
windows: [cheatsheetRoot]
active: cheatsheetRoot.visible
onCleared: () => {
if (!active)
cheatsheetRoot.hide();
Component.onCompleted: {
GlobalFocusGrab.addDismissable(cheatsheetRoot);
}
Component.onDestruction: {
GlobalFocusGrab.removeDismissable(cheatsheetRoot);
}
Connections {
target: GlobalFocusGrab
function onDismissed() {
cheatsheetRoot.hide();
}
}
@@ -133,7 +133,7 @@ Item {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
clip: true
color: Appearance.colors.colSurfaceContainer
color: Appearance.m3colors.m3surfaceContainer
radius: Appearance.rounding.normal
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.sizes.elevationMargin
@@ -6,36 +6,61 @@ import qs.modules.common.functions
import qs.modules.common.panels.lock
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
LockScreen {
id: root
// Monitor name -> workspace id to restore on unlock (set when locking)
property var savedWorkspaces: ({})
Timer {
id: restoreTimer
interval: 150
repeat: false
onTriggered: {
var batch = ""
for (var j = 0; j < Quickshell.screens.length; ++j) {
var monName = Quickshell.screens[j].name
var wsId = root.savedWorkspaces[monName]
if (wsId !== undefined) {
batch += "dispatch focusmonitor " + monName + "; dispatch workspace " + wsId + "; "
}
}
if (batch.length > 0) {
Quickshell.execDetached(["hyprctl", "--batch", batch + "reload"])
}
}
}
lockSurface: LockSurface {
context: root.context
}
// Push everything down
property var windowData: []
function saveWindowPositionAndTile() {
Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "true"]);
root.windowData = HyprlandData.windowList.filter(w => (w.floating && w.workspace.id === HyprlandData.activeWorkspace.id));
root.windowData.forEach(w => {
Hyprland.dispatch(`pseudo address:${w.address}`);
Hyprland.dispatch(`settiled address:${w.address}`);
Hyprland.dispatch(`movetoworkspacesilent ${w.workspace.id},address:${w.address}`);
});
}
function restoreWindowPositionAndTile() {
root.windowData.forEach(w => {
Hyprland.dispatch(`setfloating address:${w.address}`);
Hyprland.dispatch(`movewindowpixel exact ${w.at[0]} ${w.at[1]}, address:${w.address}`);
Hyprland.dispatch(`pseudo address:${w.address}`);
});
Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "false"]);
// Single batch for lock and unlock so we don't race multiple hyprctl calls
Connections {
target: GlobalStates
function onScreenLockedChanged() {
if (GlobalStates.screenLocked) {
// Lock: save workspace per monitor and move all to temp workspace in one batch
var next = {}
var batch = "keyword animation workspaces,1,7,menu_decel,slidevert; "
for (var i = 0; i < Quickshell.screens.length; ++i) {
var mon = Quickshell.screens[i].name
var mData = HyprlandData.monitors.find(m => m.name === mon)
var ws = (mData?.activeWorkspace?.id ?? 1)
next[mon] = ws
batch += "dispatch focusmonitor " + mon + "; dispatch workspace " + (2147483647 - ws) + "; "
}
root.savedWorkspaces = next
Quickshell.execDetached(["hyprctl", "--batch", batch + "reload"])
} else {
restoreTimer.start()
}
}
}
// Push everything down (visual only; workspace switch is in Connections above)
Variants {
model: Quickshell.screens
delegate: Scope {
@@ -44,15 +69,6 @@ LockScreen {
property string targetMonitorName: modelData.name
property int verticalMovementDistance: modelData.height
property int horizontalSqueeze: modelData.width * 0.2
onShouldPushChanged: {
if (shouldPush) {
root.saveWindowPositionAndTile();
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, ${verticalMovementDistance}, ${-verticalMovementDistance}, ${horizontalSqueeze}, ${horizontalSqueeze}`]);
} else {
Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, 0, 0, 0, 0`]);
root.restoreWindowPositionAndTile();
}
}
}
}
}
@@ -136,6 +136,8 @@ MouseArea {
// Style
clip: true
font.pixelSize: Appearance.font.pixelSize.small
selectedTextColor: materialShapeChars ? "transparent" : Appearance.colors.colOnSecondaryContainer
selectionColor: materialShapeChars ? "transparent" : Appearance.colors.colSecondaryContainer
// Password
enabled: !root.context.unlockInProgress
@@ -195,6 +197,9 @@ MouseArea {
}
sourceComponent: PasswordChars {
length: root.context.currentText.length
selectionStart: passwordBox.selectionStart
selectionEnd: passwordBox.selectionEnd
cursorPosition: passwordBox.cursorPosition
}
}
}
@@ -215,7 +220,7 @@ MouseArea {
iconSize: 24
text: {
if (root.context.targetAction === LockContext.ActionEnum.Unlock) {
return root.ctrlHeld ? "emoji_food_beverage" : "arrow_right_alt";
return root.ctrlHeld ? "coffee" : "arrow_right_alt";
} else if (root.context.targetAction === LockContext.ActionEnum.Poweroff) {
return "power_settings_new";
} else if (root.context.targetAction === LockContext.ActionEnum.Reboot) {
@@ -9,29 +9,62 @@ import Quickshell
StyledFlickable {
id: root
required property int length
property int selectionStart
property int selectionEnd
property int cursorPosition
property color color: Appearance.colors.colPrimary
property color selectedTextColor: Appearance.colors.colOnSecondaryContainer
property color selectionColor: Appearance.colors.colSecondaryContainer
property int charSize: 20
contentWidth: dotsRow.implicitWidth
contentX: (Math.max(contentWidth - width, 0))
Behavior on contentX {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Rectangle {
id: cursor
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: root.charSize * root.cursorPosition
}
color: root.color
implicitWidth: 2
implicitHeight: root.charSize
Behavior on anchors.leftMargin {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(cursor)
}
}
Row {
id: dotsRow
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: 4
leftMargin: 4 - 5 // -5 to account for spacing being simulated by char item width
}
spacing: 10
spacing: 0
Repeater {
model: ScriptModel {
model: ScriptModel { // TODO: use proper custom object model to insert new char at the correct pos
values: Array(root.length)
}
delegate: Item {
delegate: Rectangle {
id: charItem
required property int index
implicitWidth: 10
implicitHeight: 10
implicitWidth: root.charSize
implicitHeight: root.charSize
property bool selected: index >= root.selectionStart && index < root.selectionEnd
color: ColorUtils.transparentize(root.selectionColor, selected ? 0 : 1)
MaterialShape {
id: materialShape
anchors.centerIn: parent
@@ -46,7 +79,7 @@ StyledFlickable {
]
shape: charShapes[charItem.index % charShapes.length]
// Animate on appearance
color: Appearance.colors.colPrimary
color: charItem.selected ? root.selectedTextColor : root.color
implicitSize: 0
opacity: 0
scale: 0.5
@@ -81,7 +81,7 @@ Scope {
}
sourceComponent: PanelWindow {
id: mediaControlsRoot
id: panelWindow
visible: true
exclusionMode: ExclusionMode.Ignore
@@ -98,9 +98,9 @@ Scope {
right: Config.options.bar.vertical && Config.options.bar.bottom
}
margins {
top: Config.options.bar.vertical ? ((mediaControlsRoot.screen.height / 2) - widgetHeight * 1.5) : Appearance.sizes.barHeight
top: Config.options.bar.vertical ? ((panelWindow.screen.height / 2) - widgetHeight * 1.5) : Appearance.sizes.barHeight
bottom: Appearance.sizes.barHeight
left: Config.options.bar.vertical ? Appearance.sizes.barHeight : ((mediaControlsRoot.screen.width / 2) - (osdWidth / 2) - widgetWidth)
left: Config.options.bar.vertical ? Appearance.sizes.barHeight : ((panelWindow.screen.width / 2) - (osdWidth / 2) - widgetWidth)
right: Appearance.sizes.barHeight
}
@@ -108,13 +108,16 @@ Scope {
item: playerColumnLayout
}
HyprlandFocusGrab {
windows: [mediaControlsRoot]
active: mediaControlsLoader.active
onCleared: () => {
if (!active) {
GlobalStates.mediaControlsOpen = false;
}
Component.onCompleted: {
GlobalFocusGrab.addDismissable(panelWindow);
}
Component.onDestruction: {
GlobalFocusGrab.removeDismissable(panelWindow);
}
Connections {
target: GlobalFocusGrab
function onDismissed() {
GlobalStates.mediaControlsOpen = false;
}
}
@@ -137,10 +140,13 @@ Scope {
}
}
Item { // No player placeholder
Item {
// No player placeholder
Layout.alignment: {
if (mediaControlsRoot.anchors.left) return Qt.AlignLeft;
if (mediaControlsRoot.anchors.right) return Qt.AlignRight;
if (panelWindow.anchors.left)
return Qt.AlignLeft;
if (panelWindow.anchors.right)
return Qt.AlignRight;
return Qt.AlignHCenter;
}
Layout.leftMargin: Appearance.sizes.hyprlandGapsOut
@@ -153,7 +159,7 @@ Scope {
target: placeholderBackground
}
Rectangle {
Rectangle {
id: placeholderBackground
anchors.centerIn: parent
color: Appearance.colors.colLayer0
@@ -57,6 +57,13 @@ Scope { // Scope
item: oskBackground
}
// Make it usable with other panels
Component.onCompleted: {
GlobalFocusGrab.addPersistent(oskRoot);
}
Component.onDestruction: {
GlobalFocusGrab.removePersistent(oskRoot);
}
// Background
StyledRectangularShadow {
@@ -4,5 +4,5 @@ import qs.modules.common
Rectangle {
id: contentItem
anchors.fill: parent
color: Appearance.colors.colSurfaceContainer
color: Appearance.m3colors.m3surfaceContainer
}
@@ -190,7 +190,7 @@ AbstractOverlayWidget {
fill: parent
margins: root.resizeMargin
}
color: ColorUtils.transparentize(Appearance.colors.colLayer1, (root.fancyBorders && GlobalStates.overlayOpen) ? 0 : 1)
color: ColorUtils.transparentize(Appearance.colors.colLayer1Base, (root.fancyBorders && GlobalStates.overlayOpen) ? 0 : 1)
radius: root.radius
border.color: ColorUtils.transparentize(Appearance.colors.colOutlineVariant, GlobalStates.overlayOpen ? 0 : 1)
border.width: 1
@@ -217,7 +217,7 @@ AbstractOverlayWidget {
Layout.fillWidth: true
implicitWidth: titleBarRow.implicitWidth + root.padding * 2
implicitHeight: titleBarRow.implicitHeight + root.padding * 2
color: root.fancyBorders ? "transparent" : Appearance.colors.colLayer1
color: root.fancyBorders ? "transparent" : Appearance.colors.colLayer1Base
// border.color: Appearance.colors.colOutlineVariant
// border.width: 1
@@ -14,116 +14,96 @@ import Quickshell.Hyprland
Scope {
id: overviewScope
property bool dontAutoCancelSearch: false
Variants {
id: overviewVariants
model: Quickshell.screens
PanelWindow {
id: root
required property var modelData
property string searchingText: ""
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)
screen: modelData
PanelWindow {
id: panelWindow
property string searchingText: ""
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)
visible: GlobalStates.overviewOpen
WlrLayershell.namespace: "quickshell:overview"
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
color: "transparent"
mask: Region {
item: GlobalStates.overviewOpen ? columnLayout : null
}
anchors {
top: true
bottom: true
left: true
right: true
}
Connections {
target: GlobalStates
function onOverviewOpenChanged() {
if (!GlobalStates.overviewOpen) {
searchWidget.disableExpandAnimation();
overviewScope.dontAutoCancelSearch = false;
GlobalFocusGrab.dismiss();
} else {
if (!overviewScope.dontAutoCancelSearch) {
searchWidget.cancelSearch();
}
GlobalFocusGrab.addDismissable(panelWindow);
}
}
}
Connections {
target: GlobalFocusGrab
function onDismissed() {
GlobalStates.overviewOpen = false;
}
}
implicitWidth: columnLayout.implicitWidth
implicitHeight: columnLayout.implicitHeight
function setSearchingText(text) {
searchWidget.setSearchingText(text);
searchWidget.focusFirstItem();
}
Column {
id: columnLayout
visible: GlobalStates.overviewOpen
WlrLayershell.namespace: "quickshell:overview"
WlrLayershell.layer: WlrLayer.Overlay
// WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
color: "transparent"
mask: Region {
item: GlobalStates.overviewOpen ? columnLayout : null
}
anchors {
top: true
bottom: true
left: true
right: true
horizontalCenter: parent.horizontalCenter
top: parent.top
}
spacing: -8
HyprlandFocusGrab {
id: grab
windows: [root]
property bool canBeActive: root.monitorIsFocused
active: false
onCleared: () => {
if (!active)
GlobalStates.overviewOpen = false;
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
GlobalStates.overviewOpen = false;
} else if (event.key === Qt.Key_Left) {
if (!panelWindow.searchingText)
Hyprland.dispatch("workspace r-1");
} else if (event.key === Qt.Key_Right) {
if (!panelWindow.searchingText)
Hyprland.dispatch("workspace r+1");
}
}
Connections {
target: GlobalStates
function onOverviewOpenChanged() {
if (!GlobalStates.overviewOpen) {
searchWidget.disableExpandAnimation();
overviewScope.dontAutoCancelSearch = false;
} else {
if (!overviewScope.dontAutoCancelSearch) {
searchWidget.cancelSearch();
}
delayedGrabTimer.start();
}
SearchWidget {
id: searchWidget
anchors.horizontalCenter: parent.horizontalCenter
Synchronizer on searchingText {
property alias source: panelWindow.searchingText
}
}
Timer {
id: delayedGrabTimer
interval: Config.options.hacks.arbitraryRaceConditionDelay
repeat: false
onTriggered: {
if (!grab.canBeActive)
return;
grab.active = GlobalStates.overviewOpen;
}
}
implicitWidth: columnLayout.implicitWidth
implicitHeight: columnLayout.implicitHeight
function setSearchingText(text) {
searchWidget.setSearchingText(text);
searchWidget.focusFirstItem();
}
Column {
id: columnLayout
visible: GlobalStates.overviewOpen
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
}
spacing: -8
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
GlobalStates.overviewOpen = false;
} else if (event.key === Qt.Key_Left) {
if (!root.searchingText)
Hyprland.dispatch("workspace r-1");
} else if (event.key === Qt.Key_Right) {
if (!root.searchingText)
Hyprland.dispatch("workspace r+1");
}
}
SearchWidget {
id: searchWidget
anchors.horizontalCenter: parent.horizontalCenter
Synchronizer on searchingText {
property alias source: root.searchingText
}
}
Loader {
id: overviewLoader
anchors.horizontalCenter: parent.horizontalCenter
active: GlobalStates.overviewOpen && (Config?.options.overview.enable ?? true)
sourceComponent: OverviewWidget {
panelWindow: root
visible: (root.searchingText == "")
}
Loader {
id: overviewLoader
anchors.horizontalCenter: parent.horizontalCenter
active: GlobalStates.overviewOpen && (Config?.options.overview.enable ?? true)
sourceComponent: OverviewWidget {
screen: panelWindow.screen
visible: (panelWindow.searchingText == "")
}
}
}
@@ -134,15 +114,9 @@ Scope {
GlobalStates.overviewOpen = false;
return;
}
for (let i = 0; i < overviewVariants.instances.length; i++) {
let panelWindow = overviewVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
overviewScope.dontAutoCancelSearch = true;
panelWindow.setSearchingText(Config.options.search.prefix.clipboard);
GlobalStates.overviewOpen = true;
return;
}
}
overviewScope.dontAutoCancelSearch = true;
panelWindow.setSearchingText(Config.options.search.prefix.clipboard);
GlobalStates.overviewOpen = true;
}
function toggleEmojis() {
@@ -150,15 +124,9 @@ Scope {
GlobalStates.overviewOpen = false;
return;
}
for (let i = 0; i < overviewVariants.instances.length; i++) {
let panelWindow = overviewVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
overviewScope.dontAutoCancelSearch = true;
panelWindow.setSearchingText(Config.options.search.prefix.emojis);
GlobalStates.overviewOpen = true;
return;
}
}
overviewScope.dontAutoCancelSearch = true;
panelWindow.setSearchingText(Config.options.search.prefix.emojis);
GlobalStates.overviewOpen = true;
}
IpcHandler {
@@ -192,6 +160,14 @@ Scope {
GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
}
}
GlobalShortcut {
name: "overviewWorkspacesClose"
description: "Closes overview on press"
onPressed: {
GlobalStates.overviewOpen = false;
}
}
GlobalShortcut {
name: "overviewWorkspacesToggle"
description: "Toggles overview on press"
@@ -13,11 +13,13 @@ import Quickshell.Hyprland
Item {
id: root
required property var panelWindow
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
required property var screen
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(screen)
readonly property var toplevels: ToplevelManager.toplevels
// Clamp to avoid lock-screen temp workspace (2147483647 - N) leaking into UI
readonly property int effectiveActiveWorkspaceId: Math.max(1, Math.min(100, monitor?.activeWorkspace?.id ?? 1))
readonly property int workspacesShown: Config.options.overview.rows * Config.options.overview.columns
readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown)
readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - 1) / workspacesShown)
property bool monitorIsFocused: (Hyprland.focusedMonitor?.name == monitor.name)
property var windows: HyprlandData.windowList
property var windowByAddress: HyprlandData.windowByAddress
@@ -101,7 +103,7 @@ Item {
required property int index
property int colIndex: index
property int workspaceValue: root.workspaceGroup * root.workspacesShown + getWsInCell(row.index, colIndex)
property color defaultWorkspaceColor: ColorUtils.mix(Appearance.colors.colBackgroundSurfaceContainer, Appearance.colors.colSurfaceContainerHigh, 0.8)
property color defaultWorkspaceColor: Appearance.colors.colSurfaceContainerLow
property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1)
property color hoveredBorderColor: Appearance.colors.colLayer2Hover
property bool hoveredWhileDragging: false
@@ -301,8 +303,8 @@ Item {
Rectangle { // Focused workspace indicator
id: focusedWorkspaceIndicator
property int rowIndex: getWsRow(monitor.activeWorkspace?.id)
property int colIndex: getWsColumn(monitor.activeWorkspace?.id)
property int rowIndex: getWsRow(root.effectiveActiveWorkspaceId)
property int colIndex: getWsColumn(root.effectiveActiveWorkspaceId)
x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex
y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex
z: root.windowZ
@@ -1,15 +1,12 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
RowLayout {
id: root
@@ -92,6 +89,16 @@ RowLayout {
}
}
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Tab) {
if (LauncherSearch.results.length === 0) return;
const tabbedText = LauncherSearch.results[0].name;
LauncherSearch.query = tabbedText;
searchInput.text = tabbedText;
event.accepted = true;
}
}
}
IconToolbarButton {
@@ -1,23 +1,28 @@
pragma ComponentBehavior: Bound
import Qt.labs.synchronizer
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import Qt.labs.synchronizer
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
Item { // Wrapper
id: root
readonly property string xdgConfigHome: Directories.config
readonly property int typingDebounceInterval: 200
readonly property int typingResultLimit: 15 // Should be enough to cover the whole view
property string searchingText: LauncherSearch.query
property bool showResults: searchingText != ""
implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2
implicitHeight: searchBar.implicitHeight + searchBar.verticalPadding * 2 + Appearance.sizes.elevationMargin * 2
implicitHeight: searchWidgetContent.implicitHeight + searchBar.verticalPadding * 2 + Appearance.sizes.elevationMargin * 2
function focusFirstItem() {
appResults.currentIndex = 0;
@@ -178,30 +183,48 @@ Item { // Wrapper
}
}
model: ScriptModel {
id: model
objectProp: "key"
values: LauncherSearch.results
onValuesChanged: {
root.focusFirstItem();
Timer {
id: debounceTimer
interval: root.typingDebounceInterval
onTriggered: {
resultModel.values = LauncherSearch.results ?? [];
}
}
Connections {
target: LauncherSearch
function onResultsChanged() {
resultModel.values = LauncherSearch.results.slice(0, root.typingResultLimit);
root.focusFirstItem();
debounceTimer.restart();
}
}
model: ScriptModel {
id: resultModel
objectProp: "key"
}
delegate: SearchItem {
id: searchItem
// The selectable item for each search result
required property var modelData
anchors.left: parent?.left
anchors.right: parent?.right
entry: modelData
query: StringUtils.cleanOnePrefix(root.searchingText, [
Config.options.search.prefix.action,
Config.options.search.prefix.app,
Config.options.search.prefix.clipboard,
Config.options.search.prefix.emojis,
Config.options.search.prefix.math,
Config.options.search.prefix.shellCommand,
Config.options.search.prefix.webSearch
])
query: StringUtils.cleanOnePrefix(root.searchingText, [Config.options.search.prefix.action, Config.options.search.prefix.app, Config.options.search.prefix.clipboard, Config.options.search.prefix.emojis, Config.options.search.prefix.math, Config.options.search.prefix.shellCommand, Config.options.search.prefix.webSearch])
Keys.onPressed: event => {
if (event.key === Qt.Key_Tab) {
if (LauncherSearch.results.length === 0)
return;
const tabbedText = searchItem.modelData.name;
LauncherSearch.query = tabbedText;
searchBar.searchInput.text = tabbedText;
event.accepted = true;
root.focusSearchInput();
}
}
}
}
}
@@ -0,0 +1,112 @@
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
Item {
id: root
property var action
property var selectionMode
property string description: switch (root.action) {
case RegionSelection.SnipAction.Copy:
case RegionSelection.SnipAction.Edit:
return Translation.tr("Copy region (LMB) or annotate (RMB)");
case RegionSelection.SnipAction.Search:
return Translation.tr("Search with Google Lens");
case RegionSelection.SnipAction.CharRecognition:
return Translation.tr("Recognize text");
case RegionSelection.SnipAction.Record:
case RegionSelection.SnipAction.RecordWithSound:
return Translation.tr("Record region");
}
property string materialSymbol: switch (root.action) {
case RegionSelection.SnipAction.Copy:
case RegionSelection.SnipAction.Edit:
return "content_cut";
case RegionSelection.SnipAction.Search:
return "image_search";
case RegionSelection.SnipAction.CharRecognition:
return "document_scanner";
case RegionSelection.SnipAction.Record:
case RegionSelection.SnipAction.RecordWithSound:
return "videocam";
default:
return "";
}
property bool showDescription: true
function hideDescription() {
root.showDescription = false
}
Timer {
id: descTimeout
interval: 1000
running: true
onTriggered: {
root.hideDescription()
}
}
onActionChanged: {
root.showDescription = true
descTimeout.restart()
}
property int margins: 8
implicitWidth: content.implicitWidth + margins * 2
implicitHeight: content.implicitHeight + margins * 2
Rectangle {
id: content
anchors.centerIn: parent
property real padding: 8
implicitHeight: 38
implicitWidth: root.showDescription ? contentRow.implicitWidth + padding * 2 : implicitHeight
clip: true
topLeftRadius: 6
bottomLeftRadius: implicitHeight - topLeftRadius
bottomRightRadius: bottomLeftRadius
topRightRadius: bottomLeftRadius
color: Appearance.colors.colPrimary
Behavior on topLeftRadius {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on implicitWidth {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Row {
id: contentRow
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: content.padding
}
spacing: 12
MaterialSymbol {
anchors.verticalCenter: parent.verticalCenter
iconSize: 22
color: Appearance.colors.colOnPrimary
animateChange: true
text: root.materialSymbol
}
FadeLoader {
id: descriptionLoader
anchors.verticalCenter: parent.verticalCenter
shown: root.showDescription
sourceComponent: StyledText {
color: Appearance.colors.colOnPrimary
text: root.description
anchors.right: parent.right
anchors.rightMargin: 6
}
}
}
}
}
@@ -23,48 +23,6 @@ Toolbar {
// Signals
signal dismiss()
MaterialShape {
Layout.fillHeight: true
Layout.leftMargin: 2
Layout.rightMargin: 2
implicitSize: 36 // Intentionally smaller because this one is brighter than others
shape: switch (root.action) {
case RegionSelection.SnipAction.Copy:
case RegionSelection.SnipAction.Edit:
return MaterialShape.Shape.Cookie4Sided;
case RegionSelection.SnipAction.Search:
return MaterialShape.Shape.Pentagon;
case RegionSelection.SnipAction.CharRecognition:
return MaterialShape.Shape.Sunny;
case RegionSelection.SnipAction.Record:
case RegionSelection.SnipAction.RecordWithSound:
return MaterialShape.Shape.Gem;
default:
return MaterialShape.Shape.Cookie12Sided;
}
color: Appearance.colors.colPrimary
MaterialSymbol {
anchors.centerIn: parent
iconSize: 22
color: Appearance.colors.colOnPrimary
animateChange: true
text: switch (root.action) {
case RegionSelection.SnipAction.Copy:
case RegionSelection.SnipAction.Edit:
return "content_cut";
case RegionSelection.SnipAction.Search:
return "image_search";
case RegionSelection.SnipAction.CharRecognition:
return "document_scanner";
case RegionSelection.SnipAction.Record:
case RegionSelection.SnipAction.RecordWithSound:
return "videocam";
default:
return "";
}
}
}
ToolbarTabBar {
id: tabBar
tabButtonList: [
@@ -76,5 +34,4 @@ Toolbar {
root.selectionMode = currentIndex === 0 ? RegionSelection.SelectionMode.RectCorners : RegionSelection.SelectionMode.Circle;
}
}
}
@@ -33,22 +33,40 @@ Item {
}
// Selection border
Rectangle {
// Rectangle {
// id: selectionBorder
// z: 1
// anchors {
// left: parent.left
// top: parent.top
// leftMargin: root.regionX
// topMargin: root.regionY
// }
// width: root.regionWidth
// height: root.regionHeight
// color: "transparent"
// border.color: root.color
// border.width: 2
// // radius: root.standardRounding
// radius: 0 // TODO: figure out how to make the overlay thing work with rounding
// }
DashedBorder {
id: selectionBorder
z: 1
z: 9
anchors {
left: parent.left
top: parent.top
leftMargin: root.regionX
topMargin: root.regionY
leftMargin: Math.round(root.regionX) - borderWidth
topMargin: Math.round(root.regionY) - borderWidth
}
width: root.regionWidth
height: root.regionHeight
color: "transparent"
border.color: root.color
border.width: 2
// radius: root.standardRounding
radius: 0 // TODO: figure out how to make the overlay thing work with rounding
width: Math.round(root.regionWidth) + borderWidth * 2
height: Math.round(root.regionHeight) + borderWidth * 2
color: root.color
dashLength: 6
gapLength: 3
borderWidth: 1
}
StyledText {
@@ -1,5 +1,6 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.utils
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.services
@@ -32,15 +33,9 @@ PanelWindow {
property var action: RegionSelection.SnipAction.Copy
property var selectionMode: RegionSelection.SelectionMode.RectCorners
signal dismiss()
property string saveScreenshotDir: Config.options.screenSnip.savePath !== ""
? Config.options.screenSnip.savePath
: ""
property string screenshotDir: Directories.screenshotTemp
property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl
property string fileUploadApiEndpoint: "https://uguu.se/upload"
property color overlayColor: "#88111111"
property color overlayColor: ColorUtils.transparentize("#000000", 0.4)
property color brightText: Appearance.m3colors.darkmode ? Appearance.colors.colOnLayer0 : Appearance.colors.colLayer0
property color brightSecondary: Appearance.m3colors.darkmode ? Appearance.colors.colSecondary : Appearance.colors.colOnSecondary
property color brightTertiary: Appearance.m3colors.darkmode ? Appearance.colors.colTertiary : Qt.lighter(Appearance.colors.colPrimary)
@@ -180,10 +175,12 @@ PanelWindow {
property real regionX: Math.min(dragStartX, draggingX)
property real regionY: Math.min(dragStartY, draggingY)
Process {
TempScreenshotProcess {
id: screenshotProc
running: true
command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(root.screen.name)}' '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`]
screen: root.screen
screenshotDir: root.screenshotDir
screenshotPath: root.screenshotPath
onExited: (exitCode, exitStatus) => {
if (root.enableContentRegions) imageDetectionProcess.running = true;
root.preparationDone = !checkRecordingProc.running;
@@ -229,6 +226,27 @@ PanelWindow {
}
}
function getScreenshotAction() {
switch(root.action) {
case RegionSelection.SnipAction.Copy:
return ScreenshotAction.Action.Copy;
case RegionSelection.SnipAction.Edit:
return ScreenshotAction.Action.Edit;
case RegionSelection.SnipAction.Search:
return ScreenshotAction.Action.Search;
case RegionSelection.SnipAction.CharRecognition:
return ScreenshotAction.Action.CharRecognition;
case RegionSelection.SnipAction.Record:
return ScreenshotAction.Action.Record;
case RegionSelection.SnipAction.RecordWithSound:
return ScreenshotAction.Action.RecordWithSound;
default:
console.warn("[Region Selector] Unknown snip action, skipping snip.");
root.dismiss();
return;
}
}
function snip() {
// Validity check
if (root.regionWidth <= 0 || root.regionHeight <= 0) {
@@ -246,62 +264,20 @@ PanelWindow {
if (root.action === RegionSelection.SnipAction.Copy || root.action === RegionSelection.SnipAction.Edit) {
root.action = root.mouseButton === Qt.RightButton ? RegionSelection.SnipAction.Edit : RegionSelection.SnipAction.Copy;
}
// Set command for action
const rx = Math.round(root.regionX * root.monitorScale);
const ry = Math.round(root.regionY * root.monitorScale);
const rw = Math.round(root.regionWidth * root.monitorScale);
const rh = Math.round(root.regionHeight * root.monitorScale);
const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(root.screenshotPath)} `
+ `-crop ${rw}x${rh}+${rx}+${ry}`
const cropToStdout = `${cropBase} -`
const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
const slurpRegion = `${rx},${ry} ${rw}x${rh}`
const uploadAndGetUrl = (filePath) => {
return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'`
}
const annotationCommand = `${Config.options.regionSelector.annotation.useSatty ? "satty" : "swappy"} -f -`;
switch (root.action) {
case RegionSelection.SnipAction.Copy:
if (saveScreenshotDir === "") {
// not saving the screenshot, just copy to clipboard
snipProc.command = ["bash", "-c", `${cropToStdout} | wl-copy && ${cleanup}`]
break;
}
const savePathBase = root.saveScreenshotDir
snipProc.command = [
"bash", "-c",
`mkdir -p '${StringUtils.shellSingleQuoteEscape(savePathBase)}' && \
saveFileName="screenshot-$(date '+%Y-%m-%d_%H.%M.%S').png" && \
savePath="${savePathBase}/$saveFileName" && \
${cropToStdout} | tee >(wl-copy) > "$savePath" && \
${cleanup}`
]
break;
case RegionSelection.SnipAction.Edit:
snipProc.command = ["bash", "-c", `${cropToStdout} | ${annotationCommand} && ${cleanup}`]
break;
case RegionSelection.SnipAction.Search:
snipProc.command = ["bash", "-c", `${cropInPlace} && xdg-open "${root.imageSearchEngineBaseUrl}$(${uploadAndGetUrl(root.screenshotPath)})" && ${cleanup}`]
break;
case RegionSelection.SnipAction.CharRecognition:
snipProc.command = ["bash", "-c", `${cropInPlace} && tesseract '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}' stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\n' '+' | sed 's/\\+$/\\n/') | wl-copy && ${cleanup}`]
break;
case RegionSelection.SnipAction.Record:
snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}'`]
break;
case RegionSelection.SnipAction.RecordWithSound:
snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}' --sound`]
break;
default:
console.warn("[Region Selector] Unknown snip action, skipping snip.");
root.dismiss();
return;
}
const screenshotDir = Config.options.screenSnip.savePath !== "" ? //
Config.options.screenSnip.savePath : "";
var screenshotAction = root.getScreenshotAction();
const command = ScreenshotAction.getCommand(
root.regionX * root.monitorScale, //
root.regionY * root.monitorScale, //
root.regionWidth * root.monitorScale,//
root.regionHeight * root.monitorScale, //
root.screenshotPath, //
screenshotAction, //
screenshotDir
)
snipProc.command = command;
// Image post-processing
snipProc.startDetached();
@@ -399,6 +375,14 @@ PanelWindow {
}
}
CursorGuide {
z: 9999
x: root.dragging ? root.regionX + root.regionWidth : mouseArea.mouseX
y: root.dragging ? root.regionY + root.regionHeight : mouseArea.mouseY
action: root.action
selectionMode: root.selectionMode
}
// Window regions
Repeater {
model: ScriptModel {
@@ -471,7 +455,7 @@ PanelWindow {
// Controls
Row {
id: regionSelectionControls
z: 9999
z: 10
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
@@ -25,6 +25,10 @@ Rectangle {
border.width: targeted ? 4 : 2
radius: 4
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
@@ -45,9 +45,7 @@ Scope {
WlrLayershell.namespace: "quickshell:session"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
// This is a big surface so we needa carefully choose the transparency,
// or we'll get a large scary rgb blob
color: ColorUtils.transparentize(Appearance.m3colors.m3background, Appearance.m3colors.darkmode ? 0.04 : 0.12)
color: ColorUtils.transparentize(Appearance.m3colors.m3background, Appearance.m3colors.darkmode ? 0.05 : 0.12)
anchors {
top: true
@@ -240,7 +238,7 @@ Scope {
}
}
RowLayout {
ColumnLayout {
anchors {
top: contentColumn.bottom
topMargin: 10
@@ -249,19 +247,22 @@ Scope {
spacing: 10
Loader {
active: SessionWarnings.packageManagerRunning
Layout.alignment: Qt.AlignHCenter
active: SessionWarnings.downloadRunning
visible: active
sourceComponent: DescriptionLabel {
text: Translation.tr("Your package manager is running")
text: Translation.tr("There might be a download in progress. Check your Downloads folder.")
textColor: Appearance.m3colors.m3onErrorContainer
color: Appearance.m3colors.m3errorContainer
}
}
Loader {
active: SessionWarnings.downloadRunning
Layout.alignment: Qt.AlignHCenter
active: SessionWarnings.packageManagerRunning
visible: active
sourceComponent: DescriptionLabel {
text: Translation.tr("There might be a download in progress")
text: Translation.tr("Your package manager is running")
textColor: Appearance.m3colors.m3onErrorContainer
color: Appearance.m3colors.m3errorContainer
}
@@ -314,7 +314,10 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
implicitWidth: statusRowLayout.implicitWidth + 10 * 2
implicitHeight: Math.max(statusRowLayout.implicitHeight, 38)
radius: Appearance.rounding.normal - root.padding
color: Appearance.colors.colLayer2
color: messageListView.atYBeginning ? Appearance.colors.colLayer2 : Appearance.colors.colLayer2Base
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
RowLayout {
id: statusRowLayout
anchors.centerIn: parent
@@ -67,6 +67,7 @@ Scope { // Scope
onDetachChanged: {
if (root.detach) {
GlobalFocusGrab.removeDismissable(sidebarLoader.item) // Remove sidebar from the focus grab system
sidebarContent.parent = null; // Detach content from sidebar
sidebarLoader.active = false; // Unload sidebar
detachedSidebarLoader.active = true; // Load detached window
@@ -84,11 +85,11 @@ Scope { // Scope
active: true
sourceComponent: PanelWindow { // Window
id: sidebarRoot
id: panelWindow
visible: GlobalStates.sidebarLeftOpen
property bool extend: false
property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth
property real sidebarWidth: panelWindow.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth
property var contentParent: sidebarLeftBackground
function hide() {
@@ -113,15 +114,17 @@ Scope { // Scope
item: sidebarLeftBackground
}
HyprlandFocusGrab { // Click outside to close
id: grab
windows: [ sidebarRoot ]
active: sidebarRoot.visible && !root.pin
onActiveChanged: { // Focus the selected tab
if (active) sidebarLeftBackground.children[0].focusActiveItem()
onVisibleChanged: {
if (visible) {
GlobalFocusGrab.addDismissable(panelWindow);
} else {
GlobalFocusGrab.removeDismissable(panelWindow);
}
onCleared: () => {
if (!active) sidebarRoot.hide()
}
Connections {
target: GlobalFocusGrab
function onDismissed() {
panelWindow.hide();
}
}
@@ -136,7 +139,7 @@ Scope { // Scope
anchors.left: parent.left
anchors.topMargin: Appearance.sizes.hyprlandGapsOut
anchors.leftMargin: Appearance.sizes.hyprlandGapsOut
width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
width: panelWindow.sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
color: Appearance.colors.colLayer0
border.width: 1
@@ -149,11 +152,11 @@ Scope { // Scope
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
sidebarRoot.hide();
panelWindow.hide();
}
if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_O) {
sidebarRoot.extend = !sidebarRoot.extend;
panelWindow.extend = !panelWindow.extend;
} else if (event.key === Qt.Key_D) {
root.toggleDetach();
} else if (event.key === Qt.Key_P) {
@@ -48,6 +48,7 @@ Item {
spacing: sidebarPadding
Toolbar {
visible: tabButtonList.length > 0
Layout.alignment: Qt.AlignHCenter
enableShadow: false
ToolbarTabBar {
@@ -83,9 +84,10 @@ Item {
}
contentChildren: [
...((root.aiChatEnabled || (!root.translatorEnabled && !root.animeEnabled)) ? [aiChat.createObject()] : []),
...(root.aiChatEnabled ? [aiChat.createObject()] : []),
...(root.translatorEnabled ? [translator.createObject()] : []),
...(root.animeEnabled ? [anime.createObject()] : [])
...((root.tabButtonList.length === 0 || (!root.aiChatEnabled && !root.translatorEnabled && root.animeCloset)) ? [placeholder.createObject()] : []),
...(root.animeEnabled ? [anime.createObject()] : []),
]
}
}
@@ -102,6 +104,15 @@ Item {
id: anime
Anime {}
}
Component {
id: placeholder
Item {
StyledText {
anchors.centerIn: parent
text: root.animeCloset ? Translation.tr("Nothing") : Translation.tr("Enjoy your empty sidebar...")
color: Appearance.colors.colSubtext
}
}
}
}
}
@@ -126,7 +126,7 @@ Button {
opacity: root.showActions ? 1 : 0
visible: opacity > 0
radius: Appearance.rounding.small
color: Appearance.colors.colSurfaceContainer
color: Appearance.m3colors.m3surfaceContainer
implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2
implicitWidth: contextMenuColumnLayout.implicitWidth
@@ -138,7 +138,7 @@ Rectangle {
anchors.fill: parent
// implicitHeight: tabStack.implicitHeight
spacing: 10
spacing: 20
// Navigation rail
Item {
@@ -146,7 +146,7 @@ Rectangle {
Layout.fillWidth: false
Layout.leftMargin: 10
Layout.topMargin: 10
width: tabBar.width
implicitWidth: tabBar.implicitWidth
// Navigation rail buttons
NavigationRailTabArray {
id: tabBar
@@ -12,18 +12,17 @@ Scope {
property int sidebarWidth: Appearance.sizes.sidebarWidth
PanelWindow {
id: sidebarRoot
id: panelWindow
visible: GlobalStates.sidebarRightOpen
function hide() {
GlobalStates.sidebarRightOpen = false
GlobalStates.sidebarRightOpen = false;
}
exclusiveZone: 0
implicitWidth: sidebarWidth
WlrLayershell.namespace: "quickshell:sidebarRight"
// Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab
// WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.keyboardFocus: GlobalStates.sidebarRightOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
color: "transparent"
anchors {
@@ -32,12 +31,17 @@ Scope {
bottom: true
}
HyprlandFocusGrab {
id: grab
windows: [ sidebarRoot ]
active: GlobalStates.sidebarRightOpen
onCleared: () => {
if (!active) sidebarRoot.hide()
onVisibleChanged: {
if (visible) {
GlobalFocusGrab.addDismissable(panelWindow);
} else {
GlobalFocusGrab.removeDismissable(panelWindow);
}
}
Connections {
target: GlobalFocusGrab
function onDismissed() {
panelWindow.hide();
}
}
@@ -53,16 +57,14 @@ Scope {
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
focus: GlobalStates.sidebarRightOpen
Keys.onPressed: (event) => {
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
sidebarRoot.hide();
panelWindow.hide();
}
}
sourceComponent: SidebarRightContent {}
}
}
IpcHandler {
@@ -105,5 +107,4 @@ Scope {
GlobalStates.sidebarRightOpen = false;
}
}
}
@@ -47,6 +47,7 @@ DialogListItem {
color: Appearance.colors.colOnSurfaceVariant
elide: Text.ElideRight
text: root.device?.name || Translation.tr("Unknown device")
textFormat: Text.PlainText
}
StyledText {
visible: (root.device?.connected || root.device?.paired) ?? false
@@ -139,7 +139,7 @@ Item {
anchors.margins: root.dialogMargins
implicitHeight: dialogColumnLayout.implicitHeight
color: Appearance.colors.colSurfaceContainerHigh
color: Appearance.m3colors.m3surfaceContainerHigh
radius: Appearance.rounding.normal
function addTask() {
@@ -40,6 +40,7 @@ DialogListItem {
color: Appearance.colors.colOnSurfaceVariant
elide: Text.ElideRight
text: root.wifiNetwork?.ssid ?? Translation.tr("Unknown")
textFormat: Text.PlainText
}
MaterialSymbol {
visible: (root.wifiNetwork?.isSecure || root.wifiNetwork?.active) ?? false
@@ -66,6 +66,7 @@ Scope {
}
color: "transparent"
// Positioning
anchors {
left: !Config.options.bar.bottom
right: Config.options.bar.bottom
@@ -73,6 +74,14 @@ Scope {
bottom: true
}
// Include in focus grab
Component.onCompleted: {
GlobalFocusGrab.addPersistent(barRoot);
}
Component.onDestruction: {
GlobalFocusGrab.removePersistent(barRoot);
}
MouseArea {
id: hoverRegion
hoverEnabled: true
@@ -73,7 +73,7 @@ MouseArea {
target: Wallpapers
function onThumbnailGenerated(directory) {
if (thumbnailImage.status !== Image.Error) return;
if (FileUtils.parentDirectory(thumbnailImage.sourcePath) !== directory) return;
if (FileUtils.parentDirectory(thumbnailImage.sourcePath) !== FileUtils.trimFileProtocol(directory)) return;
thumbnailImage.source = "";
thumbnailImage.source = thumbnailImage.thumbnailPath;
}
@@ -25,6 +25,7 @@ Scope {
exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "quickshell:wallpaperSelector"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent"
anchors.top: true
@@ -39,12 +40,16 @@ Scope {
implicitHeight: Appearance.sizes.wallpaperSelectorHeight
implicitWidth: Appearance.sizes.wallpaperSelectorWidth
HyprlandFocusGrab { // Click outside to close
id: grab
windows: [ panelWindow ]
active: wallpaperSelectorLoader.active
onCleared: () => {
if (!active) GlobalStates.wallpaperSelectorOpen = false;
Component.onCompleted: {
GlobalFocusGrab.addDismissable(panelWindow);
}
Component.onDestruction: {
GlobalFocusGrab.removeDismissable(panelWindow);
}
Connections {
target: GlobalFocusGrab
function onDismissed() {
GlobalStates.wallpaperSelectorOpen = false;
}
}
@@ -53,9 +53,9 @@ ContentPage {
}
ContentSection {
id: settingsClock
icon: "clock_loader_40"
title: Translation.tr("Widget: Clock")
id: settingsClock
function stylePresent(styleName) {
if (!Config.options.background.widgets.clock.showOnlyWhenLocked && Config.options.background.widgets.clock.style === styleName) {
@@ -120,61 +120,161 @@ ContentPage {
}
}
ContentSubsection {
visible: !Config.options.background.widgets.clock.showOnlyWhenLocked
title: Translation.tr("Clock style")
ConfigSelectionArray {
currentValue: Config.options.background.widgets.clock.style
onSelected: newValue => {
Config.options.background.widgets.clock.style = newValue;
}
options: [
{
displayName: Translation.tr("Digital"),
icon: "timer_10",
value: "digital"
},
{
displayName: Translation.tr("Cookie"),
icon: "cookie",
value: "cookie"
ConfigRow {
ContentSubsection {
visible: !Config.options.background.widgets.clock.showOnlyWhenLocked
title: Translation.tr("Clock style")
Layout.fillWidth: true
ConfigSelectionArray {
currentValue: Config.options.background.widgets.clock.style
onSelected: newValue => {
Config.options.background.widgets.clock.style = newValue;
}
]
options: [
{
displayName: Translation.tr("Digital"),
icon: "timer_10",
value: "digital"
},
{
displayName: Translation.tr("Cookie"),
icon: "cookie",
value: "cookie"
}
]
}
}
}
ContentSubsection {
title: Translation.tr("Clock style (locked)")
ConfigSelectionArray {
currentValue: Config.options.background.widgets.clock.styleLocked
onSelected: newValue => {
Config.options.background.widgets.clock.styleLocked = newValue;
}
options: [
{
displayName: Translation.tr("Digital"),
icon: "timer_10",
value: "digital"
},
{
displayName: Translation.tr("Cookie"),
icon: "cookie",
value: "cookie"
ContentSubsection {
title: Translation.tr("Clock style (locked)")
Layout.fillWidth: false
ConfigSelectionArray {
currentValue: Config.options.background.widgets.clock.styleLocked
onSelected: newValue => {
Config.options.background.widgets.clock.styleLocked = newValue;
}
]
options: [
{
displayName: Translation.tr("Digital"),
icon: "timer_10",
value: "digital"
},
{
displayName: Translation.tr("Cookie"),
icon: "cookie",
value: "cookie"
}
]
}
}
}
ContentSubsection {
visible: settingsClock.digitalPresent
title: Translation.tr("Digital clock settings")
tooltip: Translation.tr("Font width and roundness settings are only available for some fonts like Google Sans Flex")
ConfigSwitch {
buttonIcon: "animation"
text: Translation.tr("Animate time change")
checked: Config.options.background.widgets.clock.digital.animateChange
onCheckedChanged: {
Config.options.background.widgets.clock.digital.animateChange = checked;
ConfigRow {
uniform: true
ConfigSwitch {
buttonIcon: "vertical_distribute"
text: Translation.tr("Vertical")
checked: Config.options.background.widgets.clock.digital.vertical
onCheckedChanged: {
Config.options.background.widgets.clock.digital.vertical = checked;
}
}
ConfigSwitch {
buttonIcon: "animation"
text: Translation.tr("Animate time change")
checked: Config.options.background.widgets.clock.digital.animateChange
onCheckedChanged: {
Config.options.background.widgets.clock.digital.animateChange = checked;
}
}
}
ConfigRow {
uniform: true
ConfigSwitch {
buttonIcon: "date_range"
text: Translation.tr("Show date")
checked: Config.options.background.widgets.clock.digital.showDate
onCheckedChanged: {
Config.options.background.widgets.clock.digital.showDate = checked;
}
}
ConfigSwitch {
buttonIcon: "activity_zone"
text: Translation.tr("Use adaptive alignment")
checked: Config.options.background.widgets.clock.digital.adaptiveAlignment
onCheckedChanged: {
Config.options.background.widgets.clock.digital.adaptiveAlignment = checked;
}
StyledToolTip {
text: Translation.tr("Aligns the date and quote to left, center or right depending on its position on the screen.")
}
}
}
MaterialTextArea {
Layout.fillWidth: true
placeholderText: Translation.tr("Font family")
text: Config.options.background.widgets.clock.digital.font.family
wrapMode: TextEdit.Wrap
onTextChanged: {
Config.options.background.widgets.clock.digital.font.family = text;
}
}
ConfigSlider {
text: Translation.tr("Font weight")
value: Config.options.background.widgets.clock.digital.font.weight
usePercentTooltip: false
buttonIcon: "format_bold"
from: 1
to: 1000
stopIndicatorValues: [350]
onValueChanged: {
Config.options.background.widgets.clock.digital.font.weight = value;
}
}
ConfigSlider {
text: Translation.tr("Font size")
value: Config.options.background.widgets.clock.digital.font.size
usePercentTooltip: false
buttonIcon: "format_size"
from: 50
to: 700
stopIndicatorValues: [90]
onValueChanged: {
Config.options.background.widgets.clock.digital.font.size = value;
}
}
ConfigSlider {
text: Translation.tr("Font width")
value: Config.options.background.widgets.clock.digital.font.width
usePercentTooltip: false
buttonIcon: "fit_width"
from: 25
to: 125
stopIndicatorValues: [100]
onValueChanged: {
Config.options.background.widgets.clock.digital.font.width = value;
}
}
ConfigSlider {
text: Translation.tr("Font roundness")
value: Config.options.background.widgets.clock.digital.font.roundness
usePercentTooltip: false
buttonIcon: "line_curve"
from: 0
to: 100
onValueChanged: {
Config.options.background.widgets.clock.digital.font.roundness = value;
}
}
}
@@ -141,7 +141,7 @@ ContentPage {
key: "Ctrl"
}
KeyboardKey {
key: "󰖳"
key: Config.options.cheatsheet.superKey ?? "󰖳"
}
StyledText {
Layout.alignment: Qt.AlignVCenter
@@ -225,9 +225,6 @@ ContentPage {
onCheckedChanged: {
Config.options.appearance.transparency.enable = checked;
}
StyledToolTip {
text: Translation.tr("Might look ass. Unsupported.")
}
}
}
@@ -203,6 +203,32 @@ ContentPage {
}
}
// There's no update indicator in ii for now so we shouldn't show this yet
// ContentSection {
// icon: "deployed_code_update"
// title: Translation.tr("System updates (Arch only)")
// ConfigSwitch {
// text: Translation.tr("Enable update checks")
// checked: Config.options.updates.enableCheck
// onCheckedChanged: {
// Config.options.updates.enableCheck = checked;
// }
// }
// ConfigSpinBox {
// icon: "av_timer"
// text: Translation.tr("Check interval (mins)")
// value: Config.options.updates.checkInterval
// from: 60
// to: 1440
// stepSize: 60
// onValueChanged: {
// Config.options.updates.checkInterval = value;
// }
// }
// }
ContentSection {
icon: "weather_mix"
title: Translation.tr("Weather")
@@ -41,6 +41,7 @@ ExpandableChoiceButton {
elide: Text.ElideRight
font.pixelSize: Looks.font.pixelSize.large
text: root.device?.name || Translation.tr("Unknown device")
textFormat: Text.PlainText
}
WText { // Status
id: statusText
@@ -61,6 +61,7 @@ ExpandableChoiceButton {
elide: Text.ElideRight
font.pixelSize: Looks.font.pixelSize.large
text: root.wifiNetwork?.ssid ?? Translation.tr("Unknown")
textFormat: Text.PlainText
}
WText { // Status
id: statusText
@@ -13,7 +13,7 @@ Scope {
LazyLoader {
id: barLoader
active: GlobalStates.barOpen && !GlobalStates.screenLocked
active: GlobalStates.barOpen
component: Variants {
model: Quickshell.screens
delegate: PanelWindow { // Bar window
@@ -12,7 +12,20 @@ AppButton {
required property var appEntry
readonly property bool isSeparator: appEntry.appId === "SEPARATOR"
readonly property var desktopEntry: DesktopEntries.heuristicLookup(appEntry.appId)
property var desktopEntry: DesktopEntries.heuristicLookup(appEntry.appId)
Timer {
// Retry looking up the desktop entry if it failed (e.g. database not loaded yet)
property int retryCount: 5
interval: 1000
running: !root.isSeparator && root.desktopEntry === null && retryCount > 0
repeat: true
onTriggered: {
retryCount--;
root.desktopEntry = DesktopEntries.heuristicLookup(root.appEntry.appId);
}
}
property bool active: root.appEntry.toplevels.some(t => t.activated)
property bool hasWindows: appEntry.toplevels.length > 0
@@ -36,15 +36,34 @@ LockScreen {
Image {
id: bg
z: 0
anchors.fill: parent
width: parent.width
height: parent.height
onStatusChanged: {
if (status === Image.Ready) {
print("Lock wallpaper loaded");
print(lockSurfaceItem.height);
y = -lockSurfaceItem.height;
openAnim.restart();
}
}
sourceSize: Qt.size(lockSurfaceItem.width, lockSurfaceItem.height)
source: Config.options.background.wallpaperPath
fillMode: Image.PreserveAspectCrop
PropertyAnimation {
id: openAnim
target: bg
property: "y"
to: 0
duration: 350
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
}
GaussianBlur {
z: 1
anchors.fill: parent
anchors.fill: bg
source: bg
radius: 100
samples: radius * 2 + 1
@@ -67,7 +86,7 @@ LockScreen {
Interactables {
id: interactables
z: 2
anchors.fill: parent
anchors.fill: bg
}
}
@@ -83,12 +102,31 @@ LockScreen {
// }
function switchToFocusedView() {
root.passwordView = true;
switchToPasswordViewAnim.restart();
}
SequentialAnimation {
id: switchToPasswordViewAnim
PropertyAnimation {
target: unfocusedContent
property: "y"
from: 0
to: -height * 1.1
duration: 250
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
ScriptAction {
script: {
root.passwordView = true;
}
}
}
Item {
id: unfocusedContent
anchors.fill: parent
width: parent.width
height: parent.height
visible: !root.passwordView
ClockTextGroup {
anchors {
@@ -17,8 +17,9 @@ Singleton {
property string iconsPath: `${Directories.assetsPath}/icons/fluent`
property bool dark: Appearance.m3colors.darkmode
property real backgroundTransparency: 0.16
property real panelBackgroundTransparency: 0.14
readonly property bool transparencyEnabled: Config.options.appearance.transparency.enable
property real backgroundTransparency: transparencyEnabled ? 0.16 : 0
property real panelBackgroundTransparency: transparencyEnabled ? 0.14 : 0
property real panelLayerTransparency: root.dark ? 0.9 : 0.7
property real contentTransparency: root.dark ? 0.87 : 0.5
function applyBackgroundTransparency(col) {
@@ -27,23 +28,22 @@ Singleton {
function applyContentTransparency(col) {
return ColorUtils.applyAlpha(col, 1 - root.contentTransparency)
}
lightColors: QtObject { // TODO: figure out transparency
lightColors: QtObject {
id: lightColors
property color bgPanelFooter: "#EEEEEE"
property color bgPanelBody: "#F2F2F2"
property color bgPanelSeparator: "#E0E0E0"
property color bg0: "#EEEEEE"
property color bg0Border: '#adadad'
property color bg1: "#F7F7F7"
property color bg0Border: '#BEBEBE'
property color bg1Base: "#F7F7F7"
property color bg1: "#F7F7F7"
property color bg1Hover: "#F7F7F7"
property color bg1Active: '#EFEFEF'
property color bg1Border: '#d7d7d7'
property color bg1Border: '#E9E9E9'
property color bg2: "#FBFBFB"
property color bg2Base: "#FBFBFB"
property color bg2Hover: '#ffffff'
property color bg2Active: '#eeeeee'
property color bg2Border: '#cdcdcd'
property color bg2Border: '#E0E0E0'
property color subfg: "#5C5C5C"
property color fg: "#000000"
property color fg1: "#626262"
@@ -58,21 +58,20 @@ Singleton {
}
darkColors: QtObject {
id: darkColors
property color bgPanelFooter: "#1C1C1C"
property color bgPanelBody: '#616161'
property color bgPanelBody: '#242424'
property color bgPanelSeparator: "#191919"
property color bg0: "#1C1C1C"
property color bg0Border: "#404040"
property color bg1Base: "#2C2C2C"
property color bg1: '#9f9f9f'
property color bg1Hover: "#b3b3b3"
property color bg1Active: '#727272'
property color bg1Base: '#2C2C2C'
property color bg1: '#2C2C2C'
property color bg1Hover: "#292929"
property color bg1Active: '#252525'
property color bg1Border: '#bebebe'
property color bg2Base: "#313131"
property color bg2: '#8a8a8a'
property color bg2Hover: '#b1b1b1'
property color bg2Active: '#919191'
property color bg2Border: '#bdbdbd'
property color bg2: '#313131'
property color bg2Hover: '#363636'
property color bg2Active: '#2B2B2B'
property color bg2Border: '#404040'
property color subfg: "#CED1D7"
property color fg: "#FFFFFF"
property color fg1: "#D1D1D1"
@@ -87,38 +86,47 @@ Singleton {
}
colors: QtObject {
id: colors
// Special
property color shadow: ColorUtils.transparentize('#161616', 0.62)
property color ambientShadow: ColorUtils.transparentize("#000000", 0.75)
property color bgPanelFooterBase: ColorUtils.transparentize(root.dark ? root.darkColors.bgPanelFooter : root.lightColors.bgPanelFooter, root.panelBackgroundTransparency)
property color bgPanelFooter: ColorUtils.transparentize(root.dark ? root.darkColors.bgPanelFooter : root.lightColors.bgPanelFooter, root.panelLayerTransparency)
property color bgPanelBody: ColorUtils.transparentize(root.dark ? root.darkColors.bgPanelBody : root.lightColors.bgPanelBody, root.panelLayerTransparency)
property color bgPanelSeparator: ColorUtils.transparentize(root.dark ? root.darkColors.bgPanelSeparator : root.lightColors.bgPanelSeparator, root.backgroundTransparency)
property color bg0Opaque: root.dark ? root.darkColors.bg0 : root.lightColors.bg0
property color bg0: ColorUtils.transparentize(bg0Opaque, root.backgroundTransparency)
property color bgPanelFooterBase: root.dark ? root.darkColors.bg0 : root.lightColors.bg0
property color bgPanelFooterBackground: ColorUtils.transparentize(root.dark ? root.darkColors.bg0 : root.lightColors.bg0, root.panelBackgroundTransparency)
property color bgPanelFooter: ColorUtils.transparentize(bgPanelFooterBackground, root.panelLayerTransparency)
property color bgPanelBodyBase: root.dark ? root.darkColors.bgPanelBody : root.lightColors.bgPanelBody
property color bgPanelBody: ColorUtils.solveOverlayColor(bgPanelFooterBackground,bgPanelBodyBase, 1 - root.panelLayerTransparency)
property color bgPanelSeparator: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bgPanelSeparator : root.lightColors.bgPanelSeparator, 1 - root.panelBackgroundTransparency)
// Layer 0
property color bg0Base: root.dark ? root.darkColors.bg0 : root.lightColors.bg0
property color bg0: ColorUtils.transparentize(bg0Base, root.backgroundTransparency)
property color bg0Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg0Border : root.lightColors.bg0Border, root.backgroundTransparency)
property color bg1Base: root.dark ? root.darkColors.bg1Base : root.lightColors.bg1Base
property color bg1: ColorUtils.transparentize(root.dark ? root.darkColors.bg1 : root.lightColors.bg1, root.contentTransparency)
property color bg1Hover: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Hover : root.lightColors.bg1Hover, root.contentTransparency)
property color bg1Active: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Active : root.lightColors.bg1Active, root.contentTransparency)
property color bg1Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Border : root.lightColors.bg1Border, root.contentTransparency)
property color bg2Base: root.dark ? root.darkColors.bg2Base : root.lightColors.bg2Base
property color bg2: ColorUtils.transparentize(root.dark ? root.darkColors.bg2 : root.lightColors.bg2, root.contentTransparency)
property color bg2Hover: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Hover : root.lightColors.bg2Hover, root.contentTransparency)
property color bg2Active: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Active : root.lightColors.bg2Active, root.contentTransparency)
property color bg2Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Border : root.lightColors.bg2Border, root.contentTransparency)
// Layer 1
property color bg1Base: root.dark ? root.darkColors.bg1 : root.lightColors.bg1
property color bg1: ColorUtils.solveOverlayColor(bg0Base, bg1Base, 1 - root.contentTransparency)
property color bg1Hover: ColorUtils.solveOverlayColor(bg0Base, root.dark ? root.darkColors.bg1Hover : root.lightColors.bg1Hover, 1 - root.contentTransparency)
property color bg1Active: ColorUtils.solveOverlayColor(bg0Base, root.dark ? root.darkColors.bg1Active : root.lightColors.bg1Active, 1 - root.contentTransparency)
property color bg1Border: ColorUtils.solveOverlayColor(bg0Base, root.dark ? root.darkColors.bg1Border : root.lightColors.bg1Border, 1 - root.contentTransparency)
// Layer 2
property color bg2Base: root.dark ? root.darkColors.bg2 : root.lightColors.bg2
property color bg2: ColorUtils.solveOverlayColor(bgPanelBodyBase, bg2Base, 1 - root.contentTransparency)
property color bg2Hover: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bg2Hover : root.lightColors.bg2Hover, 1 - root.contentTransparency)
property color bg2Active: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bg2Active : root.lightColors.bg2Active, 1 - root.contentTransparency)
property color bg2Border: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bg2Border : root.lightColors.bg2Border, 1 - root.contentTransparency)
// Foreground / Text
property color subfg: root.dark ? root.darkColors.subfg : root.lightColors.subfg
property color fg: root.dark ? root.darkColors.fg : root.lightColors.fg
property color fg1: root.dark ? root.darkColors.fg1 : root.lightColors.fg1
property color inactiveIcon: root.dark ? root.darkColors.inactiveIcon : root.lightColors.inactiveIcon
property color link: root.dark ? root.darkColors.link : root.lightColors.link
// Controls
property color controlBgInactive: root.dark ? root.darkColors.controlBgInactive : root.lightColors.controlBgInactive
property color controlBg: root.dark ? root.darkColors.controlBg : root.lightColors.controlBg
property color controlBgHover: root.dark ? root.darkColors.controlBgHover : root.lightColors.controlBgHover
property color controlFg: root.dark ? root.darkColors.controlFg : root.lightColors.controlFg
property color inputBg: root.dark ? root.darkColors.inputBg : root.lightColors.inputBg
property color link: root.dark ? root.darkColors.link : root.lightColors.link
property color danger: "#C42B1C"
property color dangerActive: "#B62D1F"
property color warning: "#FF9900"
// Accent
property color accent: Appearance.colors.colPrimary
property color accentHover: Appearance.colors.colPrimaryHover
property color accentActive: Appearance.colors.colPrimaryActive
@@ -39,9 +39,10 @@ Button {
}
}
property color fgColor: {
if (!root.enabled) return root.colForegroundDisabled
if (root.checked) return root.colForegroundToggled
if (root.enabled) return root.colForeground
return root.colForegroundDisabled
return root.colForeground
}
property alias horizontalAlignment: buttonText.horizontalAlignment
font {
@@ -76,8 +76,9 @@ Menu {
contentItem: Item {
implicitWidth: menuListView.implicitWidth
implicitHeight: menuListView.implicitHeight
ListView {
WListView {
id: menuListView
interactive: contentHeight > height
anchors {
left: parent.left
right: parent.right
@@ -87,6 +88,7 @@ Menu {
topMargin: root.downDirection ? root.sourceEdgeMargin : root.margins
bottomMargin: root.downDirection ? root.margins : root.sourceEdgeMargin
}
clip: true
implicitHeight: contentHeight
implicitWidth: Array.from({
length: count
@@ -6,6 +6,7 @@ import Quickshell
import Quickshell.Hyprland
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
MenuItem {
@@ -14,11 +15,11 @@ MenuItem {
property color colBackground: ColorUtils.transparentize(Looks.colors.bg1)
property color colBackgroundHover: Looks.colors.bg2Hover
property color colBackgroundActive: Looks.colors.bg2Active
property color colBackgroundToggled: Looks.colors.accent
property color colBackgroundToggledHover: Looks.colors.accentHover
property color colBackgroundToggledActive: Looks.colors.accentActive
property color colBackgroundToggled: Looks.colors.bg2Hover
property color colBackgroundToggledHover: Looks.colors.bg2Active
property color colBackgroundToggledActive: Looks.colors.bg2Hover
property color colForeground: Looks.colors.fg
property color colForegroundToggled: Looks.colors.accentFg
property color colForegroundToggled: Looks.colors.fg
property color colForegroundDisabled: ColorUtils.transparentize(Looks.colors.subfg, 0.4)
property color color: {
if (!root.enabled)
@@ -70,27 +71,57 @@ MenuItem {
implicitHeight: Math.max(28, contentItem.implicitHeight) + topInset + bottomInset
implicitWidth: contentItem.implicitWidth + leftInset + rightInset + leftPadding + rightPadding
contentItem: RowLayout {
id: contentLayout
spacing: 12
FluentIcon {
id: buttonIcon
monochrome: true
implicitSize: 20
Layout.fillWidth: false
Layout.alignment: Qt.AlignVCenter
color: root.fgColor
visible: root.icon.name !== "";
icon: root.icon.name
contentItem: Item {
implicitWidth: contentLayout.implicitWidth
implicitHeight: contentLayout.implicitHeight
RowLayout {
id: contentLayout
anchors.fill: parent
spacing: 12
FluentIcon {
id: buttonIcon
monochrome: true
implicitSize: 20
Layout.fillWidth: false
Layout.alignment: Qt.AlignVCenter
color: root.fgColor
visible: root.icon.name !== ""
icon: root.icon.name
}
WText {
id: buttonText
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
text: root.text
horizontalAlignment: Text.AlignLeft
font.pixelSize: Looks.font.pixelSize.large
color: root.fgColor
}
}
WText {
id: buttonText
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
text: root.text
horizontalAlignment: Text.AlignLeft
font.pixelSize: Looks.font.pixelSize.large
color: root.fgColor
WFadeLoader {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: -root.leftPadding + width
}
shown: root.checked
sourceComponent: Rectangle {
implicitWidth: 3
implicitHeight: 3
radius: width / 2
color: Looks.colors.accent
property bool forceZeroHeight: true
height: forceZeroHeight ? 0 : Math.max(root.down ? 10 : 16, root.background.height - 18 * 2)
Component.onCompleted: {
forceZeroHeight = false;
}
Behavior on height {
animation: Looks.transition.resize.createObject(this)
}
}
}
}
}
@@ -12,6 +12,7 @@ Item {
id: root
property Item contentItem
property real radius: Looks.radius.large
property alias color: contentRect.color
property alias border: borderRect
property alias borderColor: borderRect.border.color
property alias borderWidth: borderRect.border.width
@@ -42,7 +43,7 @@ Item {
anchors.centerIn: parent
z: 0
color: Looks.colors.bgPanelFooterBase
color: Looks.colors.bgPanelFooterBackground
implicitWidth: contentItem.implicitWidth
implicitHeight: contentItem.implicitHeight
layer.enabled: true
@@ -0,0 +1,38 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.common
import qs.modules.common.widgets
Item {
id: root
property real padding: 9
property alias colBackground: background.color
property alias spacing: toolbarLayout.spacing
property alias radius: background.radius
default property alias data: toolbarLayout.data
implicitWidth: background.implicitWidth
implicitHeight: background.implicitHeight
Rectangle {
id: background
anchors.fill: parent
implicitHeight: 50
implicitWidth: toolbarLayout.implicitWidth + root.padding * 2
radius: Looks.radius.large
color: Looks.colors.bg0Base
border.width: 1
border.color: Looks.colors.bg1Border
RowLayout {
id: toolbarLayout
spacing: 4
anchors {
fill: parent
margins: root.padding
}
}
}
}
@@ -0,0 +1,8 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.common
WButton {
implicitHeight: 32
radius: Looks.radius.medium
}
@@ -0,0 +1,16 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.common
WToolbarButton {
id: root
implicitWidth: height
contentItem: Item {
FluentIcon {
anchors.centerIn: parent
icon: root.icon.name
implicitSize: 18
color: root.fgColor
}
}
}
@@ -0,0 +1,21 @@
import QtQuick
import QtQuick.Controls
import qs.modules.common
TabButton {
id: root
implicitWidth: 38
implicitHeight: 32
padding: 0
background: null
contentItem: Item {
FluentIcon {
anchors.centerIn: parent
icon: root.icon.name
color: root.icon.color
implicitSize: 18
}
}
}
@@ -0,0 +1,11 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.common
Rectangle {
Layout.leftMargin: 4
Layout.rightMargin: 4
implicitHeight: 24
implicitWidth: 1
color: Looks.colors.bg0Border
}
@@ -0,0 +1,53 @@
import QtQuick
import QtQuick.Controls
import qs.modules.common
import qs.modules.common.functions
TabBar {
id: root
implicitHeight: 32
background: Rectangle {
radius: Looks.radius.medium
color: Looks.colors.bgPanelFooter
border.color: ColorUtils.transparentize(Looks.colors.bg0Border, 0.7)
border.width: 1
// Indicator
Rectangle {
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
leftMargin: root.currentIndex * (root.width / root.count)
Behavior on leftMargin {
animation: Looks.transition.resize.createObject(this)
}
}
radius: Looks.radius.medium
color: Looks.colors.bg2Base
border.color: Looks.colors.bg0Border
border.width: 1
width: root.width / root.count
Rectangle {
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 1
}
implicitWidth: pressDetector.containsPress ? 16 : 12
implicitHeight: 3
radius: height / 2
color: Looks.colors.accent
}
}
}
MouseArea {
id: pressDetector
z: 9999
anchors.fill: parent
acceptedButtons: Qt.LeftButton
}
}
@@ -79,7 +79,7 @@ BodyRectangle {
id: dayButton
required property var model
checked: model.today
enabled: hovered || calendarView.scrolling || checked || model.month === calendarView.focusedMonth
enabled: hovered || checked || model.month === calendarView.focusedMonth
implicitWidth: calendarView.buttonSize
implicitHeight: calendarView.buttonSize
radius: height / 2
@@ -7,7 +7,7 @@ import qs.modules.common.widgets
import qs.modules.common.functions
import qs.modules.waffle.looks
BodyRectangle {
FooterRectangle {
id: root
anchors.fill: parent
implicitHeight: 230
@@ -15,6 +15,9 @@ MouseArea {
required property var notification
property bool expanded: notification.actions.length > 0
property string groupExpandControlMessage: ""
readonly property bool isPopup: notification?.popup ?? false
signal groupExpandToggle
hoverEnabled: true
@@ -56,13 +59,13 @@ MouseArea {
Rectangle {
id: contentItem
width: parent.width
color: Looks.colors.bgPanelBody
radius: Looks.radius.medium
color: root.isPopup ? Looks.colors.bg0 : Looks.colors.bgPanelBody
radius: root.isPopup ? Looks.radius.large : Looks.radius.medium
property real padding: 12
implicitHeight: notificationContent.implicitHeight + padding * 2
implicitWidth: notificationContent.implicitWidth + padding * 2
border.width: 1
border.color: ColorUtils.applyAlpha(Looks.colors.ambientShadow, 0.1)
border.color: root.isPopup ? Looks.colors.bg2Border : Looks.colors.bgPanelSeparator
Behavior on x {
animation: Looks.transition.enter.createObject(this)
@@ -157,9 +160,9 @@ MouseArea {
NotificationHeaderButton {
Layout.rightMargin: 4
opacity: root.containsMouse ? 1 : 0
opacity: (root.containsMouse || root.isPopup) ? 1 : 0
icon.name: "dismiss"
implicitSize: 12
implicitSize: 14
onClicked: root.dismiss()
}
}
@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.waffle.looks
import qs.modules.waffle.notificationCenter
Scope {
id: notificationPopup
PanelWindow {
id: root
visible: (Notifications.popupList.length > 0) && !GlobalStates.screenLocked
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? null
WlrLayershell.namespace: "quickshell:notificationPopup"
WlrLayershell.layer: WlrLayer.Overlay
exclusiveZone: 0
anchors {
top: true
right: true
bottom: true
}
mask: Region {
item: listview.contentItem
}
color: "transparent"
implicitWidth: listview.implicitWidth
WListView {
id: listview
anchors {
bottom: parent.bottom
right: parent.right
left: parent.left
}
leftMargin: 16
rightMargin: 16
topMargin: 16
bottomMargin: 16
height: Math.min(contentItem.height + topMargin + bottomMargin, parent.height)
width: parent.width - Appearance.sizes.elevationMargin * 2
implicitWidth: 396
spacing:12
model: ScriptModel {
values: Notifications.popupList
}
delegate: WSingleNotification {
required property var modelData
notification: modelData
width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin
}
}
}
}
@@ -135,7 +135,7 @@ Rectangle {
}
BodyRectangle {
implicitHeight: 80
color: Looks.colors.bgPanelFooterBase
color: Looks.colors.bgPanelFooterBackground
RowLayout {
anchors.fill: parent
anchors.margins: 24
@@ -0,0 +1,62 @@
import QtQuick
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
Item {
id: root
required property int regionX
required property int regionY
required property int regionWidth
required property int regionHeight
property bool dashed: true
property color borderColor: "#ffffff"
property color overlayColor: ColorUtils.transparentize("#000000", 1)
Component.onCompleted: overlayColor = ColorUtils.transparentize("#000000", 0.4)
Behavior on overlayColor {
ColorAnimation {
duration: 150
easing.type: Easing.InOutQuad
}
}
// Overlay to darken screen
// Base dark overlay around region
Rectangle {
id: darkenOverlay
z: 1
anchors {
left: parent.left
top: parent.top
leftMargin: root.regionX - darkenOverlay.border.width
topMargin: root.regionY - darkenOverlay.border.width
}
width: root.regionWidth + darkenOverlay.border.width * 2
height: root.regionHeight + darkenOverlay.border.width * 2
color: "transparent"
border.color: root.overlayColor
border.width: Math.max(root.width, root.height)
}
// Selection border
DashedBorder {
id: border
z: 2
visible: root.regionWidth > 0 && root.regionHeight > 0
anchors {
left: parent.left
top: parent.top
leftMargin: Math.round(root.regionX - borderWidth)
topMargin: Math.round(root.regionY - borderWidth)
}
width: Math.round(root.regionWidth + borderWidth * 2)
height: Math.round(root.regionHeight + borderWidth * 2)
color: root.borderColor
dashLength: 4
gapLength: root.dashed ? 3 : 0
borderWidth: 1
}
}
@@ -0,0 +1,374 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.synchronizer
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.utils
import qs.modules.common.widgets
import qs.modules.waffle.looks
PanelWindow {
id: root
enum MediaType {
Image,
Video
}
enum ImageAction {
Copy,
Menu,
CharRecognition,
Search
}
enum VideoAction {
Record,
RecordWithSound
}
enum SelectionMode {
Rect,
Window
}
function close() {
root.closed();
}
property var mediaType: WRegionSelectionPanel.MediaType.Image
property var imageAction: WRegionSelectionPanel.ImageAction.Copy
property var selectionMode: WRegionSelectionPanel.SelectionMode.Rect
visible: false
color: "transparent"
WlrLayershell.namespace: "quickshell:regionSelector"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
exclusionMode: ExclusionMode.Ignore
anchors {
left: true
right: true
top: true
bottom: true
}
// Hyprland stuff
readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen)
readonly property real monitorScale: hyprlandMonitor.scale
readonly property var windows: [...HyprlandData.windowList].sort((a, b) => {
// Sort floating=true windows before others
if (a.floating === b.floating)
return 0;
return a.floating ? -1 : 1;
})
property string screenshotDir: Directories.screenshotTemp
property string screenshotPath: `${root.screenshotDir}/image-${screen.name}`
TempScreenshotProcess {
id: screenshotProc
running: true
screen: root.screen
screenshotDir: root.screenshotDir
screenshotPath: root.screenshotPath
onExited: (exitCode, exitStatus) => {
root.preparationDone = true;
}
}
property bool preparationDone: false
onPreparationDoneChanged: {
if (!preparationDone)
return;
root.visible = true;
}
function getScreenshotAction() {
switch (root.mediaType) {
case WRegionSelectionPanel.MediaType.Image:
switch (root.imageAction) {
case WRegionSelectionPanel.ImageAction.Copy:
return ScreenshotAction.Action.Copy;
case WRegionSelectionPanel.ImageAction.Menu:
return ScreenshotAction.Action.Edit;
case WRegionSelectionPanel.ImageAction.CharRecognition:
return ScreenshotAction.Action.CharRecognition;
case WRegionSelectionPanel.ImageAction.Search:
return ScreenshotAction.Action.Search;
default:
return ScreenshotAction.Action.Copy;
}
break;
case WRegionSelectionPanel.MediaType.Video:
switch (root.videoAction) {
case WRegionSelectionPanel.VideoAction.Record:
return ScreenshotAction.Action.Record;
case WRegionSelectionPanel.VideoAction.RecordWithSound:
return ScreenshotAction.Action.RecordWithSound;
}
}
}
Process {
id: snipProc
}
ScreencopyView {
id: screencopyView
anchors.fill: parent
live: false
captureSource: root.screen
focus: root.visible
Keys.onPressed: event => { // Esc to close
if (event.key === Qt.Key_Escape) {
root.close();
} else if (event.key === Qt.Key_E && event.modifiers & Qt.ControlModifier) {
if (root.imageAction === WRegionSelectionPanel.ImageAction.Menu) {
root.imageAction = WRegionSelectionPanel.ImageAction.Copy;
} else {
root.imageAction = WRegionSelectionPanel.ImageAction.Menu;
}
}
}
DragManager {
id: dragArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.CrossCursor
property bool isWindowSelection: root.selectionMode === WRegionSelectionPanel.SelectionMode.Window
property var hoveredWindow: root.windows.find(w => {
const inCurrentWorkspace = w.workspace.id === HyprlandData.activeWorkspace.id;
const withinXRange = w.at[0] <= dragArea.mouseX && dragArea.mouseX <= w.at[0] + w.size[0];
const withinYRange = w.at[1] <= dragArea.mouseY && dragArea.mouseY <= w.at[1] + w.size[1];
return inCurrentWorkspace && withinXRange && withinYRange;
})
property int winPadding: 1
property int selectionX: isWindowSelection ? ((hoveredWindow?.at[0] ?? 0) - winPadding) : regionTopLeftX
property int selectionY: isWindowSelection ? ((hoveredWindow?.at[1] ?? 0) - winPadding) : regionTopLeftY
property int selectionWidth: isWindowSelection ? ((hoveredWindow?.size[0] ?? 0) + winPadding * 2) : regionWidth
property int selectionHeight: isWindowSelection ? ((hoveredWindow?.size[1] ?? 0) + winPadding * 2) : regionHeight
onDragReleased: (diffX, diffY) => {
if (selectionWidth === 0 || selectionHeight === 0) {
return;
}
const screenshotDir = Config.options.screenSnip.savePath !== "" ? Config.options.screenSnip.savePath : "";
const screenshotAction = root.getScreenshotAction();
const command = ScreenshotAction.getCommand(dragArea.selectionX * root.monitorScale //
, dragArea.selectionY * root.monitorScale //
, dragArea.selectionWidth * root.monitorScale//
, dragArea.selectionHeight * root.monitorScale //
, root.screenshotPath //
, screenshotAction //
, screenshotDir); // yo wtf is this formatting qmlls do be funnie
snipProc.command = command;
// Image post-processing
snipProc.startDetached();
root.close();
}
WRectangularSelection {
id: rectangularSelection
anchors.fill: parent
regionX: dragArea.selectionX
regionY: dragArea.selectionY
regionWidth: dragArea.selectionWidth
regionHeight: dragArea.selectionHeight
dashed: root.selectionMode === WRegionSelectionPanel.SelectionMode.Rect
}
RegionSelectionOptionsToolbar {
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: 12
}
}
}
}
component RegionSelectionOptionsToolbar: WToolbar {
// Image/video
WToolbarTabBar {
currentIndex: switch (root.mediaType) {
case WRegionSelectionPanel.MediaType.Image:
return 0;
case WRegionSelectionPanel.MediaType.Video:
return 1;
default:
return 0;
}
WToolbarIconTabButton {
icon.name: "camera"
icon.color: Looks.colors.fg
}
WToolbarIconTabButton {
icon.name: "video"
icon.color: Looks.colors.fg
}
onCurrentIndexChanged: {
switch (currentIndex) {
case 0:
root.mediaType = WRegionSelectionPanel.MediaType.Image;
break;
case 1:
root.mediaType = WRegionSelectionPanel.MediaType.Video;
break;
}
}
WToolTip {
text: Translation.tr("Snip")
}
}
// Selection type
WToolbarButton {
id: selectionTypeBtn
implicitWidth: selectionTypeBtnRow.implicitWidth + 11 * 2
leftPadding: 11
rightPadding: 11
onClicked: {
selectionTypeMenu.visible = !selectionTypeMenu.visible;
}
contentItem: Row {
id: selectionTypeBtnRow
spacing: 4
FluentIcon {
anchors.verticalCenter: parent.verticalCenter
icon: switch (root.selectionMode) {
case WRegionSelectionPanel.SelectionMode.Rect:
return "crop";
case WRegionSelectionPanel.SelectionMode.Window:
return "calendar-add";
default:
return "crop";
}
implicitSize: 18
}
FluentIcon {
anchors {
top: parent.top
topMargin: (parent.height - height) / 2 + (selectionTypeBtn.down ? 2 : 0)
Behavior on topMargin {
animation: Looks.transition.enter.createObject(this)
}
}
icon: "chevron-down"
implicitSize: 12
}
}
WMenu {
id: selectionTypeMenu
onClosed: screencopyView.focus = true
x: -margins
y: -margins - (selectionTypeBtn.parent.height - selectionTypeBtn.height) - 16
topMargin: -6
height: implicitHeight + sourceEdgeMargin
color: Looks.colors.bg1Base
Action {
icon.name: "crop"
text: Translation.tr("Rectangle")
checked: root.selectionMode === WRegionSelectionPanel.SelectionMode.Rect
onTriggered: {
root.selectionMode = WRegionSelectionPanel.SelectionMode.Rect;
}
}
Action {
icon.name: "calendar-add"
text: Translation.tr("Window")
checked: root.selectionMode === WRegionSelectionPanel.SelectionMode.Window
onTriggered: {
root.selectionMode = WRegionSelectionPanel.SelectionMode.Window;
}
}
}
WToolTip {
text: Translation.tr("Snipping area")
}
}
// Markup
WToolbarIconButton {
icon.name: "image-edit"
enabled: root.mediaType === WRegionSelectionPanel.MediaType.Image
checked: root.imageAction === WRegionSelectionPanel.ImageAction.Menu
onClicked: {
if (root.imageAction === WRegionSelectionPanel.ImageAction.Menu) {
root.imageAction = WRegionSelectionPanel.ImageAction.Copy;
} else {
root.imageAction = WRegionSelectionPanel.ImageAction.Menu;
}
}
WToolTip {
text: Translation.tr("Quick markup (Ctrl+E)")
}
}
WToolbarSeparator {}
// Tools
WToolbarIconButton {
icon.name: "search-visual"
checked: root.imageAction === WRegionSelectionPanel.ImageAction.Search
onClicked: {
if (root.imageAction === WRegionSelectionPanel.ImageAction.Search && root.mediaType === WRegionSelectionPanel.MediaType.Image) {
root.imageAction = WRegionSelectionPanel.ImageAction.Copy;
} else {
root.mediaType = WRegionSelectionPanel.MediaType.Image;
root.imageAction = WRegionSelectionPanel.ImageAction.Search;
}
}
WToolTip {
text: Translation.tr("Image search")
}
}
WToolbarIconButton {
icon.name: "eyedropper"
onClicked: {
Quickshell.execDetached(["bash", "-c", "sleep 0.2; hyprpicker -a"]);
root.closed();
}
WToolTip {
text: Translation.tr("Color picker")
}
}
WToolbarIconButton {
icon.name: "scan-text"
checked: root.imageAction === WRegionSelectionPanel.ImageAction.CharRecognition
onClicked: {
if (root.imageAction === WRegionSelectionPanel.ImageAction.CharRecognition && root.mediaType === WRegionSelectionPanel.MediaType.Image) {
root.imageAction = WRegionSelectionPanel.ImageAction.Copy;
} else {
root.mediaType = WRegionSelectionPanel.MediaType.Image;
root.imageAction = WRegionSelectionPanel.ImageAction.CharRecognition;
}
}
WToolTip {
text: Translation.tr("Text extractor")
}
}
WToolbarSeparator {}
WToolbarIconButton {
icon.name: "dismiss"
onClicked: root.close()
WToolTip {
text: Translation.tr("Close (Esc)")
}
}
}
}
@@ -0,0 +1,106 @@
pragma ComponentBehavior: Bound
import qs
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Hyprland
Scope {
id: root
function dismiss() {
GlobalStates.regionSelectorOpen = false;
}
Loader {
id: regionSelectorLoader
active: GlobalStates.regionSelectorOpen
sourceComponent: WRegionSelectionPanel {
onClosed: root.dismiss()
}
}
function screenshot() {
GlobalStates.regionSelectorOpen = true;
}
function ocr() {
GlobalStates.regionSelectorOpen = true;
regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Image;
regionSelectorLoader.item.imageAction = WRegionSelectionPanel.ImageAction.CharRecognition;
}
function record() {
GlobalStates.regionSelectorOpen = true;
regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Video;
regionSelectorLoader.item.videoAction = WRegionSelectionPanel.VideoAction.Record;
}
function recordWithSound() {
GlobalStates.regionSelectorOpen = true;
regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Video;
regionSelectorLoader.item.videoAction = WRegionSelectionPanel.VideoAction.RecordWithSound;
}
function search() {
GlobalStates.regionSelectorOpen = true;
regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Image;
regionSelectorLoader.item.imageAction = WRegionSelectionPanel.ImageAction.Search;
}
IpcHandler {
target: "region"
function screenshot() {
root.screenshot();
}
function ocr() {
root.ocr();
}
function record() {
root.record();
}
function recordWithSound() {
root.recordWithSound();
}
function search() {
root.search();
}
}
GlobalShortcut {
name: "regionScreenshot"
description: "Takes a screenshot of the selected region"
onPressed: root.screenshot()
}
GlobalShortcut {
name: "regionSearch"
description: "Searches the selected region"
onPressed: root.search()
}
GlobalShortcut {
name: "regionOcr"
description: "Recognizes text in the selected region"
onPressed: root.ocr()
}
GlobalShortcut {
name: "regionRecord"
description: "Records the selected region"
onPressed: root.record()
}
GlobalShortcut {
name: "regionRecordWithSound"
description: "Records the selected region with sound"
onPressed: root.recordWithSound()
}
}
@@ -86,7 +86,7 @@ WBarAttachedPanelContent {
id: searchBar
Layout.fillWidth: true
implicitWidth: 832 // TODO: Make sizes naturally inferred
horizontalPadding: root.searching ? 24 : 32
horizontalPadding: 32
// verticalPadding: root.searching ? 32 : 16 // TODO: make this not nuke the panel
Synchronizer on searching {
property alias target: root.searching
@@ -44,7 +44,7 @@ WChoiceButton {
Layout.fillHeight: true
horizontalPadding: 10
verticalPadding: 11
implicitHeight: root.firstEntry ? 62 : 36
implicitHeight: Math.max(root.firstEntry ? 62 : 36, entryContentRow.implicitHeight + 8 * 2)
implicitWidth: entryContentRow.implicitWidth + leftPadding + rightPadding
topRightRadius: 0
bottomRightRadius: 0
@@ -54,6 +54,7 @@ WChoiceButton {
id: entryContentRow
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
spacing: 8
@@ -102,6 +103,7 @@ WChoiceButton {
text: root.entry.name
font.pixelSize: Looks.font.pixelSize.large
maximumLineCount: 2
elide: Text.ElideRight
}
WText {
@@ -109,6 +111,7 @@ WChoiceButton {
visible: root.firstEntry
text: root.entry.type
color: Looks.colors.accentUnfocused
elide: Text.ElideRight
}
}
@@ -15,6 +15,7 @@ RowLayout {
id: root
property int maxResultsPerCategory: 4
property int resultLimit: 20
property StartMenuContext context
property int currentIndex: context.currentIndex
onCurrentIndexChanged: {
@@ -99,21 +100,33 @@ RowLayout {
// Collect max 4 per category
var categorizedResults = [];
categories.forEach(category => {
let categoriesArray = Array.from(categories);
let totalCount = 0;
for (let c = 0; c < categoriesArray.length; c++) {
let category = categoriesArray[c];
let count = 0;
for (let i = 0; i < allResults.length; i++) {
if (allResults[i].type === category) {
if (totalCount >= root.resultLimit) {
break;
}
const entry = allResults[i];
const tweakedEntry = searchResultComp.createObject(null, Object.assign({}, entry));
tweakedEntry.category = categorizedResults.length === 0 ? Translation.tr("Best match") : entry.type
tweakedEntry.category = categorizedResults.length === 0 ? Translation.tr("Best match") : entry.type;
categorizedResults.push(tweakedEntry); // Section header
count++;
totalCount++;
if (count >= root.maxResultsPerCategory) {
break;
}
}
}
});
if (totalCount >= root.resultLimit) {
break;
}
}
// print(JSON.stringify(categorizedResults, null, 2));
return categorizedResults;
}
@@ -28,13 +28,13 @@ GridLayout {
}), aggAppCatComp.createObject(null, {
name: Translation.tr("Creativity"),
categories: ["AudioVideo", "Graphics"]
}), aggAppCatComp.createObject(null, {
name: Translation.tr("Other"),
categories: ["Game"]
}), aggAppCatComp.createObject(null, {
name: Translation.tr("System"),
categories: ["Settings", "System"]
})
}), aggAppCatComp.createObject(null, {
name: Translation.tr("Other"),
categories: ["Game"]
}),
]
Repeater {
@@ -16,29 +16,43 @@ Rectangle {
id: root
color: ColorUtils.transparentize(Looks.colors.bg1Base, 0.5)
property bool draggingWindow: false
property real openProgress: 0
property Item hoveredWorkspace: null
signal closed
Component.onCompleted: {
openAnim.start();
}
function close() {
closeAnim.start();
}
PropertyAnimation {
id: openAnim
target: root
property: "openProgress"
to: 1
duration: 200
duration: 250
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
PropertyAnimation {
SequentialAnimation {
id: closeAnim
target: root
property: "openProgress"
to: 0
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
PropertyAnimation {
target: root
property: "openProgress"
to: 0
duration: 250
easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
}
ScriptAction {
script: {
root.closed();
}
}
}
// Windows
@@ -81,8 +95,18 @@ Rectangle {
return resultLayout;
}
MouseArea {
z: 0
anchors.fill: parent
onClicked: {
GlobalStates.overviewOpen = false;
}
}
// Windows
WListView {
id: windowListView
z: root.openProgress == 1 ? 2 : 1
anchors {
left: parent.left
right: parent.right
@@ -96,9 +120,8 @@ Rectangle {
rightMargin: root.padding
height: Math.min(contentHeight + topMargin + bottomMargin, root.height - (wsBorder.height + 16))
interactive: height < contentHeight
clip: true
interactive: (height < contentHeight) && !root.draggingWindow
clip: root.openProgress > 0.99 && !root.draggingWindow
model: ScriptModel {
values: root.arrangedToplevels
@@ -107,7 +130,7 @@ Rectangle {
id: clientRow
required property var modelData
spacing: root.spacing
anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenter: parent?.horizontalCenter ?? undefined
Repeater {
model: ScriptModel {
@@ -117,24 +140,61 @@ Rectangle {
id: clientGridArea
required property int index
required property var modelData
implicitWidth: windowItem.implicitWidth
implicitHeight: windowItem.implicitHeight
implicitWidth: windowItem.openedSize.width
implicitHeight: windowItem.openedSize.height + windowItem.titleBarImplicitHeight
TaskViewWindow {
id: windowItem
z: 9999
drag {
target: this
z: Drag.active ? 2 : 1
opacity: openAnim.running ? root.openProgress : 1
property int mappedX: {
// print("AAAWAWAAWAWWA: ", -(clientRow.x + clientGridArea.x + root.padding));
var rootPosToThis = -(clientRow.x + clientGridArea.x + root.padding);
return rootPosToThis + hyprlandClient.at[0];
}
property int mappedY: {
// print("AAAWAWAAWAWWA YYYY YUIUSDFOIU: ", clientRow.y + windowListView.y + root.padding + windowItem.titleBarImplicitHeight)
var rootPosToThis = -(clientRow.y + windowListView.y + root.padding + windowItem.titleBarImplicitHeight);
return rootPosToThis + hyprlandClient.at[1];
}
property int openedX: 0
property int openedY: 0
// property int openedX: Drag.active ? (dragHandler.xAxis.activeValue) : 0
// property int openedY: Drag.active ? (dragHandler.yAxis.activeValue) : 0
scaleSize: (root.openProgress > 0 && !closeAnim.running)
x: mappedX + (openedX - mappedX) * root.openProgress
y: mappedY + (openedY - mappedY) * root.openProgress
droppable: root.hoveredWorkspace !== null
Drag.active: dragHandler.active
Drag.hotSpot.x: mouseX
Drag.hotSpot.y: mouseY
DragHandler {
id: dragHandler
target: null
xAxis.onActiveValueChanged: {
windowItem.openedX = dragHandler.xAxis.activeValue;
}
yAxis.onActiveValueChanged: {
windowItem.openedY = dragHandler.yAxis.activeValue;
}
onActiveChanged: {
if (drag.active) {
parent = root;
if (active) {
root.draggingWindow = true;
} else {
parent = clientGridArea;
x = 0;
y = 0;
root.draggingWindow = false;
if (root.hoveredWorkspace !== null && root.hoveredWorkspace.workspace !== windowItem.hyprlandClient.workspace.id) {
Hyprland.dispatch(`movetoworkspacesilent ${root.hoveredWorkspace.workspace}, address:${windowItem.hyprlandClient.address}`);
} else {
windowItem.openedX = 0;
windowItem.openedY = 0;
}
}
}
}
Layout.alignment: Qt.AlignTop
maxHeight: root.maxWindowHeight
maxWidth: root.maxWindowWidth
@@ -148,6 +208,7 @@ Rectangle {
// Workspaces
Rectangle {
id: wsBorder
z: root.openProgress == 1 ? 1 : 2
property real sourceEdgeMargin: -(height + 8) + root.openProgress * (height + 16)
anchors {
left: parent.left
@@ -170,7 +231,7 @@ Rectangle {
anchors.fill: parent
anchors.margins: wsBorder.border.width
radius: wsBorder.radius - wsBorder.border.width
color: Looks.colors.bgPanelFooterBase
color: Looks.colors.bgPanelFooterBackground
implicitHeight: 174
@@ -210,9 +271,29 @@ Rectangle {
}
}
delegate: TaskViewWorkspace {
id: workspaceItem
required property int index
workspace: index + 1
newWorkspace: index == workspaceIndexModel.count - 1
droppable: root.hoveredWorkspace === workspaceItem
DropArea {
anchors.fill: parent
onEntered: drag => {
root.hoveredWorkspace = workspaceItem;
}
onExited: {
if (root.hoveredWorkspace === workspaceItem) {
root.hoveredWorkspace = null;
}
}
}
onClicked: {
GlobalStates.overviewOpen = false;
root.closed(); // Close immediately to avoid weird animations
Hyprland.dispatch(`workspace ${workspaceItem.workspace}`);
}
}
}
}
@@ -25,47 +25,60 @@ WMouseAreaButton {
property string iconName: AppSearch.guessIcon(hyprlandClient?.class)
color: containsMouse ? Looks.colors.bg1Base : Looks.colors.bgPanelFooterBase
borderColor: Looks.colors.bg2Border
color: drag.active ? ColorUtils.transparentize(Looks.colors.bg1Base) : (containsMouse ? Looks.colors.bg1Base : Looks.colors.bgPanelFooterBackground)
borderColor: ColorUtils.transparentize(Looks.colors.bg2Border, drag.active ? 1 : 0)
radius: Looks.radius.xLarge
property size size: WindowLayout.scaleWindow(hyprlandClient, maxWidth, maxHeight)
property real titleBarImplicitHeight: titleBar.implicitHeight
property bool scaleSize: true
property size openedSize: WindowLayout.scaleWindow(hyprlandClient, maxWidth, maxHeight);
property size fullSize: Qt.size(hyprlandClient?.size[0] ?? maxWidth, hyprlandClient?.size[1] ?? maxHeight)
property size size: scaleSize ? openedSize : fullSize
implicitWidth: Math.max(Math.round(contentItem.implicitWidth), 138)
implicitHeight: Math.round(contentItem.implicitHeight)
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
maskSource: Item {
width: root.background.width
height: root.background.height
radius: root.background.radius
Rectangle {
radius: root.background.radius
anchors {
fill: parent
topMargin: root.drag.active ? root.titleBarImplicitHeight : 0
}
}
}
}
scale: (root.pressedButtons & Qt.LeftButton) ? 0.95 : 1
property bool droppable: false
scale: (root.pressedButtons & Qt.LeftButton || root.Drag.active) ? (droppable ? 0.4 : 0.95) : 1
Behavior on scale {
NumberAnimation {
id: scaleAnim
duration: 300
duration: 200
easing.type: Easing.OutExpo
}
}
function closeWindow() {
Hyprland.dispatch(`closewindow address:${root.hyprlandClient?.address}`)
Hyprland.dispatch(`closewindow address:${root.hyprlandClient?.address}`);
}
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
onClicked: (event) => {
onClicked: event => {
if (event.button === Qt.LeftButton) {
GlobalStates.overviewOpen = false
Hyprland.dispatch(`focuswindow address:${root.hyprlandClient?.address}`)
GlobalStates.overviewOpen = false;
Hyprland.dispatch(`focuswindow address:${root.hyprlandClient?.address}`);
GlobalStates.overviewOpen = false;
} else if (event.button === Qt.MiddleButton) {
root.closeWindow();
event.accepted = true;
} else if (event.button === Qt.RightButton) {
if (!windowMenu.visible) windowMenu.popup();
else windowMenu.close();
if (!windowMenu.visible)
windowMenu.popup();
else
windowMenu.close();
}
}
@@ -77,6 +90,8 @@ WMouseAreaButton {
spacing: 0
RowLayout {
id: titleBar
opacity: root.drag.active ? 0 : 1
spacing: 8
WAppIcon {
Layout.leftMargin: 10
@@ -104,6 +119,14 @@ WMouseAreaButton {
Layout.alignment: Qt.AlignHCenter
implicitWidth: Math.round(root.size.width)
implicitHeight: Math.round(root.size.height)
constraintSize: Qt.size(Math.round(root.size.width), Math.round(root.size.height))
Behavior on implicitWidth {
animation: Looks.transition.enter.createObject(this)
}
Behavior on implicitHeight {
animation: Looks.transition.enter.createObject(this)
}
captureSource: root.toplevel ?? null
live: true
@@ -120,7 +143,7 @@ WMouseAreaButton {
icon.name: isPinned ? "checkmark" : "empty"
text: Translation.tr("Show this window on all desktops")
onTriggered: {
Hyprland.dispatch(`pin address:${root.hyprlandClient?.address}`)
Hyprland.dispatch(`pin address:${root.hyprlandClient?.address}`);
}
}
Action {
@@ -16,6 +16,7 @@ WMouseAreaButton {
required property int workspace
property bool newWorkspace: false
property bool droppable: false
readonly property bool isActiveWorkspace: HyprlandData.activeWorkspace?.id === root.workspace
readonly property real screenWidth: QsWindow.window?.width ?? 0
@@ -28,12 +29,7 @@ WMouseAreaButton {
height: ListView.view?.height ?? 100
implicitWidth: 244 // for now
onClicked: {
GlobalStates.overviewOpen = false;
Hyprland.dispatch(`workspace ${root.workspace}`);
}
colBackground: ColorUtils.transparentize(Looks.colors.bg2, isActiveWorkspace ? 0 : 1)
colBackground: ColorUtils.transparentize(Looks.colors.bg2, (isActiveWorkspace || droppable) ? 0 : 1)
Behavior on color {
animation: Looks.transition.color.createObject(this)
}
@@ -119,6 +115,14 @@ WMouseAreaButton {
icon: "add"
}
}
Rectangle {
z: 2
visible: root.droppable && !root.newWorkspace
anchors.fill: parent
color: Looks.colors.accent
opacity: 0.2
}
}
}
@@ -23,7 +23,14 @@ Scope {
Loader {
id: panelLoader
required property var modelData
active: GlobalStates.overviewOpen
active: false
Connections {
target: GlobalStates
function onOverviewOpenChanged() {
if (GlobalStates.overviewOpen)
panelLoader.active = true;
}
}
sourceComponent: PanelWindow {
id: root
property string searchingText: ""
@@ -33,7 +40,7 @@ Scope {
WlrLayershell.namespace: "quickshell:wTaskView"
WlrLayershell.layer: WlrLayer.Overlay
// WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent"
anchors {
@@ -44,7 +51,26 @@ Scope {
}
TaskViewContent {
id: taskViewContent
anchors.fill: parent
Component.onCompleted: {
taskViewContent.forceActiveFocus();
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
GlobalStates.overviewOpen = false;
}
}
Connections {
target: GlobalStates
function onOverviewOpenChanged() {
if (!GlobalStates.overviewOpen)
taskViewContent.close();
}
}
onClosed: panelLoader.active = false
}
}
}