parent
1f3656c2f6
commit
3bdffca061
4 changed files with 285 additions and 75 deletions
|
|
@ -3,7 +3,7 @@ pragma ComponentBehavior: Bound
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
import Caelestia.Internal
|
||||||
import Caelestia.Services
|
import Caelestia.Services
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.services
|
import qs.services
|
||||||
|
|
@ -55,25 +55,29 @@ Item {
|
||||||
service: Audio.cava
|
service: Audio.cava
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
VisualiserBars {
|
||||||
id: content
|
id: bars
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Config.border.thickness
|
anchors.margins: Config.border.thickness
|
||||||
anchors.leftMargin: Visibilities.bars.get(root.screen).exclusiveZone + Appearance.spacing.small * Config.background.visualiser.spacing
|
anchors.leftMargin: Visibilities.bars.get(root.screen).exclusiveZone + Appearance.spacing.small * Config.background.visualiser.spacing
|
||||||
|
|
||||||
Side {
|
values: Audio.cava.values
|
||||||
content: content
|
primaryColor: Qt.alpha(Colours.palette.m3primary, 0.7)
|
||||||
}
|
secondaryColor: Qt.alpha(Colours.palette.m3inversePrimary, 0.7)
|
||||||
Side {
|
rounding: Appearance.rounding.small * Config.background.visualiser.rounding
|
||||||
content: content
|
spacing: Appearance.spacing.small * Config.background.visualiser.spacing
|
||||||
isRight: true
|
animationDuration: Appearance.anim.durations.normal
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on anchors.leftMargin {
|
Behavior on anchors.leftMargin {
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FrameAnimation {
|
||||||
|
running: root.opacity > 0 && !bars.settled
|
||||||
|
onTriggered: bars.advance(frameTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,69 +89,4 @@ Item {
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
component Side: Repeater {
|
|
||||||
id: side
|
|
||||||
|
|
||||||
required property Item content
|
|
||||||
property bool isRight
|
|
||||||
|
|
||||||
model: Config.services.visualiserBars
|
|
||||||
|
|
||||||
ClippingRectangle {
|
|
||||||
id: bar
|
|
||||||
|
|
||||||
required property int modelData
|
|
||||||
property real value: Math.max(0, Math.min(1, Audio.cava.values[side.isRight ? modelData : side.count - modelData - 1]))
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
x: modelData * ((side.content.width * 0.4) / Config.services.visualiserBars) + (side.isRight ? side.content.width * 0.6 : 0)
|
|
||||||
implicitWidth: (side.content.width * 0.4) / Config.services.visualiserBars - Appearance.spacing.small * Config.background.visualiser.spacing
|
|
||||||
|
|
||||||
y: side.content.height - height
|
|
||||||
implicitHeight: bar.value * side.content.height * 0.4
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
topLeftRadius: Appearance.rounding.small * Config.background.visualiser.rounding
|
|
||||||
topRightRadius: Appearance.rounding.small * Config.background.visualiser.rounding
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
topLeftRadius: parent.topLeftRadius
|
|
||||||
topRightRadius: parent.topRightRadius
|
|
||||||
|
|
||||||
gradient: Gradient {
|
|
||||||
orientation: Gradient.Vertical
|
|
||||||
|
|
||||||
GradientStop {
|
|
||||||
position: 0
|
|
||||||
color: Qt.alpha(Colours.palette.m3primary, 0.7)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
CAnim {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GradientStop {
|
|
||||||
position: 1
|
|
||||||
color: Qt.alpha(Colours.palette.m3inversePrimary, 0.7)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
CAnim {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
y: parent.height - height
|
|
||||||
implicitHeight: side.content.height * 0.4
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on value {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.small
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ qml_module(caelestia-internal
|
||||||
hyprextras.hpp hyprextras.cpp
|
hyprextras.hpp hyprextras.cpp
|
||||||
logindmanager.hpp logindmanager.cpp
|
logindmanager.hpp logindmanager.cpp
|
||||||
sparklineitem.hpp sparklineitem.cpp
|
sparklineitem.hpp sparklineitem.cpp
|
||||||
|
visualiserbars.hpp visualiserbars.cpp
|
||||||
LIBRARIES
|
LIBRARIES
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
Qt::Quick
|
Qt::Quick
|
||||||
|
|
|
||||||
198
plugin/src/Caelestia/Internal/visualiserbars.cpp
Normal file
198
plugin/src/Caelestia/Internal/visualiserbars.cpp
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
#include "visualiserbars.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <qbrush.h>
|
||||||
|
#include <qpainter.h>
|
||||||
|
#include <qpainterpath.h>
|
||||||
|
#include <qpen.h>
|
||||||
|
|
||||||
|
namespace caelestia::internal {
|
||||||
|
|
||||||
|
VisualiserBars::VisualiserBars(QQuickItem* parent)
|
||||||
|
: QQuickPaintedItem(parent) {
|
||||||
|
setAntialiasing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::advance(qreal dt) {
|
||||||
|
if (m_displayValues.isEmpty() || m_settled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// dt is in seconds (from FrameAnimation.frameTime), convert to ms
|
||||||
|
const qreal dtMs = dt * 1000.0;
|
||||||
|
const qreal tau = m_animationDuration / 3.0;
|
||||||
|
const qreal alpha = 1.0 - std::exp(-dtMs / tau);
|
||||||
|
|
||||||
|
bool allSettled = true;
|
||||||
|
|
||||||
|
for (qsizetype i = 0; i < m_displayValues.size(); ++i) {
|
||||||
|
const double diff = m_targetValues[i] - m_displayValues[i];
|
||||||
|
|
||||||
|
if (std::abs(diff) > 0.001) {
|
||||||
|
m_displayValues[i] += diff * alpha;
|
||||||
|
allSettled = false;
|
||||||
|
} else {
|
||||||
|
m_displayValues[i] = m_targetValues[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
if (allSettled && !m_settled) {
|
||||||
|
m_settled = true;
|
||||||
|
emit settledChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::paint(QPainter* painter) {
|
||||||
|
if (m_displayValues.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
painter->setPen(Qt::NoPen);
|
||||||
|
|
||||||
|
const qreal h = height();
|
||||||
|
const qreal maxBarHeight = h * 0.4;
|
||||||
|
|
||||||
|
QLinearGradient gradient(0, h - maxBarHeight, 0, h);
|
||||||
|
gradient.setColorAt(0, m_primaryColor);
|
||||||
|
gradient.setColorAt(1, m_secondaryColor);
|
||||||
|
painter->setBrush(gradient);
|
||||||
|
|
||||||
|
drawSide(painter, false);
|
||||||
|
drawSide(painter, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::drawSide(QPainter* painter, bool rightSide) {
|
||||||
|
const qreal w = width();
|
||||||
|
const qreal h = height();
|
||||||
|
const auto count = m_displayValues.size();
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const qreal sideWidth = w * 0.4;
|
||||||
|
const qreal slotWidth = sideWidth / static_cast<qreal>(count);
|
||||||
|
const qreal barWidth = slotWidth - m_spacing;
|
||||||
|
|
||||||
|
if (barWidth <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const qreal sideOffset = rightSide ? w * 0.6 : 0;
|
||||||
|
const qreal maxBarHeight = h * 0.4;
|
||||||
|
|
||||||
|
for (qsizetype i = 0; i < count; ++i) {
|
||||||
|
const qsizetype valueIndex = rightSide ? i : (count - i - 1);
|
||||||
|
const qreal value = std::clamp(m_displayValues[valueIndex], 0.0, 1.0);
|
||||||
|
const qreal barHeight = value * maxBarHeight;
|
||||||
|
|
||||||
|
if (barHeight <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const qreal x = static_cast<qreal>(i) * slotWidth + sideOffset;
|
||||||
|
const qreal y = h - barHeight;
|
||||||
|
const qreal r = std::min({ m_rounding, barWidth / 2.0, barHeight });
|
||||||
|
|
||||||
|
QPainterPath path;
|
||||||
|
path.moveTo(x, h);
|
||||||
|
path.lineTo(x, y + r);
|
||||||
|
|
||||||
|
if (r > 0) {
|
||||||
|
path.arcTo(x, y, r * 2, r * 2, 180, -90);
|
||||||
|
path.lineTo(x + barWidth - r, y);
|
||||||
|
path.arcTo(x + barWidth - r * 2, y, r * 2, r * 2, 90, -90);
|
||||||
|
} else {
|
||||||
|
path.lineTo(x, y);
|
||||||
|
path.lineTo(x + barWidth, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.lineTo(x + barWidth, h);
|
||||||
|
path.closeSubpath();
|
||||||
|
|
||||||
|
painter->drawPath(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<double> VisualiserBars::values() const {
|
||||||
|
return m_targetValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setValues(const QVector<double>& values) {
|
||||||
|
m_targetValues = values;
|
||||||
|
|
||||||
|
if (m_displayValues.size() != values.size()) {
|
||||||
|
m_displayValues.resize(values.size(), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_settled) {
|
||||||
|
m_settled = false;
|
||||||
|
emit settledChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit valuesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VisualiserBars::settled() const {
|
||||||
|
return m_settled;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor VisualiserBars::primaryColor() const {
|
||||||
|
return m_primaryColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setPrimaryColor(const QColor& color) {
|
||||||
|
if (m_primaryColor == color)
|
||||||
|
return;
|
||||||
|
m_primaryColor = color;
|
||||||
|
emit primaryColorChanged();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor VisualiserBars::secondaryColor() const {
|
||||||
|
return m_secondaryColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setSecondaryColor(const QColor& color) {
|
||||||
|
if (m_secondaryColor == color)
|
||||||
|
return;
|
||||||
|
m_secondaryColor = color;
|
||||||
|
emit secondaryColorChanged();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal VisualiserBars::rounding() const {
|
||||||
|
return m_rounding;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setRounding(qreal rounding) {
|
||||||
|
if (qFuzzyCompare(m_rounding, rounding))
|
||||||
|
return;
|
||||||
|
m_rounding = rounding;
|
||||||
|
emit roundingChanged();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal VisualiserBars::spacing() const {
|
||||||
|
return m_spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setSpacing(qreal spacing) {
|
||||||
|
if (qFuzzyCompare(m_spacing, spacing))
|
||||||
|
return;
|
||||||
|
m_spacing = spacing;
|
||||||
|
emit spacingChanged();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
int VisualiserBars::animationDuration() const {
|
||||||
|
return m_animationDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setAnimationDuration(int duration) {
|
||||||
|
if (m_animationDuration == duration)
|
||||||
|
return;
|
||||||
|
m_animationDuration = duration;
|
||||||
|
emit animationDurationChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace caelestia::internal
|
||||||
72
plugin/src/Caelestia/Internal/visualiserbars.hpp
Normal file
72
plugin/src/Caelestia/Internal/visualiserbars.hpp
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qcolor.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
#include <qquickpainteditem.h>
|
||||||
|
#include <qvector.h>
|
||||||
|
|
||||||
|
namespace caelestia::internal {
|
||||||
|
|
||||||
|
class VisualiserBars : public QQuickPaintedItem {
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
Q_PROPERTY(QVector<double> values READ values WRITE setValues NOTIFY valuesChanged)
|
||||||
|
Q_PROPERTY(QColor primaryColor READ primaryColor WRITE setPrimaryColor NOTIFY primaryColorChanged)
|
||||||
|
Q_PROPERTY(QColor secondaryColor READ secondaryColor WRITE setSecondaryColor NOTIFY secondaryColorChanged)
|
||||||
|
Q_PROPERTY(qreal rounding READ rounding WRITE setRounding NOTIFY roundingChanged)
|
||||||
|
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
||||||
|
Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged)
|
||||||
|
Q_PROPERTY(bool settled READ settled NOTIFY settledChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit VisualiserBars(QQuickItem* parent = nullptr);
|
||||||
|
|
||||||
|
void paint(QPainter* painter) override;
|
||||||
|
|
||||||
|
Q_INVOKABLE void advance(qreal dt);
|
||||||
|
|
||||||
|
[[nodiscard]] QVector<double> values() const;
|
||||||
|
void setValues(const QVector<double>& values);
|
||||||
|
|
||||||
|
[[nodiscard]] QColor primaryColor() const;
|
||||||
|
void setPrimaryColor(const QColor& color);
|
||||||
|
|
||||||
|
[[nodiscard]] QColor secondaryColor() const;
|
||||||
|
void setSecondaryColor(const QColor& color);
|
||||||
|
|
||||||
|
[[nodiscard]] qreal rounding() const;
|
||||||
|
void setRounding(qreal rounding);
|
||||||
|
|
||||||
|
[[nodiscard]] qreal spacing() const;
|
||||||
|
void setSpacing(qreal spacing);
|
||||||
|
|
||||||
|
[[nodiscard]] int animationDuration() const;
|
||||||
|
void setAnimationDuration(int duration);
|
||||||
|
|
||||||
|
[[nodiscard]] bool settled() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void valuesChanged();
|
||||||
|
void primaryColorChanged();
|
||||||
|
void secondaryColorChanged();
|
||||||
|
void roundingChanged();
|
||||||
|
void spacingChanged();
|
||||||
|
void animationDurationChanged();
|
||||||
|
void settledChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drawSide(QPainter* painter, bool rightSide);
|
||||||
|
|
||||||
|
QVector<double> m_targetValues;
|
||||||
|
QVector<double> m_displayValues;
|
||||||
|
QColor m_primaryColor;
|
||||||
|
QColor m_secondaryColor;
|
||||||
|
qreal m_rounding = 0.0;
|
||||||
|
qreal m_spacing = 0.0;
|
||||||
|
int m_animationDuration = 200;
|
||||||
|
bool m_settled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace caelestia::internal
|
||||||
Loading…
Add table
Reference in a new issue