Feat: MD3 wallpaper launcher carousel #146
@@ -26,6 +26,7 @@ Singleton {
|
||||
|
||||
function query(search: string): list<var> {
|
||||
search = transformSearch(search);
|
||||
console.log(!search, search, ...list);
|
||||
if (!search)
|
||||
return [...list];
|
||||
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import qs.Modules.Launcher.Services
|
||||
import qs.Modules.Launcher.Items
|
||||
import qs.Components
|
||||
import Quickshell
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
import qs.Modules.Launcher.Items
|
||||
import qs.Modules.Launcher.Services
|
||||
|
||||
CustomListView {
|
||||
id: root
|
||||
|
||||
readonly property string displayState: stateForText(displayText)
|
||||
property string displayText
|
||||
readonly property string requestedState: stateForText(search.text)
|
||||
required property SearchBar search
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
highlightFollowsCurrentItem: false
|
||||
highlightRangeMode: ListView.ApplyRange
|
||||
implicitHeight: (Config.launcher.sizes.itemHeight + spacing) * Math.min(Config.launcher.maxAppsShown, count) - spacing
|
||||
orientation: Qt.Vertical
|
||||
preferredHighlightBegin: 0
|
||||
preferredHighlightEnd: height
|
||||
spacing: Tokens.spacing.small
|
||||
state: {
|
||||
const text = search.text;
|
||||
function resultsForText(text: string): var {
|
||||
switch (stateForText(text)) {
|
||||
case "actions":
|
||||
return Actions.query(text);
|
||||
case "calc":
|
||||
return [0];
|
||||
case "variant":
|
||||
return SchemeVariants.query(text);
|
||||
default:
|
||||
return Apps.search(text);
|
||||
}
|
||||
}
|
||||
|
||||
function stateForText(text: string): string {
|
||||
const prefix = Config.launcher.actionPrefix;
|
||||
if (text.startsWith(prefix)) {
|
||||
for (const action of ["calc", "scheme", "variant"])
|
||||
@@ -34,6 +42,20 @@ CustomListView {
|
||||
|
||||
return "apps";
|
||||
}
|
||||
|
||||
function syncDisplayText(): void {
|
||||
if (visibilities.launcher && requestedState === displayState)
|
||||
displayText = search.text;
|
||||
}
|
||||
|
||||
highlightFollowsCurrentItem: false
|
||||
highlightRangeMode: ListView.ApplyRange
|
||||
implicitHeight: (Config.launcher.sizes.itemHeight + spacing) * Math.min(Config.launcher.maxAppsShown, count) - spacing
|
||||
orientation: Qt.Vertical
|
||||
preferredHighlightBegin: 0
|
||||
preferredHighlightEnd: height
|
||||
spacing: Tokens.spacing.small
|
||||
state: visibilities.launcher ? requestedState : displayState
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
|
||||
CustomScrollBar.vertical: CustomScrollBar {
|
||||
@@ -44,19 +66,21 @@ CustomListView {
|
||||
|
||||
Anim {
|
||||
from: 0
|
||||
properties: "opacity,scale"
|
||||
property: "opacity"
|
||||
to: 1
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
addDisplaced: Transition {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.small
|
||||
property: "y"
|
||||
type: Anim.StandardSmall
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
property: "opacity"
|
||||
to: 1
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
displaced: Transition {
|
||||
@@ -65,8 +89,9 @@ CustomListView {
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
property: "opacity"
|
||||
to: 1
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
highlight: CustomRect {
|
||||
@@ -85,7 +110,7 @@ CustomListView {
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
id: model
|
||||
values: root.resultsForText(root.displayText)
|
||||
|
||||
onValuesChanged: root.currentIndex = 0
|
||||
}
|
||||
@@ -95,8 +120,9 @@ CustomListView {
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
property: "opacity"
|
||||
to: 1
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
remove: Transition {
|
||||
@@ -104,8 +130,9 @@ CustomListView {
|
||||
|
||||
Anim {
|
||||
from: 1
|
||||
properties: "opacity,scale"
|
||||
property: "opacity"
|
||||
to: 0
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
states: [
|
||||
@@ -113,7 +140,6 @@ CustomListView {
|
||||
name: "apps"
|
||||
|
||||
PropertyChanges {
|
||||
model.values: Apps.search(search.text)
|
||||
root.delegate: appItem
|
||||
}
|
||||
},
|
||||
@@ -121,7 +147,6 @@ CustomListView {
|
||||
name: "actions"
|
||||
|
||||
PropertyChanges {
|
||||
model.values: Actions.query(search.text)
|
||||
root.delegate: actionItem
|
||||
}
|
||||
},
|
||||
@@ -129,7 +154,6 @@ CustomListView {
|
||||
name: "calc"
|
||||
|
||||
PropertyChanges {
|
||||
model.values: [0]
|
||||
root.delegate: calcItem
|
||||
}
|
||||
},
|
||||
@@ -137,7 +161,6 @@ CustomListView {
|
||||
name: "variant"
|
||||
|
||||
PropertyChanges {
|
||||
model.values: SchemeVariants.query(search.text)
|
||||
root.delegate: variantItem
|
||||
}
|
||||
}
|
||||
@@ -147,7 +170,7 @@ CustomListView {
|
||||
ParallelAnimation {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.small
|
||||
easing.bezierCurve: Tokens.anim.curves.expressiveEffects
|
||||
easing.bezierCurve: Tokens.anim.curves.standardAccel
|
||||
from: 1
|
||||
property: "opacity"
|
||||
target: root
|
||||
@@ -156,7 +179,7 @@ CustomListView {
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.small
|
||||
easing.bezierCurve: Tokens.anim.curves.expressiveEffects
|
||||
easing.bezierCurve: Tokens.anim.curves.standardAccel
|
||||
from: 1
|
||||
property: "scale"
|
||||
target: root
|
||||
@@ -165,14 +188,24 @@ CustomListView {
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
properties: "values,delegate"
|
||||
targets: [model, root]
|
||||
property: "delegate"
|
||||
target: root
|
||||
value: null
|
||||
}
|
||||
|
||||
ScriptAction {
|
||||
script: root.displayText = root.search.text
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "delegate"
|
||||
target: root
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.small
|
||||
easing.bezierCurve: Tokens.anim.curves.expressiveEffects
|
||||
easing.bezierCurve: Tokens.anim.curves.standardDecel
|
||||
from: 0
|
||||
property: "opacity"
|
||||
target: root
|
||||
@@ -181,7 +214,7 @@ CustomListView {
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.small
|
||||
easing.bezierCurve: Tokens.anim.curves.expressiveEffects
|
||||
easing.bezierCurve: Tokens.anim.curves.standardDecel
|
||||
from: 0.9
|
||||
property: "scale"
|
||||
target: root
|
||||
@@ -197,6 +230,8 @@ CustomListView {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: displayText = search.text
|
||||
|
||||
Component {
|
||||
id: appItem
|
||||
|
||||
@@ -228,4 +263,20 @@ CustomListView {
|
||||
list: root
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onTextChanged() {
|
||||
root.syncDisplayText();
|
||||
}
|
||||
|
||||
target: root.search
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onLauncherChanged() {
|
||||
root.syncDisplayText();
|
||||
}
|
||||
|
||||
target: root.visibilities
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ constexpr int kMaxLayoutCapacity = 64;
|
||||
|
||||
CarouselView::CarouselView(QQuickItem* parent) : QQuickItem(parent) {
|
||||
setAcceptedMouseButtons(Qt::LeftButton);
|
||||
setFiltersChildMouseEvents(true);
|
||||
setFlag(QQuickItem::ItemHasContents, false);
|
||||
setClip(false);
|
||||
|
||||
@@ -80,6 +81,89 @@ void CarouselView::setDelegate(QQmlComponent* c) {
|
||||
relayout();
|
||||
}
|
||||
|
||||
bool CarouselView::childMouseEventFilter(QQuickItem* item, QEvent* event) {
|
||||
Q_UNUSED(item);
|
||||
|
||||
auto* mouseEvent = dynamic_cast<QMouseEvent*>(event);
|
||||
if (!mouseEvent) return false;
|
||||
|
||||
const QPointF pos = mapFromItem(item, mouseEvent->position());
|
||||
|
||||
switch (mouseEvent->type()) {
|
||||
case QEvent::MouseButtonPress: {
|
||||
if (mouseEvent->button() != Qt::LeftButton) return false;
|
||||
|
||||
m_dragging = true;
|
||||
m_dragActive = false;
|
||||
|
||||
m_pressPos = pos;
|
||||
m_pressContentX = m_contentX;
|
||||
|
||||
m_dragTimer.start();
|
||||
|
||||
m_lastMoveX = pos.x();
|
||||
m_lastMoveT = 0;
|
||||
m_velocity = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
case QEvent::MouseMove: {
|
||||
if (!m_dragging) return false;
|
||||
|
||||
const qreal dx = pos.x() - m_pressPos.x();
|
||||
|
||||
if (!m_dragActive && std::abs(dx) > kDragThreshold) {
|
||||
m_dragActive = true;
|
||||
m_pressContentX = m_contentX;
|
||||
|
||||
cancelAnimations();
|
||||
grabMouse();
|
||||
}
|
||||
|
||||
if (!m_dragActive) return false;
|
||||
|
||||
m_contentX = m_pressContentX - dx;
|
||||
|
||||
wrapContentIfNeeded();
|
||||
updateCurrentIndexFromContentX(true);
|
||||
relayout();
|
||||
|
||||
const qint64 t = m_dragTimer.elapsed();
|
||||
const qint64 dt = t - m_lastMoveT;
|
||||
|
||||
if (dt > 0) {
|
||||
const qreal instVel = (m_lastMoveX - pos.x()) / (dt / 1000.0);
|
||||
|
||||
m_velocity = m_velocity * 0.7 + instVel * 0.3;
|
||||
}
|
||||
|
||||
m_lastMoveX = pos.x();
|
||||
m_lastMoveT = t;
|
||||
|
||||
mouseEvent->accept();
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::MouseButtonRelease: {
|
||||
if (!m_dragging) return false;
|
||||
|
||||
if (!m_dragActive) {
|
||||
m_dragging = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
case QEvent::MouseButtonDblClick:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CarouselView::setModel(const QVariantList& m) {
|
||||
m_model = m;
|
||||
|
||||
|
||||
@@ -118,6 +118,8 @@ class CarouselView : public QQuickItem {
|
||||
void previewIndexChanged(int realIndex, QVariant modelData);
|
||||
|
||||
protected:
|
||||
bool childMouseEventFilter(QQuickItem* item, QEvent* event) override;
|
||||
|
||||
void geometryChange(const QRectF& newGeo, const QRectF& oldGeo) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
|
||||
Reference in New Issue
Block a user