Triangles clip includes border radius
This commit is contained in:
347
shell.qml
347
shell.qml
@@ -7,7 +7,6 @@ import QtQuick.Shapes
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
@@ -15,8 +14,8 @@ PanelWindow {
|
|||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
visible = false
|
visible = false
|
||||||
searchInput.text = ""
|
|
||||||
list.visible = false
|
list.visible = false
|
||||||
|
searchInput.text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
@@ -24,9 +23,11 @@ PanelWindow {
|
|||||||
function show() {
|
function show() {
|
||||||
visible = true
|
visible = true
|
||||||
list.visible = true
|
list.visible = true
|
||||||
|
list.modelChanged
|
||||||
}
|
}
|
||||||
function hide() {
|
function hide() {
|
||||||
root.hide()
|
root.hide()
|
||||||
|
searchInput.text = ""
|
||||||
}
|
}
|
||||||
function toggle() { visible ? hide() : show() }
|
function toggle() { visible ? hide() : show() }
|
||||||
}
|
}
|
||||||
@@ -39,19 +40,24 @@ PanelWindow {
|
|||||||
top: true
|
top: true
|
||||||
bottom: true
|
bottom: true
|
||||||
}
|
}
|
||||||
margins {
|
|
||||||
top: {
|
|
||||||
if (ToplevelManager.activeToplevel?.fullscreen) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return 36;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
readonly property int radius: 20
|
readonly property int radius: 20
|
||||||
readonly property var font: {
|
readonly property var font: {
|
||||||
family: "comfortaa"
|
family: "comfortaa"
|
||||||
}
|
}
|
||||||
readonly property int entry_height: 100
|
readonly property int entry_height: 100
|
||||||
|
property var currentApps: {
|
||||||
|
let apps = Array.from(DesktopEntries.applications.values);
|
||||||
|
if (searchInput.text.length === 0) {
|
||||||
|
apps.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
return apps;
|
||||||
|
}
|
||||||
|
apps = apps.filter(app =>
|
||||||
|
app.name.toLowerCase().search(searchInput.text.toLowerCase()) !== -1
|
||||||
|
);
|
||||||
|
return apps
|
||||||
|
}
|
||||||
|
property string lastLaunched: ""
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -68,33 +74,27 @@ PanelWindow {
|
|||||||
break;
|
break;
|
||||||
case Qt.Key_Return:
|
case Qt.Key_Return:
|
||||||
list.currentItem.execute()
|
list.currentItem.execute()
|
||||||
root.hide()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Swap ListView for ScriptModel
|
||||||
ListView {
|
ListView {
|
||||||
id: list
|
id: list
|
||||||
spacing: 6
|
spacing: 6
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
layer.enabled: true
|
|
||||||
highlightMoveDuration: 150
|
highlightMoveDuration: 150
|
||||||
highlightRangeMode: ListView.StrictlyEnforceRange
|
highlightRangeMode: ListView.StrictlyEnforceRange
|
||||||
preferredHighlightBegin: 120
|
preferredHighlightBegin: 120
|
||||||
preferredHighlightEnd: this.height/2 + 200
|
preferredHighlightEnd: this.height/2
|
||||||
property var currentApps: {
|
onModelChanged: {
|
||||||
let apps = Array.from(DesktopEntries.applications.values);
|
if (searchInput.text === "")
|
||||||
apps.sort((a, b) => a.name.localeCompare(b.name));
|
currentIndex = currentApps.findIndex(entry =>
|
||||||
if (searchInput.text.length === 0) {
|
entry.id === lastLaunched
|
||||||
return apps;
|
);
|
||||||
}
|
|
||||||
return apps.filter(app => {
|
|
||||||
return app.name.toLowerCase().search(searchInput.text.toLowerCase()) != -1
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
model: currentApps
|
model: currentApps
|
||||||
delegate: Item {
|
delegate: Button {
|
||||||
height: entry_height
|
height: entry_height
|
||||||
width: root.width + 10
|
width: root.width + 10
|
||||||
property var leftMargin: (Math.pow(Math.abs(this.y - list.contentY - root.height/2), 1.8) / 1400)
|
property var leftMargin: (Math.pow(Math.abs(this.y - list.contentY - root.height/2), 1.8) / 1400)
|
||||||
@@ -104,158 +104,168 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
z: -index
|
z: -index
|
||||||
property real selectedOffset: ListView.isCurrentItem ? 16 : 64
|
property real selectedOffset: ListView.isCurrentItem ? 16 : 64
|
||||||
function execute() { modelData.execute() }
|
function execute() {
|
||||||
|
modelData.execute()
|
||||||
|
root.lastLaunched = modelData.id
|
||||||
|
root.hide()
|
||||||
|
}
|
||||||
Behavior on selectedOffset {
|
Behavior on selectedOffset {
|
||||||
NumberAnimation {duration: 500; easing.type: Easing.OutQuint}
|
NumberAnimation {duration: 500; easing.type: Easing.OutQuint}
|
||||||
}
|
}
|
||||||
Button {
|
id: button
|
||||||
id: button
|
onClicked: execute()
|
||||||
anchors {
|
|
||||||
fill: parent
|
|
||||||
// left: parent.left
|
|
||||||
// right: parent.right
|
|
||||||
// verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
onClicked: modelData.execute()
|
|
||||||
Component.onCompleted: {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate background color based on average color of icon
|
// Calculate background color based on average color of icon
|
||||||
Canvas {
|
Canvas {
|
||||||
id: canvas
|
id: canvas
|
||||||
width: icon.width
|
width: icon.width/2
|
||||||
height: icon.height
|
height: icon.height/2
|
||||||
visible: false
|
visible: false
|
||||||
onImageLoaded: {
|
onImageLoaded: {
|
||||||
console.log("loaded");
|
requestPaint();
|
||||||
requestPaint();
|
|
||||||
}
|
|
||||||
Component.onCompleted: loadImage(button.iconPath)
|
|
||||||
}
|
}
|
||||||
property color color: {
|
Component.onCompleted: loadImage(button.iconPath)
|
||||||
if (!canvas.available) {
|
}
|
||||||
return null
|
property var color: {
|
||||||
}
|
if (!canvas.available) {
|
||||||
const ctx = canvas.getContext("2d");
|
return null
|
||||||
ctx.drawImage(button.iconPath, 0, 0, canvas.width, canvas.height)
|
|
||||||
const pixels = ctx.getImageData(0, 0, 100, 100).data;
|
|
||||||
let avg = [0, 0, 0, 0];
|
|
||||||
let count = 0;
|
|
||||||
for (let i = 0; i < pixels.length; i += 4) {
|
|
||||||
avg[0] += pixels[i+0];
|
|
||||||
avg[1] += pixels[i+1];
|
|
||||||
avg[2] += pixels[i+2];
|
|
||||||
count += pixels[i+3];
|
|
||||||
}
|
|
||||||
return Qt.rgba(avg[0]/count, avg[1]/count, avg[2]/count, 1);
|
|
||||||
}
|
}
|
||||||
property var iconPath: Quickshell.iconPath(modelData.icon)
|
const ctx = canvas.getContext("2d");
|
||||||
|
ctx.drawImage(button.iconPath, 0, 0, canvas.width, canvas.height)
|
||||||
|
const pixels = ctx.getImageData(0, 0, 100, 100).data;
|
||||||
|
let avg = [0, 0, 0, 0];
|
||||||
|
let count = 0;
|
||||||
|
for (let i = 0; i < pixels.length; i += 4) {
|
||||||
|
let c_max = Math.max(pixels[i+0], pixels[i+1], pixels[i+2]);
|
||||||
|
let c_min = Math.min(pixels[i+0], pixels[i+1], pixels[i+2]);
|
||||||
|
let saturation = c_max === 0 ? 0 : (c_max - c_min) / c_max;
|
||||||
|
avg[0] += pixels[i+0] * saturation;
|
||||||
|
avg[1] += pixels[i+1] * saturation;
|
||||||
|
avg[2] += pixels[i+2] * saturation;
|
||||||
|
count += pixels[i+3] * saturation;
|
||||||
|
}
|
||||||
|
return Qt.rgba(avg[0]/count, avg[1]/count, avg[2]/count, 1);
|
||||||
|
}
|
||||||
|
property var iconPath: Quickshell.iconPath(modelData.icon)
|
||||||
|
|
||||||
// Background gradient
|
// Background gradient
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
radius: root.radius
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Horizontal
|
||||||
|
GradientStop {
|
||||||
|
color: Qt.darker(button.color, 8)
|
||||||
|
position: 0
|
||||||
|
}
|
||||||
|
GradientStop {
|
||||||
|
color: Qt.darker(button.color, 4)
|
||||||
|
position: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Triangles
|
||||||
|
Item {
|
||||||
|
id: triangles
|
||||||
|
visible: false
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
layer.enabled: true
|
||||||
|
Instantiator {
|
||||||
|
model: 6
|
||||||
|
delegate: Shape {
|
||||||
|
id: tri
|
||||||
|
property real x_size: Math.random() * button.width/12 + button.width/10
|
||||||
|
property real y_size: x_size * Math.SQRT1_2
|
||||||
|
|
||||||
|
property real y_anim: 0
|
||||||
|
property real y_off: Math.random() * entry_height
|
||||||
|
x: Math.random() * (button.width + x_size*2) - x_size
|
||||||
|
y: (y_anim + y_off) % (entry_height + y_size) - y_size
|
||||||
|
ShapePath {
|
||||||
|
strokeWidth: 2
|
||||||
|
strokeColor: Qt.darker(button.color, 2.5)
|
||||||
|
fillColor: "transparent"
|
||||||
|
|
||||||
|
startX: tri.x_size/2
|
||||||
|
startY: 0
|
||||||
|
PathLine {x: 0; y: tri.y_size}
|
||||||
|
PathLine {x: tri.x_size; y: tri.y_size}
|
||||||
|
PathLine {x: tri.x_size/2; y: 0}
|
||||||
|
}
|
||||||
|
PropertyAnimation on y_anim {
|
||||||
|
from: entry_height + tri.y_size
|
||||||
|
running: root.visible
|
||||||
|
to: 0
|
||||||
|
duration: Math.random() * 12000 + 5000
|
||||||
|
loops: Animation.Infinite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onObjectAdded: (index, obj) => obj.parent = triangles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MultiEffect {
|
||||||
|
source: triangles
|
||||||
|
anchors.fill: triangles
|
||||||
|
maskEnabled: true
|
||||||
|
maskSource: ShaderEffectSource { sourceItem: Rectangle {
|
||||||
|
x: triangles.x
|
||||||
|
y: triangles.y
|
||||||
|
width: triangles.width
|
||||||
|
height: triangles.height
|
||||||
radius: root.radius
|
radius: root.radius
|
||||||
gradient: Gradient {
|
} }
|
||||||
orientation: Gradient.Horizontal
|
}
|
||||||
GradientStop {
|
|
||||||
color: Qt.darker(button.color, 16)
|
|
||||||
position: 0
|
|
||||||
}
|
|
||||||
GradientStop {
|
|
||||||
color: Qt.darker(button.color, 4)
|
|
||||||
position: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Triangles
|
Item {
|
||||||
Item {
|
anchors.fill: parent
|
||||||
id: triangles
|
clip: true
|
||||||
anchors.fill: parent
|
Image {
|
||||||
clip: true
|
id: icon
|
||||||
layer.enabled: true
|
source: button.iconPath
|
||||||
Instantiator {
|
width: 80
|
||||||
model: 6
|
height: width
|
||||||
delegate: Shape {
|
|
||||||
id: tri
|
|
||||||
property real x_size: Math.random() * button.width/12 + button.width/10
|
|
||||||
property real y_size: x_size * Math.SQRT1_2
|
|
||||||
|
|
||||||
property real y_anim: 0
|
|
||||||
property real y_off: Math.random() * entry_height
|
|
||||||
x: Math.random() * (button.width + x_size*2) - x_size
|
|
||||||
y: (y_anim + y_off) % (entry_height + y_size) - y_size
|
|
||||||
ShapePath {
|
|
||||||
strokeWidth: 2
|
|
||||||
strokeColor: Qt.darker(button.color, 3)
|
|
||||||
fillColor: "transparent"
|
|
||||||
|
|
||||||
startX: tri.x_size/2
|
|
||||||
startY: 0
|
|
||||||
PathLine {x: 0; y: tri.y_size}
|
|
||||||
PathLine {x: tri.x_size; y: tri.y_size}
|
|
||||||
PathLine {x: tri.x_size/2; y: 0}
|
|
||||||
}
|
|
||||||
PropertyAnimation on y_anim {
|
|
||||||
from: entry_height + tri.y_size
|
|
||||||
to: 0
|
|
||||||
duration: Math.random() * 12000 + 3500
|
|
||||||
loops: Animation.Infinite
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onObjectAdded: (index, obj) => obj.parent = triangles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
clip: true
|
|
||||||
Image {
|
|
||||||
id: icon
|
|
||||||
source: button.iconPath
|
|
||||||
width: 80
|
|
||||||
height: width
|
|
||||||
anchors {
|
|
||||||
right: parent.right
|
|
||||||
rightMargin: 140
|
|
||||||
verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// App name/description
|
|
||||||
Column {
|
|
||||||
anchors {
|
anchors {
|
||||||
leftMargin: 12
|
right: parent.right
|
||||||
left: parent.left
|
rightMargin: 140
|
||||||
verticalCenter: parent.verticalCenter
|
verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
spacing: 4
|
|
||||||
Text {
|
|
||||||
text: modelData.name
|
|
||||||
font.family: root.font
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
visible: modelData.comment.length > 0
|
|
||||||
width: parent.parent.width - icon.anchors.rightMargin-icon.width - 20
|
|
||||||
text: modelData.comment
|
|
||||||
font.family: root.font
|
|
||||||
color: "white"
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Drop shadow
|
// App name/description
|
||||||
background: RectangularShadow {
|
Column {
|
||||||
anchors.fill: parent
|
anchors {
|
||||||
radius: root.radius
|
leftMargin: 12
|
||||||
blur: 8
|
left: parent.left
|
||||||
spread: 4
|
verticalCenter: parent.verticalCenter
|
||||||
opacity: 0.6
|
|
||||||
}
|
}
|
||||||
|
spacing: 4
|
||||||
|
Text {
|
||||||
|
text: modelData.name
|
||||||
|
font.family: root.font
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
visible: modelData.comment.length > 0
|
||||||
|
width: parent.parent.width - icon.anchors.rightMargin-icon.width - 20
|
||||||
|
text: modelData.comment
|
||||||
|
font.family: root.font
|
||||||
|
color: "white"
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop shadow
|
||||||
|
background: RectangularShadow {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: root.radius
|
||||||
|
blur: 8
|
||||||
|
spread: 4
|
||||||
|
opacity: 0.6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,6 +325,13 @@ PanelWindow {
|
|||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Text {
|
||||||
|
text: "Search apps..."
|
||||||
|
anchors.fill: searchInput
|
||||||
|
font: searchInput.font
|
||||||
|
color: "#888"
|
||||||
|
visible: searchInput.text.length === 0
|
||||||
|
}
|
||||||
Image {
|
Image {
|
||||||
source: Quickshell.iconPath("search")
|
source: Quickshell.iconPath("search")
|
||||||
anchors {
|
anchors {
|
||||||
@@ -325,14 +342,6 @@ PanelWindow {
|
|||||||
width: 24
|
width: 24
|
||||||
height: 24
|
height: 24
|
||||||
}
|
}
|
||||||
Text {
|
|
||||||
text: "Search apps..."
|
|
||||||
anchors.fill: searchInput
|
|
||||||
font: searchInput.font
|
|
||||||
color: "#888"
|
|
||||||
visible: searchInput.text.length === 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user