forked from Shinonome/dots-hyprland
wbar: add tooltip and stuff
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.waffle.looks
|
||||
import qs.modules.waffle.bar
|
||||
import Quickshell
|
||||
|
||||
AppButton {
|
||||
id: root
|
||||
|
||||
required property var appEntry
|
||||
readonly property bool isSeparator: appEntry.appId === "SEPARATOR"
|
||||
readonly property var desktopEntry: DesktopEntries.heuristicLookup(appEntry.appId)
|
||||
property bool active: root.appEntry.toplevels.some(t => t.activated)
|
||||
property bool hasWindows: appEntry.toplevels.length > 0
|
||||
|
||||
signal hoverPreviewRequested()
|
||||
|
||||
multiple: appEntry.toplevels.length > 1
|
||||
checked: active
|
||||
iconName: AppSearch.guessIcon(appEntry.appId)
|
||||
tryCustomIcon: false
|
||||
|
||||
onHoverTimedOut: {
|
||||
root.hoverPreviewRequested()
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
root.hoverTimer.stop() // Prevents preview showing up when clicking to focus
|
||||
if (root.multiple) {
|
||||
root.hoverPreviewRequested()
|
||||
} else if (root.appEntry.toplevels.length === 1) {
|
||||
root.appEntry.toplevels[0].activate()
|
||||
} else {
|
||||
root.desktopEntry.execute()
|
||||
}
|
||||
}
|
||||
|
||||
// Active indicator
|
||||
Rectangle {
|
||||
id: activeIndicator
|
||||
opacity: root.hasWindows ? 1 : 0
|
||||
anchors {
|
||||
horizontalCenter: root.background.horizontalCenter
|
||||
bottom: root.background.bottom
|
||||
bottomMargin: 1
|
||||
}
|
||||
|
||||
implicitWidth: root.active ? 16 : 6
|
||||
implicitHeight: 3
|
||||
radius: height / 2
|
||||
|
||||
color: root.active ? Looks.colors.accent : Looks.colors.accentUnfocused
|
||||
|
||||
Behavior on implicitWidth {
|
||||
animation: Looks.transition.enter.createObject(this)
|
||||
}
|
||||
Behavior on color {
|
||||
animation: Looks.transition.color.createObject(this)
|
||||
}
|
||||
Behavior on opacity {
|
||||
animation: Looks.transition.opacity.createObject(this)
|
||||
}
|
||||
}
|
||||
|
||||
BarToolTip {
|
||||
extraVisibleCondition: root.shouldShowTooltip && !root.hasWindows
|
||||
text: desktopEntry ? desktopEntry.name : appEntry.appId
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.waffle.looks
|
||||
import Quickshell
|
||||
|
||||
PopupWindow {
|
||||
id: root
|
||||
|
||||
///////////////////// Properties ////////////////////
|
||||
required property bool tasksHovered
|
||||
property var appEntry
|
||||
property Item anchorItem
|
||||
|
||||
//////////////////// Functions ////////////////////
|
||||
function close() {
|
||||
marginBehavior.enabled = false;
|
||||
root.visible = false;
|
||||
}
|
||||
|
||||
function open() {
|
||||
marginBehavior.enabled = true;
|
||||
root.visible = true;
|
||||
}
|
||||
|
||||
function show(appEntry: var, button: Item) {
|
||||
root.appEntry = appEntry;
|
||||
root.anchorItem = button;
|
||||
root.anchor.updateAnchor();
|
||||
root.open();
|
||||
}
|
||||
|
||||
///////////////////// Internals /////////////////////
|
||||
readonly property bool bottom: Config.options.waffles.bar.bottom
|
||||
property real visualMargin: 12
|
||||
property alias ambientShadowWidth: ambientShadow.border.width
|
||||
|
||||
visible: false
|
||||
color: "transparent"
|
||||
implicitWidth: contentItem.implicitWidth + ambientShadowWidth + (visualMargin * 2)
|
||||
implicitHeight: contentItem.implicitHeight + ambientShadowWidth + (visualMargin * 2)
|
||||
anchor {
|
||||
adjustment: PopupAdjustment.Slide
|
||||
item: root.anchorItem
|
||||
gravity: bottom ? Edges.Top : Edges.Bottom
|
||||
edges: bottom ? Edges.Top : Edges.Bottom
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 250
|
||||
running: root.visible && !hoverChecker.containsMouse && !root.tasksHovered
|
||||
onTriggered: {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
MouseArea {
|
||||
id: hoverChecker
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
// Shadow
|
||||
Rectangle {
|
||||
id: ambientShadow
|
||||
anchors {
|
||||
fill: contentItem
|
||||
margins: -border.width
|
||||
}
|
||||
border.color: ColorUtils.transparentize(Looks.colors.bg0Border, Looks.shadowTransparency)
|
||||
border.width: 1
|
||||
color: "transparent"
|
||||
radius: Looks.radius.large + border.width
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: contentItem
|
||||
property real sourceEdgeMargin: root.visible ? (root.ambientShadowWidth + root.visualMargin) : -root.implicitHeight
|
||||
Behavior on sourceEdgeMargin {
|
||||
id: marginBehavior
|
||||
animation: Looks.transition.enter.createObject(this)
|
||||
}
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: root.bottom ? undefined : parent.top
|
||||
bottom: root.bottom ? parent.bottom : undefined
|
||||
margins: root.ambientShadowWidth + root.visualMargin
|
||||
// Opening anim
|
||||
bottomMargin: root.bottom ? sourceEdgeMargin : (root.ambientShadowWidth + root.visualMargin)
|
||||
topMargin: root.bottom ? (root.ambientShadowWidth + root.visualMargin) : sourceEdgeMargin
|
||||
}
|
||||
color: Looks.colors.bg1
|
||||
radius: Looks.radius.large
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: contentItem.width
|
||||
height: contentItem.height
|
||||
radius: contentItem.radius
|
||||
}
|
||||
}
|
||||
|
||||
// Testing
|
||||
implicitHeight: Math.min(158, windowsRow.implicitHeight)
|
||||
implicitWidth: windowsRow.implicitWidth
|
||||
|
||||
RowLayout {
|
||||
id: windowsRow
|
||||
anchors.fill: parent
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.appEntry?.toplevels ?? []
|
||||
}
|
||||
delegate: WindowPreview {
|
||||
required property var modelData
|
||||
toplevel: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
Layout.fillHeight: true
|
||||
implicitHeight: row.implicitHeight
|
||||
implicitWidth: row.implicitWidth
|
||||
hoverEnabled: true
|
||||
|
||||
function showPreviewPopup(appEntry, button) {
|
||||
previewPopup.show(appEntry, button);
|
||||
}
|
||||
|
||||
// Apps row
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
// TODO: Include only apps (and windows) in current workspace only | wait, does that even make sense in a Hyprland workflow?
|
||||
model: ScriptModel {
|
||||
objectProp: "appId"
|
||||
values: TaskbarApps.apps.filter(app => app.appId !== "SEPARATOR")
|
||||
}
|
||||
delegate: TaskAppButton {
|
||||
required property var modelData
|
||||
appEntry: modelData
|
||||
|
||||
onHoverPreviewRequested: {
|
||||
root.showPreviewPopup(appEntry, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Previews popup
|
||||
TaskPreview {
|
||||
id: previewPopup
|
||||
tasksHovered: root.containsMouse
|
||||
anchor.window: root.QsWindow.window
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.waffle.looks
|
||||
import qs.modules.waffle.bar
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
|
||||
Button {
|
||||
id: root
|
||||
|
||||
required property var toplevel
|
||||
property real previewWidthConstraint: 200
|
||||
property real previewHeightConstraint: 110
|
||||
padding: 5
|
||||
Layout.fillHeight: true
|
||||
|
||||
onClicked: {
|
||||
root.toplevel.activate(); // TODO: make this work with those who disable focus on activate because telegram is abusive
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: background
|
||||
radius: Looks.radius.medium
|
||||
color: root.down ? Looks.colors.bg2Active : (root.hovered ? Looks.colors.bg2Hover : ColorUtils.transparentize(Looks.colors.bg2))
|
||||
Behavior on color {
|
||||
animation: Looks.transition.color.createObject(this)
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: contentItem
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.padding
|
||||
spacing: 5
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: false
|
||||
spacing: 8
|
||||
|
||||
AppIcon {
|
||||
id: appIcon
|
||||
Layout.leftMargin: Looks.radius.large - root.padding + 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
iconName: AppSearch.guessIcon(root.toplevel.appId)
|
||||
implicitSize: 16
|
||||
}
|
||||
|
||||
Item {
|
||||
id: appTitleContainer
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
implicitHeight: closeButton.implicitHeight // Enforce height, because closeButton doesn't contribute when it's invisible
|
||||
WText {
|
||||
id: appTitleText
|
||||
anchors.fill: parent
|
||||
text: root.toplevel.title
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: Looks.font.pixelSize.large
|
||||
font.weight: Looks.font.weight.thin
|
||||
color: Looks.colors.fg1
|
||||
}
|
||||
}
|
||||
|
||||
CloseButton {
|
||||
id: closeButton
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.margins: Looks.radius.large - root.padding
|
||||
Layout.topMargin: 0
|
||||
implicitWidth: Math.max(screencopyView.implicitWidth, 80)
|
||||
implicitHeight: screencopyView.implicitHeight
|
||||
|
||||
ScreencopyView {
|
||||
id: screencopyView
|
||||
anchors.centerIn: parent
|
||||
captureSource: root.toplevel
|
||||
live: true
|
||||
paintCursor: true
|
||||
constraintSize: Qt.size(root.previewWidthConstraint, root.previewHeightConstraint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component CloseButton: Button {
|
||||
id: reusableCloseButton
|
||||
visible: root.hovered
|
||||
Layout.leftMargin: 4
|
||||
implicitHeight: 30
|
||||
implicitWidth: 30
|
||||
onClicked: {
|
||||
root.toplevel.close();
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
z: 0
|
||||
color: "transparent"
|
||||
anchors.fill: closeButtonBg
|
||||
anchors.margins: -1
|
||||
opacity: closeButtonBg.opacity
|
||||
border.width: 1
|
||||
radius: closeButtonBg.radius + 1
|
||||
border.color: Looks.colors.bg2Border
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: closeButtonBg
|
||||
z: 1
|
||||
opacity: reusableCloseButton.hovered ? 1 : 0
|
||||
radius: Looks.radius.large - root.padding
|
||||
color: reusableCloseButton.pressed ? Looks.colors.dangerActive : Looks.colors.danger
|
||||
Behavior on opacity {
|
||||
animation: Looks.transition.opacity.createObject(this)
|
||||
}
|
||||
Behavior on color {
|
||||
animation: Looks.transition.color.createObject(this)
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: FluentIcon {
|
||||
z: 2
|
||||
anchors.centerIn: parent
|
||||
icon: "dismiss"
|
||||
implicitSize: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user