Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce1a48639f | |||
| 4d19cfe87d | |||
| cd5eb171c8 | |||
| 0ff5c32c51 | |||
| 7e3109f758 | |||
| f5a0b763d5 | |||
| 4f813a2de7 | |||
| 7784cfd99b | |||
| 7e5b5ffed5 | |||
| 3f969d9447 | |||
| b68c139d8d | |||
| 175c3463f7 | |||
| d446be5fbd | |||
| e31ff0aa27 | |||
| a0b552e796 | |||
| b9a590be69 | |||
| 16642e7d02 | |||
| b8d825843d | |||
| 62027782a7 |
@@ -0,0 +1,4 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug
|
||||||
|
target
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 294 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 MiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 298 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 647 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 294 KiB |
@@ -1,6 +1,5 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
import QtCore
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -8,46 +7,50 @@ import Quickshell
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias gifFolder: adapter.gifFolder
|
|
||||||
property string configDir: Quickshell.env("HOME") + "/.config/I-DeskPet"
|
property string configDir: Quickshell.env("HOME") + "/.config/I-DeskPet"
|
||||||
property string configPath: configDir + "/config.json"
|
property string configPath: configDir + "/config.json"
|
||||||
|
property alias gifFolder: adapter.gifFolder
|
||||||
signal folderChanged()
|
property alias maxScaling: adapter.maxScale
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: dirCheck
|
id: dirCheck
|
||||||
running: true
|
|
||||||
command: ["test", "-d", root.configDir]
|
command: ["test", "-d", root.configDir]
|
||||||
|
running: true
|
||||||
|
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
dirCreate.running = true
|
console.log("creating dir");
|
||||||
|
dirCreate.running = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: dirCreate
|
id: dirCreate
|
||||||
running: false
|
|
||||||
command: ["mkdir", "-p", root.configDir]
|
command: ["mkdir", "-p", root.configDir]
|
||||||
|
running: false
|
||||||
|
|
||||||
onExited: function (): void {
|
onExited: function (): void {
|
||||||
console.log("Created config directory:", root.configDir)
|
console.log("Created config directory:", root.configDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileView {
|
FileView {
|
||||||
id: watcher
|
id: watcher
|
||||||
path: root.configPath
|
|
||||||
|
|
||||||
|
path: root.configPath
|
||||||
watchChanges: true
|
watchChanges: true
|
||||||
|
|
||||||
|
onAdapterUpdated: writeAdapter()
|
||||||
onFileChanged: reload()
|
onFileChanged: reload()
|
||||||
|
|
||||||
JsonAdapter {
|
JsonAdapter {
|
||||||
id: adapter
|
id: adapter
|
||||||
|
|
||||||
property string gifFolder: Quickshell.shellDir + "/Gifs"
|
property string gifFolder: Quickshell.shellDir + "/Gifs"
|
||||||
|
property real maxScale: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-26
@@ -1,32 +1,20 @@
|
|||||||
import Quickshell.Io
|
import QtQuick
|
||||||
|
import Qt.labs.folderlistmodel
|
||||||
|
|
||||||
Process {
|
Item {
|
||||||
id: getGifsProcess
|
id: root
|
||||||
|
|
||||||
|
property alias count: folderModel.count
|
||||||
required property string gifFolder
|
required property string gifFolder
|
||||||
property list<string> gifsList: []
|
property alias gifsModel: folderModel
|
||||||
|
|
||||||
command: ["find", gifFolder, "-type", "f", "-name", "*.gif"]
|
FolderListModel {
|
||||||
|
id: folderModel
|
||||||
|
|
||||||
stdout: StdioCollector {
|
folder: "file://" + root.gifFolder
|
||||||
onStreamFinished: {
|
nameFilters: ["*.gif"]
|
||||||
var gifs = this.text.trim().split("\n")
|
showDirs: false
|
||||||
if (gifs.length > 0 && gifs[0] !== "") {
|
showHidden: false
|
||||||
getGifsProcess.gifsList = gifs
|
sortField: FolderListModel.Name
|
||||||
} else {
|
|
||||||
getGifsProcess.gifsList = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reload() {
|
|
||||||
gifsList = []
|
|
||||||
running = false
|
|
||||||
running = true
|
|
||||||
}
|
|
||||||
|
|
||||||
onGifFolderChanged: {
|
|
||||||
if (running) {
|
|
||||||
reload()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-20
@@ -1,41 +1,88 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Qt.labs.folderlistmodel
|
||||||
import qs.Modules
|
import qs.Modules
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: gifRepeater
|
id: gifRepeater
|
||||||
required property list<string> gifsList
|
|
||||||
property int firstWidth
|
required property FolderListModel gifsModel
|
||||||
property int lastWidth
|
|
||||||
model: gifsList
|
model: gifsModel
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: gifItem
|
id: gifItem
|
||||||
|
|
||||||
|
required property string fileBaseName
|
||||||
|
required property url fileUrl
|
||||||
|
property alias hovered: mouse.containsMouse
|
||||||
required property int index
|
required property int index
|
||||||
required property string modelData
|
property bool loaded: false
|
||||||
|
property alias zIndex: gifSaved.zIndex
|
||||||
|
|
||||||
function xPos(): void {
|
height: Math.floor(gif.sourceSize.height / gifSaved.scaling)
|
||||||
let xPos = 0;
|
visible: gifItem.loaded
|
||||||
const item = gifRepeater.itemAt(index - 1);
|
width: Math.floor(gif.sourceSize.width / gifSaved.scaling)
|
||||||
if ( item ) xPos += item.x + item.width;
|
z: gifSaved.zIndex
|
||||||
return xPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: x = xPos()
|
onXChanged: if (gifItem.loaded)
|
||||||
|
gifSaved.positionX = gifItem.x
|
||||||
x: 0
|
onYChanged: if (gifItem.loaded)
|
||||||
y: Screen.height - ( height + 10 )
|
gifSaved.positionY = gifItem.y
|
||||||
width: Math.floor( gif.sourceSize.width / 1.25 )
|
|
||||||
height: Math.floor( gif.sourceSize.height / 1.25 )
|
|
||||||
|
|
||||||
AnimatedImage {
|
AnimatedImage {
|
||||||
id: gif
|
id: gif
|
||||||
source: gifItem.modelData
|
|
||||||
// fillMode: Image.PreserveAspectFit
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
source: gifItem.fileUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
Mouse {}
|
Mouse {
|
||||||
|
id: mouse
|
||||||
|
|
||||||
|
onDoubleClicked: gifSaved.scaling = 1
|
||||||
|
onWheel: wheel => {
|
||||||
|
gifSaved.scaling = Math.max(ConfigLoader.maxScaling, (gifSaved.scaling + 0.1 * (wheel.angleDelta.y / 120)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: watcher
|
||||||
|
|
||||||
|
property string configDir: Quickshell.env("HOME") + "/.config/I-DeskPet/"
|
||||||
|
property string configPath: configDir + name
|
||||||
|
property string name: gifItem.fileBaseName + ".json"
|
||||||
|
|
||||||
|
path: configPath
|
||||||
|
watchChanges: true
|
||||||
|
|
||||||
|
onAdapterUpdated: writeAdapter()
|
||||||
|
onFileChanged: reload()
|
||||||
|
onLoadFailed: {
|
||||||
|
gifSaved.zIndex = gifItem.index;
|
||||||
|
writeAdapter();
|
||||||
|
gifItem.loaded = true;
|
||||||
|
}
|
||||||
|
onLoaded: {
|
||||||
|
if (gifSaved.zIndex === -1)
|
||||||
|
gifSaved.zIndex = gifItem.index;
|
||||||
|
gifItem.x = gifSaved.positionX;
|
||||||
|
gifItem.y = gifSaved.positionY;
|
||||||
|
gifItem.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonAdapter {
|
||||||
|
id: gifSaved
|
||||||
|
|
||||||
|
property int positionX: 0
|
||||||
|
property int positionY: 0
|
||||||
|
property real scaling: 1
|
||||||
|
property int zIndex: -1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -1,12 +1,13 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
drag.target: parent
|
anchors.fill: parent
|
||||||
drag.axis: Drag.XAndYAxis
|
drag.axis: Drag.XAndYAxis
|
||||||
drag.minimumX: 0
|
|
||||||
drag.maximumX: Screen.width - parent.width
|
drag.maximumX: Screen.width - parent.width
|
||||||
drag.minimumY: 0
|
|
||||||
drag.maximumY: Screen.height - parent.height
|
drag.maximumY: Screen.height - parent.height
|
||||||
|
drag.minimumX: 0
|
||||||
|
drag.minimumY: 0
|
||||||
|
drag.target: parent
|
||||||
|
hoverEnabled: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,63 @@
|
|||||||
<div align="Center">
|
<div align="Center">
|
||||||
<h3> Pet March (Evernight) </h3>
|
<h3> Pet March (Evernight) </h3>
|
||||||
<p>My selfmade desktop pet using QT </p>
|
<p>My selfmade desktop pet using QT </p>
|
||||||
<img src=./Assets/evernight.gif style="margin: 0px 30px 0px 0px;" />
|
<img src=./Assets/Evernight.gif style="margin: 0px 30px 0px 0px;" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Feature to-do list
|
## Feature list
|
||||||
- [x] Hyprland keybind support (Swap between top/bottom/overlay)
|
|
||||||
- [x] Be able to click through your pet
|
- [x] Hyprland keybind support
|
||||||
- [] Dynamic pets
|
- [x] Toggle layer ontop/bottom
|
||||||
- [] Multiple pets
|
- [x] Toggle active mouse area
|
||||||
|
- [x] Dynamic path + live update
|
||||||
|
- [x] Supports multiple gifs
|
||||||
|
- [x] User config options
|
||||||
|
- [x] Evernight base gif img
|
||||||
|
|
||||||
|
# Config
|
||||||
|
|
||||||
|
Configuration is found at:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
~/.config/I-DeskPet
|
||||||
|
```
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
- gifFolder
|
||||||
|
- maxScaling
|
||||||
|
|
||||||
|
Example for config.json:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"gifFolder": "/home/inorishio/Pictures/Pets",
|
||||||
|
"maxScaling": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Hyprland keybinds
|
||||||
|
|
||||||
|
Toggle click through
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
bind = CTRL, mouse:274, global, I-DeskPet:toggle-Region
|
||||||
|
```
|
||||||
|
|
||||||
|
Toggle between having your gif on your background vs foreground
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
bind = SHIFT, mouse:274, global, I-DeskPet:toggle-Layer
|
||||||
|
```
|
||||||
|
|
||||||
|
Keybind for cycling through gif layering.
|
||||||
|
Hover over which gif you want to cycle it's layer for and use the keybind.
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
bind = $mainMod, Z, global, I-DeskPet:cycle-zIndex
|
||||||
|
```
|
||||||
|
|
||||||
|
# Other keybinds
|
||||||
|
|
||||||
|
- Double click = Reset gif size to original
|
||||||
|
- Scroll = Scales the gif up and or down
|
||||||
|
|||||||
@@ -9,39 +9,75 @@ import qs.Modules
|
|||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: mainWindow
|
id: mainWindow
|
||||||
color: "transparent"
|
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
property var noMove: Region {
|
||||||
|
}
|
||||||
|
property bool onTop: true
|
||||||
|
property var petMove: Region {
|
||||||
|
id: pets
|
||||||
|
|
||||||
|
height: Screen.height
|
||||||
|
intersection: Intersection.Xor
|
||||||
|
regions: maskVariants.instances
|
||||||
|
width: Screen.width
|
||||||
|
}
|
||||||
|
property list<Item> repeaterItems: []
|
||||||
|
property bool setMask: true
|
||||||
|
|
||||||
|
function petRegion(itemObject) {
|
||||||
|
let newregion = regionComponent.createObject(pets, {
|
||||||
|
"item": itemObject
|
||||||
|
});
|
||||||
|
pets.regions.push(newregion);
|
||||||
|
}
|
||||||
|
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
|
WlrLayershell.namespace: "I-DeskPet"
|
||||||
|
color: "transparent"
|
||||||
surfaceFormat.opaque: false
|
surfaceFormat.opaque: false
|
||||||
|
|
||||||
property bool onTop: true
|
mask: Region {
|
||||||
property list<Item> repeaterItems: []
|
height: Screen.height
|
||||||
|
intersection: Intersection.Xor
|
||||||
|
regions: maskVariants.instances
|
||||||
|
width: Screen.width
|
||||||
|
}
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
left: true
|
|
||||||
bottom: true
|
bottom: true
|
||||||
|
left: true
|
||||||
right: true
|
right: true
|
||||||
top: true
|
top: true
|
||||||
}
|
}
|
||||||
|
|
||||||
margins {
|
margins {
|
||||||
|
bottom: 0
|
||||||
left: 0
|
left: 0
|
||||||
bottom: 9
|
|
||||||
right: 0
|
right: 0
|
||||||
top: 0
|
top: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
GetGifs {
|
GetGifs {
|
||||||
id: getGifs
|
id: getGifs
|
||||||
|
|
||||||
gifFolder: ConfigLoader.gifFolder
|
gifFolder: ConfigLoader.gifFolder
|
||||||
running: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GifsLoader {
|
GifsLoader {
|
||||||
id: gifLoader
|
id: gifLoader
|
||||||
gifsList: getGifs.gifsList
|
|
||||||
|
gifsModel: getGifs.gifsModel
|
||||||
|
|
||||||
onItemAdded: function (index, item) {
|
onItemAdded: function (index, item) {
|
||||||
mainWindow.repeaterItems.push( item )
|
mainWindow.repeaterItems = Array.from({
|
||||||
|
length: gifLoader.count
|
||||||
|
}, (_, i) => gifLoader.itemAt(i)).filter(v => v !== null);
|
||||||
|
}
|
||||||
|
onItemRemoved: function (index, item) {
|
||||||
|
mainWindow.repeaterItems = Array.from({
|
||||||
|
length: gifLoader.count
|
||||||
|
}, (_, i) => gifLoader.itemAt(i)).filter(v => v !== null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,56 +89,36 @@ PanelWindow {
|
|||||||
Region {
|
Region {
|
||||||
required property Item modelData
|
required property Item modelData
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
console.log(modelData)
|
|
||||||
}
|
|
||||||
|
|
||||||
x: modelData.x
|
|
||||||
y: modelData.y
|
|
||||||
width: modelData.width
|
|
||||||
height: modelData.height
|
height: modelData.height
|
||||||
intersection: Intersection.Subtract
|
intersection: Intersection.Subtract
|
||||||
}
|
width: modelData.width
|
||||||
}
|
x: modelData.x
|
||||||
|
y: modelData.y
|
||||||
|
|
||||||
function petRegion( itemObject ) {
|
Component.onCompleted: {
|
||||||
let newregion = regionComponent.createObject( pets, { "item": itemObject })
|
console.log(modelData);
|
||||||
pets.regions.push( newregion )
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: regionComponent
|
id: regionComponent
|
||||||
Region { }
|
|
||||||
|
Region {
|
||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {
|
|
||||||
width: Screen.width
|
|
||||||
height: Screen.height
|
|
||||||
intersection: Intersection.Xor
|
|
||||||
regions: maskVariants.instances
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property var petMove: Region { id: pets
|
|
||||||
width: Screen.width
|
|
||||||
height: Screen.height
|
|
||||||
intersection: Intersection.Xor
|
|
||||||
regions: maskVariants.instances
|
|
||||||
}
|
|
||||||
|
|
||||||
property var noMove: Region {}
|
|
||||||
|
|
||||||
property bool setMask: true
|
|
||||||
|
|
||||||
GlobalShortcut {
|
GlobalShortcut {
|
||||||
appid: "I-DeskPet"
|
appid: "I-DeskPet"
|
||||||
name: "toggle-Layer"
|
name: "toggle-Layer"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
if (!mainWindow.onTop) {
|
if (!mainWindow.onTop) {
|
||||||
mainWindow.WlrLayershell.layer = WlrLayer.Overlay
|
mainWindow.WlrLayershell.layer = WlrLayer.Overlay;
|
||||||
mainWindow.onTop = true
|
mainWindow.onTop = true;
|
||||||
} else {
|
} else {
|
||||||
mainWindow.WlrLayershell.layer = WlrLayer.Bottom
|
mainWindow.WlrLayershell.layer = WlrLayer.Bottom;
|
||||||
mainWindow.onTop = false
|
mainWindow.onTop = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,13 +126,57 @@ PanelWindow {
|
|||||||
GlobalShortcut {
|
GlobalShortcut {
|
||||||
appid: "I-DeskPet"
|
appid: "I-DeskPet"
|
||||||
name: "toggle-Region"
|
name: "toggle-Region"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
if (!mainWindow.setMask) {
|
if (!mainWindow.setMask) {
|
||||||
mainWindow.mask = mainWindow.petMove
|
mainWindow.mask = mainWindow.petMove;
|
||||||
mainWindow.setMask = true
|
mainWindow.setMask = true;
|
||||||
} else {
|
} else {
|
||||||
mainWindow.mask = mainWindow.noMove
|
mainWindow.mask = mainWindow.noMove;
|
||||||
mainWindow.setMask = false
|
mainWindow.setMask = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalShortcut {
|
||||||
|
appid: "I-DeskPet"
|
||||||
|
name: "cycle-zIndex"
|
||||||
|
|
||||||
|
onPressed: {
|
||||||
|
let items = mainWindow.repeaterItems;
|
||||||
|
if (items.length < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Find the hovered GIF
|
||||||
|
let hovered = null;
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (items[i].hovered) {
|
||||||
|
hovered = items[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hovered)
|
||||||
|
return;
|
||||||
|
let currentZ = hovered.zIndex;
|
||||||
|
let maxZ = items.length - 1;
|
||||||
|
|
||||||
|
if (currentZ >= maxZ) {
|
||||||
|
// Already on top, wrap to bottom: shift everyone else up by 1
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (items[i] !== hovered) {
|
||||||
|
items[i].zIndex += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hovered.zIndex = 0;
|
||||||
|
} else {
|
||||||
|
// Swap with the item directly above
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (items[i] !== hovered && items[i].zIndex === currentZ + 1) {
|
||||||
|
items[i].zIndex = currentZ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hovered.zIndex = currentZ + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user