feat: m3 expressive menus
Also fix the player selector being click through Fixes #1405 Closes #1350
This commit is contained in:
parent
2bb28197a0
commit
ec8ce65857
7 changed files with 167 additions and 89 deletions
|
|
@ -1,41 +1,116 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import "../effects"
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.effects
|
||||
import qs.services
|
||||
import qs.modules.drawers
|
||||
|
||||
Elevation {
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
enum Side {
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
required property Item attachTo
|
||||
property int attachSideX: Menu.Right
|
||||
property int attachSideY: Menu.Bottom
|
||||
property int thisSideX: Menu.Right
|
||||
property int thisSideY: Menu.Top
|
||||
property real marginX
|
||||
property real marginY
|
||||
|
||||
property list<MenuItem> items
|
||||
property MenuItem active: items[0] ?? null
|
||||
property bool expanded
|
||||
|
||||
signal itemSelected(item: MenuItem)
|
||||
|
||||
radius: Tokens.rounding.small / 2
|
||||
parent: {
|
||||
const win = QsWindow.window;
|
||||
const contentWin = win as ContentWindow; // If inside the drawer content window, put it inside the interaction wrapper so hover works
|
||||
return contentWin ? contentWin.interactionWrapper : (win as QsWindow).contentItem;
|
||||
}
|
||||
anchors.fill: parent
|
||||
|
||||
enabled: expanded
|
||||
onClicked: expanded = false
|
||||
|
||||
opacity: expanded ? 1 : 0
|
||||
layer.enabled: opacity < 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.small
|
||||
}
|
||||
}
|
||||
|
||||
TransformWatcher {
|
||||
id: watcher
|
||||
|
||||
a: root.parent
|
||||
b: root.attachTo
|
||||
}
|
||||
|
||||
Elevation {
|
||||
id: menu
|
||||
|
||||
x: {
|
||||
watcher.transform; // mapToItem is not reactive so this forces updates
|
||||
const item = root.attachTo;
|
||||
let off = root.attachSideX === Menu.Left ? 0 : item.width;
|
||||
if (root.thisSideX === Menu.Right)
|
||||
off -= width;
|
||||
return item.mapToItem(root.parent, off, 0).x + root.marginX;
|
||||
}
|
||||
y: {
|
||||
watcher.transform; // mapToItem is not reactive so this forces updates
|
||||
const item = root.attachTo;
|
||||
let off = root.attachSideY === Menu.Top ? 0 : item.height;
|
||||
if (root.thisSideY === Menu.Bottom)
|
||||
off -= height;
|
||||
return item.mapToItem(root.parent, 0, off).y + root.marginY;
|
||||
}
|
||||
|
||||
radius: Tokens.rounding.normal
|
||||
level: 2
|
||||
|
||||
implicitWidth: Math.max(200, column.implicitWidth)
|
||||
implicitHeight: root.expanded ? column.implicitHeight : 0
|
||||
opacity: root.expanded ? 1 : 0
|
||||
implicitWidth: Math.max(200, column.implicitWidth + column.anchors.margins * 2)
|
||||
implicitHeight: column.implicitHeight + column.anchors.margins * 2
|
||||
|
||||
StyledClippingRect {
|
||||
transform: Scale {
|
||||
yScale: root.expanded ? 1 : 0.1
|
||||
origin.y: root.thisSideY === Menu.Bottom ? menu.height : 0
|
||||
|
||||
Behavior on yScale {
|
||||
Anim {
|
||||
type: Anim.DefaultSpatial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: Colours.palette.m3surfaceContainer
|
||||
color: Colours.palette.m3surfaceContainerLow
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.small
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
model: root.items
|
||||
|
||||
StyledRect {
|
||||
|
|
@ -49,10 +124,25 @@ Elevation {
|
|||
implicitWidth: menuOptionRow.implicitWidth + Tokens.padding.normal * 2
|
||||
implicitHeight: menuOptionRow.implicitHeight + Tokens.padding.normal * 2
|
||||
|
||||
color: Qt.alpha(Colours.palette.m3secondaryContainer, active ? 1 : 0)
|
||||
radius: active ? 12 : Tokens.rounding.extraSmall // This should use a token, but tokens are currently extremely scuffed
|
||||
topLeftRadius: index === 0 ? Tokens.rounding.small : radius
|
||||
topRightRadius: index === 0 ? Tokens.rounding.small : radius
|
||||
bottomLeftRadius: index === repeater.count - 1 ? Tokens.rounding.small : radius
|
||||
bottomRightRadius: index === repeater.count - 1 ? Tokens.rounding.small : radius
|
||||
|
||||
color: Qt.alpha(Colours.palette.m3tertiaryContainer, active ? 1 : 0)
|
||||
|
||||
Behavior on radius {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
|
||||
topLeftRadius: parent.topLeftRadius
|
||||
topRightRadius: parent.topRightRadius
|
||||
bottomLeftRadius: parent.bottomLeftRadius
|
||||
bottomRightRadius: parent.bottomRightRadius
|
||||
|
||||
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurface
|
||||
disabled: !root.expanded
|
||||
onClicked: {
|
||||
root.itemSelected(item.modelData);
|
||||
|
|
@ -72,14 +162,14 @@ Elevation {
|
|||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: item.modelData.icon
|
||||
color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurfaceVariant
|
||||
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
text: item.modelData.text
|
||||
color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
|
||||
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurface
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
|
@ -90,7 +180,7 @@ Elevation {
|
|||
|
||||
sourceComponent: MaterialIcon {
|
||||
text: item.modelData.trailingIcon
|
||||
color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
|
||||
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -98,16 +188,5 @@ Elevation {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
type: Anim.DefaultSpatial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,24 +138,14 @@ Row {
|
|||
Behavior on rad {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: menu
|
||||
|
||||
states: State {
|
||||
when: root.menuOnTop
|
||||
|
||||
AnchorChanges {
|
||||
target: menu
|
||||
anchors.top: undefined
|
||||
anchors.bottom: expandBtn.top
|
||||
}
|
||||
}
|
||||
|
||||
anchors.top: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Tokens.spacing.small
|
||||
anchors.bottomMargin: Tokens.spacing.small
|
||||
}
|
||||
attachTo: expandBtn
|
||||
attachSideY: root.menuOnTop ? Menu.Top : Menu.Bottom
|
||||
thisSideY: root.menuOnTop ? Menu.Bottom : Menu.Top
|
||||
marginY: Tokens.spacing.small * (root.menuOnTop ? -1 : 1)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ StyledWindow {
|
|||
id: root
|
||||
|
||||
readonly property alias bar: bar
|
||||
readonly property alias interactionWrapper: interactions
|
||||
|
||||
readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen)
|
||||
readonly property bool hasSpecialWorkspace: (monitor?.lastIpcObject.specialWorkspace?.name.length ?? 0) > 0
|
||||
|
|
@ -229,6 +230,8 @@ StyledWindow {
|
|||
}
|
||||
|
||||
Interactions {
|
||||
id: interactions
|
||||
|
||||
screen: root.screen
|
||||
popouts: panels.popouts
|
||||
visibilities: visibilities
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ void AppearanceRounding::bindTokens(RoundingTokens* tokens) {
|
|||
connectTokenSignals(tokens, this);
|
||||
}
|
||||
|
||||
int AppearanceRounding::extraSmall() const {
|
||||
return m_tokens ? static_cast<int>(m_tokens->extraSmall() * m_scale) : 0;
|
||||
}
|
||||
|
||||
int AppearanceRounding::small() const {
|
||||
return m_tokens ? static_cast<int>(m_tokens->small() * m_scale) : 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class AppearanceRounding : public ConfigObject {
|
|||
|
||||
CONFIG_PROPERTY(qreal, scale, 1)
|
||||
|
||||
Q_PROPERTY(int extraSmall READ extraSmall NOTIFY valuesChanged)
|
||||
Q_PROPERTY(int small READ small NOTIFY valuesChanged)
|
||||
Q_PROPERTY(int normal READ normal NOTIFY valuesChanged)
|
||||
Q_PROPERTY(int large READ large NOTIFY valuesChanged)
|
||||
|
|
@ -30,6 +31,7 @@ public:
|
|||
|
||||
void bindTokens(RoundingTokens* tokens);
|
||||
|
||||
[[nodiscard]] int extraSmall() const;
|
||||
[[nodiscard]] int small() const;
|
||||
[[nodiscard]] int normal() const;
|
||||
[[nodiscard]] int large() const;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
#include "tokens.hpp"
|
||||
#include "config.hpp"
|
||||
#include "monitorconfigmanager.hpp"
|
||||
|
||||
#include <qqmlengine.h>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class RoundingTokens : public ConfigObject {
|
|||
Q_OBJECT
|
||||
QML_ANONYMOUS
|
||||
|
||||
CONFIG_PROPERTY(int, extraSmall, 4)
|
||||
CONFIG_PROPERTY(int, small, 12)
|
||||
CONFIG_PROPERTY(int, normal, 17)
|
||||
CONFIG_PROPERTY(int, large, 25)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue