fedora
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
clip: true
|
||||
|
||||
// Primary line
|
||||
property var values: []
|
||||
property color color: Color.mPrimary
|
||||
|
||||
// Optional secondary line
|
||||
property var values2: []
|
||||
property color color2: Color.mError
|
||||
|
||||
// Range settings for primary line
|
||||
property real minValue: 0
|
||||
property real maxValue: 100
|
||||
|
||||
// Range settings for secondary line (defaults to primary range)
|
||||
property real minValue2: minValue
|
||||
property real maxValue2: maxValue
|
||||
|
||||
// Style settings
|
||||
property real strokeWidth: 1
|
||||
property bool fill: true
|
||||
property real fillOpacity: 0.15
|
||||
property real antialiasing: 0.5
|
||||
|
||||
// Smooth scrolling interval (how often data updates)
|
||||
property int updateInterval: 1000
|
||||
|
||||
// Animate scale changes (for network graphs with dynamic max)
|
||||
property bool animateScale: false
|
||||
|
||||
// Vertical padding (percentage of range) to keep values from touching edges
|
||||
readonly property real curvePadding: 0.12
|
||||
|
||||
readonly property bool hasData: values.length >= 4
|
||||
readonly property bool hasData2: values2.length >= 4
|
||||
|
||||
// Scale animation state
|
||||
property real _targetMax1: maxValue
|
||||
property real _targetMax2: maxValue2
|
||||
property real _animMax1: maxValue
|
||||
property real _animMax2: maxValue2
|
||||
|
||||
onMaxValueChanged: {
|
||||
_targetMax1 = maxValue;
|
||||
if (animateScale && _ready1) {
|
||||
_scaleTimer.start();
|
||||
} else {
|
||||
_animMax1 = maxValue;
|
||||
}
|
||||
}
|
||||
|
||||
onMaxValue2Changed: {
|
||||
_targetMax2 = maxValue2;
|
||||
if (animateScale && _ready2) {
|
||||
_scaleTimer.start();
|
||||
} else {
|
||||
_animMax2 = maxValue2;
|
||||
}
|
||||
}
|
||||
|
||||
// Effective max values (animated or direct)
|
||||
readonly property real _effectiveMax1: animateScale ? _animMax1 : maxValue
|
||||
readonly property real _effectiveMax2: animateScale ? _animMax2 : maxValue2
|
||||
|
||||
// Scroll state (driven by NumberAnimation)
|
||||
property real _t1: 1.0
|
||||
property bool _ready1: false
|
||||
property real _pred1: 0
|
||||
|
||||
property real _t2: 1.0
|
||||
property bool _ready2: false
|
||||
property real _pred2: 0
|
||||
|
||||
// Frame-accurate scroll animations tied to Qt's render loop
|
||||
NumberAnimation {
|
||||
id: _scrollAnim1
|
||||
target: root
|
||||
property: "_t1"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: root.updateInterval
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: _scrollAnim2
|
||||
target: root
|
||||
property: "_t2"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: root.updateInterval
|
||||
}
|
||||
|
||||
onValuesChanged: {
|
||||
if (values.length < 4)
|
||||
return;
|
||||
|
||||
const last = values[values.length - 1];
|
||||
const prev = values[values.length - 2];
|
||||
_pred1 = Math.max(minValue, last + (last - prev) * 0.5);
|
||||
|
||||
if (!_ready1)
|
||||
_ready1 = true;
|
||||
_scrollAnim1.restart();
|
||||
}
|
||||
|
||||
onValues2Changed: {
|
||||
if (values2.length < 4)
|
||||
return;
|
||||
|
||||
const last = values2[values2.length - 1];
|
||||
const prev = values2[values2.length - 2];
|
||||
_pred2 = Math.max(minValue2, last + (last - prev) * 0.5);
|
||||
|
||||
if (!_ready2)
|
||||
_ready2 = true;
|
||||
_scrollAnim2.restart();
|
||||
}
|
||||
|
||||
// Scale animation timer (only needed for animateScale mode)
|
||||
Timer {
|
||||
id: _scaleTimer
|
||||
interval: 16
|
||||
repeat: true
|
||||
|
||||
onTriggered: {
|
||||
const scaleLerp = 0.15;
|
||||
const threshold = 0.5;
|
||||
let stillAnimating = false;
|
||||
|
||||
if (Math.abs(root._animMax1 - root._targetMax1) > threshold) {
|
||||
root._animMax1 += (root._targetMax1 - root._animMax1) * scaleLerp;
|
||||
stillAnimating = true;
|
||||
} else if (root._animMax1 !== root._targetMax1) {
|
||||
root._animMax1 = root._targetMax1;
|
||||
}
|
||||
|
||||
if (Math.abs(root._animMax2 - root._targetMax2) > threshold) {
|
||||
root._animMax2 += (root._targetMax2 - root._animMax2) * scaleLerp;
|
||||
stillAnimating = true;
|
||||
} else if (root._animMax2 !== root._targetMax2) {
|
||||
root._animMax2 = root._targetMax2;
|
||||
}
|
||||
|
||||
if (!stillAnimating)
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize a value to [0, 1] with padding applied
|
||||
function _normalize(val, minVal, maxVal) {
|
||||
let range = maxVal - minVal;
|
||||
if (range <= 0)
|
||||
return 0.5;
|
||||
let padding = range * curvePadding;
|
||||
let paddedMin = minVal - padding;
|
||||
let paddedRange = (maxVal + padding) - paddedMin;
|
||||
return Math.max(0, Math.min(1, (val - paddedMin) / paddedRange));
|
||||
}
|
||||
|
||||
// Data texture built from Rectangles instead of Canvas.
|
||||
// Each Rectangle is one data point, color-coded with normalized values.
|
||||
// R channel = primary, G channel = secondary.
|
||||
Item {
|
||||
id: _dataRow
|
||||
width: Math.max(root.values.length + 1, root.values2.length + 1, 4)
|
||||
height: 1
|
||||
|
||||
Repeater {
|
||||
model: _dataRow.width
|
||||
|
||||
Rectangle {
|
||||
required property int index
|
||||
x: index
|
||||
width: 1
|
||||
height: 1
|
||||
color: {
|
||||
let r = 0, g = 0;
|
||||
let n1 = root.values.length;
|
||||
let n2 = root.values2.length;
|
||||
let eMax1 = root._effectiveMax1;
|
||||
let eMax2 = root._effectiveMax2;
|
||||
|
||||
if (index < n1)
|
||||
r = root._normalize(root.values[index], root.minValue, eMax1);
|
||||
else if (n1 > 0)
|
||||
r = root._normalize(root._pred1, root.minValue, eMax1);
|
||||
|
||||
if (index < n2)
|
||||
g = root._normalize(root.values2[index], root.minValue2, eMax2);
|
||||
else if (n2 > 0)
|
||||
g = root._normalize(root._pred2, root.minValue2, eMax2);
|
||||
|
||||
return Qt.rgba(r, g, 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShaderEffectSource {
|
||||
id: _dataTex
|
||||
sourceItem: _dataRow
|
||||
textureSize: Qt.size(_dataRow.width, 1)
|
||||
live: true
|
||||
smooth: false
|
||||
hideSource: true
|
||||
}
|
||||
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
visible: (root.hasData || root.hasData2) && width > 0 && height > 0
|
||||
|
||||
property variant dataSource: _dataTex
|
||||
property color lineColor1: root.color
|
||||
property color lineColor2: root.color2
|
||||
property real count1: root.values.length
|
||||
property real count2: root.values2.length
|
||||
property real scroll1: root._t1
|
||||
property real scroll2: root._t2
|
||||
property real lineWidth: root.strokeWidth
|
||||
property real graphFillOpacity: root.fill ? root.fillOpacity : 0.0
|
||||
property real texWidth: _dataRow.width
|
||||
property real resY: height
|
||||
property real aaSize: root.antialiasing
|
||||
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/graph.frag.qsb")
|
||||
blending: true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user