added two new themes, reworked Crypto.qml and Stats.qml, standardized the buttons for the modules, added a new Sidebar widget which contains new 3 modules
This commit is contained in:
@@ -385,7 +385,7 @@ ModuleChip {
|
||||
property bool isActive: interactive && (root.pp.profile === profileValue)
|
||||
|
||||
height: 38
|
||||
radius: 10
|
||||
radius: 8
|
||||
color: isActive ? root.iconColor
|
||||
: Qt.rgba(root.iconColor.r, root.iconColor.g, root.iconColor.b,
|
||||
(profileHover.containsMouse && interactive) ? 0.14 : 0.06)
|
||||
|
||||
@@ -89,7 +89,7 @@ ModuleChip {
|
||||
id: navBtn
|
||||
required property int direction
|
||||
|
||||
width: 26; height: 26; radius: 6
|
||||
width: 26; height: 26; radius: 8
|
||||
|
||||
color: btnMouse.containsMouse
|
||||
? (btnMouse.pressed ? root.t.calArrowBgPress : root.t.calArrowBgHover)
|
||||
|
||||
@@ -40,6 +40,7 @@ ModuleChip {
|
||||
property string localVsCur: "usd"
|
||||
property int localDecimals: cfg.cryptoDecimals
|
||||
property int localRefreshSec: cfg.cryptoRefreshSec
|
||||
property int localRotateSec: cfg.cryptoRotateSec
|
||||
property bool localShowIcon: cfg.cryptoShowIcon
|
||||
|
||||
function resolveWithPersistence() {
|
||||
@@ -49,6 +50,7 @@ ModuleChip {
|
||||
root.activeCoinIndex = 0
|
||||
root.localDecimals = cfg.cryptoDecimals
|
||||
root.localRefreshSec = cfg.cryptoRefreshSec
|
||||
root.localRotateSec = cfg.cryptoRotateSec
|
||||
root.localShowIcon = cfg.cryptoShowIcon
|
||||
}
|
||||
|
||||
@@ -56,6 +58,7 @@ ModuleChip {
|
||||
const pairs = root.localPairs
|
||||
const decimals = Math.round(root.localDecimals)
|
||||
const refreshSec = Math.round(root.localRefreshSec)
|
||||
const rotateSec = Math.round(root.localRotateSec)
|
||||
const showIcon = root.localShowIcon
|
||||
|
||||
const pairsQml = "[" + pairs.map(p =>
|
||||
@@ -64,7 +67,8 @@ ModuleChip {
|
||||
|
||||
configWriter.writeInts([
|
||||
{ key: "cryptoDecimals", value: decimals },
|
||||
{ key: "cryptoRefreshSec", value: refreshSec }
|
||||
{ key: "cryptoRefreshSec", value: refreshSec },
|
||||
{ key: "cryptoRotateSec", value: rotateSec }
|
||||
])
|
||||
configWriter.writeBool("cryptoShowIcon", showIcon)
|
||||
configWriter.writeArray("cryptoPairs", pairsQml)
|
||||
@@ -87,6 +91,10 @@ ModuleChip {
|
||||
property bool loading: false
|
||||
property bool loadingVisible: false
|
||||
property bool hasError: false
|
||||
property var sparklineData: []
|
||||
property real change7d: 0
|
||||
property var _priceCache: ({})
|
||||
property int _fetchGen: 0
|
||||
|
||||
Timer {
|
||||
id: minLoadTimer
|
||||
@@ -140,66 +148,148 @@ ModuleChip {
|
||||
return val.slice(0, maxLen).replace(/[\x00-\x1f\x7f-\x9f]/g, "")
|
||||
}
|
||||
|
||||
function rotatePrev() {
|
||||
if (root.localPairs.length <= 1) return
|
||||
root.activeCoinIndex = (root.activeCoinIndex - 1 + root.localPairs.length) % root.localPairs.length
|
||||
root._applyFromCache()
|
||||
rotateTimer.restart()
|
||||
}
|
||||
|
||||
function rotateNext() {
|
||||
if (root.localPairs.length <= 1) return
|
||||
root.activeCoinIndex = (root.activeCoinIndex + 1) % root.localPairs.length
|
||||
root._applyFromCache()
|
||||
rotateTimer.restart()
|
||||
}
|
||||
|
||||
function _applyFromCache() {
|
||||
var key = root.localCoinId + "_" + root.localActiveCurrency
|
||||
var entry = root._priceCache[key]
|
||||
if (!entry) { console.log("Crypto: no cache entry for", key); return }
|
||||
console.log("Crypto: applying", key, "price=" + entry.price)
|
||||
price = entry.price
|
||||
change24h = entry.change24h
|
||||
high24h = entry.high24h
|
||||
low24h = entry.low24h
|
||||
marketCap = entry.marketCap
|
||||
volume24h = entry.volume24h
|
||||
rank = entry.rank
|
||||
circulatingSupply = entry.circulatingSupply
|
||||
ath = entry.ath
|
||||
athChange = entry.athChange
|
||||
coinSymbol = safeString(entry.symbol, 8, root.localCoinId).toUpperCase()
|
||||
coinName = safeString(entry.name, 50, "")
|
||||
lastUpdated = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss")
|
||||
root.displayCurrency = root.localActiveCurrency
|
||||
sparklineData = entry.sparkline || []
|
||||
change7d = entry.change7d || 0
|
||||
hasError = false
|
||||
}
|
||||
|
||||
function fetchPrice() {
|
||||
if (loading) return
|
||||
loading = true
|
||||
hasError = false
|
||||
_fetchGen++
|
||||
var myGen = _fetchGen
|
||||
|
||||
const url =
|
||||
"https://api.coingecko.com/api/v3/coins/markets" +
|
||||
"?vs_currency=" + encodeURIComponent(localActiveCurrency || "usd") +
|
||||
"&ids=" + encodeURIComponent(localCoinId || "bitcoin") +
|
||||
"&order=market_cap_desc&per_page=1&page=1" +
|
||||
"&sparkline=false&price_change_percentage=24h"
|
||||
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.timeout = 15000
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== XMLHttpRequest.DONE) return
|
||||
loading = false
|
||||
if (xhr.status !== 200) { hasError = true; return }
|
||||
try {
|
||||
const text = xhr.responseText
|
||||
if (text.length > 65536) { hasError = true; return }
|
||||
const data = JSON.parse(text)
|
||||
if (!Array.isArray(data) || data.length === 0) { hasError = true; return }
|
||||
const coin = data[0]
|
||||
if (typeof coin !== "object" || coin === null) { hasError = true; return }
|
||||
price = safeNumber(coin.current_price, -1)
|
||||
change24h = safeNumber(coin.price_change_percentage_24h, 0)
|
||||
high24h = safeNumber(coin.high_24h, 0)
|
||||
low24h = safeNumber(coin.low_24h, 0)
|
||||
marketCap = safeNumber(coin.market_cap, 0)
|
||||
volume24h = safeNumber(coin.total_volume, 0)
|
||||
rank = Math.trunc(safeNumber(coin.market_cap_rank, 0))
|
||||
circulatingSupply = safeNumber(coin.circulating_supply, 0)
|
||||
ath = safeNumber(coin.ath, 0)
|
||||
athChange = safeNumber(coin.ath_change_percentage, 0)
|
||||
coinSymbol = safeString(coin.symbol, 8, localCoinId).toUpperCase()
|
||||
coinName = safeString(coin.name, 50, "")
|
||||
lastUpdated = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss")
|
||||
root.displayCurrency = root.localActiveCurrency
|
||||
root.activeCoinIndex = (root.activeCoinIndex + 1) % Math.max(1, root.localPairs.length)
|
||||
} catch (_) {
|
||||
hasError = true
|
||||
}
|
||||
var coinGroups = {}
|
||||
for (var i = 0; i < root.localPairs.length; i++) {
|
||||
var p = root.localPairs[i]
|
||||
if (!coinGroups[p.currency]) coinGroups[p.currency] = []
|
||||
var coinList = coinGroups[p.currency]
|
||||
if (coinList.indexOf(p.coin) === -1) coinList.push(p.coin)
|
||||
}
|
||||
|
||||
var currencies = Object.keys(coinGroups)
|
||||
var pending = currencies.length
|
||||
|
||||
if (pending === 0) { loading = false; hasError = true; return }
|
||||
|
||||
for (var ci = 0; ci < currencies.length; ci++) {
|
||||
var cur = currencies[ci]
|
||||
var ids = coinGroups[cur].join(",")
|
||||
|
||||
var url =
|
||||
"https://api.coingecko.com/api/v3/coins/markets" +
|
||||
"?vs_currency=" + encodeURIComponent(cur || "usd") +
|
||||
"&ids=" + encodeURIComponent(ids) +
|
||||
"&order=market_cap_desc&per_page=250&page=1" +
|
||||
"&sparkline=true&price_change_percentage=24h,7d"
|
||||
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr._currency = cur
|
||||
xhr.timeout = 15000
|
||||
xhr.onreadystatechange = (function(xhr, cur, myGen) {
|
||||
return function() {
|
||||
if (myGen !== root._fetchGen) return
|
||||
if (xhr.readyState !== XMLHttpRequest.DONE) return
|
||||
if (xhr.status !== 200) { hasError = true }
|
||||
else {
|
||||
try {
|
||||
var text = xhr.responseText
|
||||
if (text.length <= 65536) {
|
||||
var data = JSON.parse(text)
|
||||
if (Array.isArray(data)) {
|
||||
for (var di = 0; di < data.length; di++) {
|
||||
var coin = data[di]
|
||||
var key = coin.id + "_" + cur
|
||||
console.log("Crypto: cache", key, "price=" + coin.current_price)
|
||||
root._priceCache[key] = {
|
||||
price: safeNumber(coin.current_price, -1),
|
||||
change24h: safeNumber(coin.price_change_percentage_24h, 0),
|
||||
high24h: safeNumber(coin.high_24h, 0),
|
||||
low24h: safeNumber(coin.low_24h, 0),
|
||||
marketCap: safeNumber(coin.market_cap, 0),
|
||||
volume24h: safeNumber(coin.total_volume, 0),
|
||||
rank: Math.trunc(safeNumber(coin.market_cap_rank, 0)),
|
||||
circulatingSupply: safeNumber(coin.circulating_supply, 0),
|
||||
ath: safeNumber(coin.ath, 0),
|
||||
athChange: safeNumber(coin.ath_change_percentage, 0),
|
||||
symbol: safeString(coin.symbol, 8, coin.id),
|
||||
name: safeString(coin.name, 50, ""),
|
||||
sparkline: (coin.sparkline_in_7d && Array.isArray(coin.sparkline_in_7d.price))
|
||||
? coin.sparkline_in_7d.price : [],
|
||||
change7d: safeNumber(coin.price_change_percentage_7d_in_currency, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
if (--pending <= 0) {
|
||||
loading = false
|
||||
root._applyFromCache()
|
||||
}
|
||||
}
|
||||
})(xhr, cur, myGen)
|
||||
|
||||
xhr.open("GET", url)
|
||||
xhr.setRequestHeader("Accept", "application/json")
|
||||
xhr.send()
|
||||
}
|
||||
xhr.open("GET", url)
|
||||
xhr.setRequestHeader("Accept", "application/json")
|
||||
xhr.send()
|
||||
}
|
||||
|
||||
Component.onCompleted: { resolveWithPersistence(); fetchPrice() }
|
||||
|
||||
Timer {
|
||||
id: rotateTimer
|
||||
interval: root.localRotateSec * 1000
|
||||
running: root.localRotateSec > 0 && root.localPairs.length > 1
|
||||
repeat: true
|
||||
onTriggered: root.rotateNext()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: root.localRefreshSec * 1000
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: root.fetchPrice()
|
||||
}
|
||||
|
||||
onLocalRefreshSecChanged: refreshTimer.restart()
|
||||
onLocalRotateSecChanged: rotateTimer.restart()
|
||||
|
||||
RowLayout {
|
||||
id: chipRow
|
||||
@@ -277,36 +367,64 @@ ModuleChip {
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 30
|
||||
radius: 10
|
||||
height: 36
|
||||
radius: 12
|
||||
color: t.cryptoFieldBg
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 3
|
||||
spacing: 3
|
||||
anchors.margins: 4
|
||||
spacing: 4
|
||||
|
||||
component TabBtn: Rectangle {
|
||||
required property string label
|
||||
required property int tabIndex
|
||||
property string icon: ""
|
||||
Layout.fillWidth: true
|
||||
height: 24
|
||||
radius: 8
|
||||
height: 28
|
||||
radius: 9
|
||||
|
||||
readonly property bool active: cryptoPopup.activeTab === tabIndex
|
||||
|
||||
color: active
|
||||
? (t.accent)
|
||||
: (tm.containsMouse ? (t.cryptoFieldBgFocus) : "transparent")
|
||||
Behavior on color { ColorAnimation { duration: 130 } }
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
|
||||
Text {
|
||||
border.color: active ? Qt.rgba(1, 1, 1, 0.18) : "transparent"
|
||||
border.width: 1
|
||||
Behavior on border.color { ColorAnimation { duration: 150 } }
|
||||
|
||||
scale: tm.pressed ? 0.93 : 1.0
|
||||
Behavior on scale { NumberAnimation { duration: 90; easing.type: Easing.OutQuad } }
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
text: parent.label
|
||||
color: parent.active ? (t.cryptoTextOnAccent) : (t.cryptoPopupDim)
|
||||
font.pixelSize: 11
|
||||
font.bold: parent.active
|
||||
Behavior on color { ColorAnimation { duration: 130 } }
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: parent.parent.icon !== ""
|
||||
text: parent.parent.icon
|
||||
color: parent.parent.active ? (t.cryptoTextOnAccent) : (t.cryptoPopupDim)
|
||||
font.pixelSize: 11
|
||||
opacity: parent.parent.active ? 1.0 : 0.7
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
Behavior on opacity { NumberAnimation { duration: 150 } }
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: parent.parent.label
|
||||
color: parent.parent.active ? (t.cryptoTextOnAccent) : (t.cryptoPopupDim)
|
||||
font.pixelSize: 12
|
||||
font.bold: parent.parent.active
|
||||
font.letterSpacing: parent.parent.active ? 0.4 : 0.0
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
Behavior on font.letterSpacing { NumberAnimation { duration: 150 } }
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: tm
|
||||
anchors.fill: parent; hoverEnabled: true
|
||||
@@ -315,8 +433,8 @@ ModuleChip {
|
||||
}
|
||||
}
|
||||
|
||||
TabBtn { label: "Price"; tabIndex: 0 }
|
||||
TabBtn { label: "Settings"; tabIndex: 1 }
|
||||
TabBtn { label: "Price"; icon: ""; tabIndex: 0 }
|
||||
TabBtn { label: "Settings"; icon: ""; tabIndex: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,6 +608,113 @@ ModuleChip {
|
||||
|
||||
Rectangle { Layout.fillWidth: true; height: 1; color: t.calSeparator }
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 5
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Text {
|
||||
text: "7d Chart"
|
||||
color: t.cryptoPopupDim
|
||||
font.pixelSize: 12
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Rectangle {
|
||||
visible: root.sparklineData.length > 1
|
||||
width: badge7d.implicitWidth + 12
|
||||
height: 20; radius: 10
|
||||
color: root.change7d >= 0 ? (t.cryptoUpBg) : (t.cryptoDownBg)
|
||||
Text {
|
||||
id: badge7d
|
||||
anchors.centerIn: parent
|
||||
text: (root.change7d >= 0 ? "▲ +" : "▼ ") + root.change7d.toFixed(2) + "%"
|
||||
color: root.change7d >= 0 ? (t.cryptoUp) : (t.cryptoDown)
|
||||
font.pixelSize: 10
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: sparklineCanvas
|
||||
Layout.fillWidth: true
|
||||
height: 52
|
||||
|
||||
property var prices: root.sparklineData
|
||||
property bool isUp: root.change7d >= 0
|
||||
property string upColor: t.cryptoUp || "#4ade80"
|
||||
property string downColor: t.cryptoDown || "#f87171"
|
||||
|
||||
onPricesChanged: requestPaint()
|
||||
onIsUpChanged: requestPaint()
|
||||
onUpColorChanged: requestPaint()
|
||||
onDownColorChanged:requestPaint()
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
var pts = prices
|
||||
if (!pts || pts.length < 2) {
|
||||
ctx.fillStyle = Qt.rgba(1, 1, 1, 0.06)
|
||||
ctx.beginPath()
|
||||
ctx.roundedRect(0, 0, width, height, 6, 6)
|
||||
ctx.fill()
|
||||
return
|
||||
}
|
||||
var minP = pts[0], maxP = pts[0]
|
||||
for (var i = 1; i < pts.length; i++) {
|
||||
if (pts[i] < minP) minP = pts[i]
|
||||
if (pts[i] > maxP) maxP = pts[i]
|
||||
}
|
||||
var range = maxP - minP
|
||||
if (range === 0) range = 1
|
||||
var pad = 4, pw = width - pad * 2, ph = height - pad * 2
|
||||
var lineColor = isUp ? upColor : downColor
|
||||
|
||||
ctx.beginPath()
|
||||
for (var j = 0; j < pts.length; j++) {
|
||||
var x = pad + (j / (pts.length - 1)) * pw
|
||||
var y = pad + ph - ((pts[j] - minP) / range) * ph
|
||||
if (j === 0) ctx.moveTo(x, y)
|
||||
else ctx.lineTo(x, y)
|
||||
}
|
||||
ctx.lineTo(pad + pw, pad + ph)
|
||||
ctx.lineTo(pad, pad + ph)
|
||||
ctx.closePath()
|
||||
var grad = ctx.createLinearGradient(0, 0, 0, height)
|
||||
grad.addColorStop(0, Qt.rgba(
|
||||
isUp ? 0.29 : 0.97,
|
||||
isUp ? 0.87 : 0.44,
|
||||
isUp ? 0.50 : 0.44, 0.22))
|
||||
grad.addColorStop(1, Qt.rgba(0, 0, 0, 0))
|
||||
ctx.fillStyle = grad
|
||||
ctx.fill()
|
||||
|
||||
ctx.beginPath()
|
||||
for (var k = 0; k < pts.length; k++) {
|
||||
var lx = pad + (k / (pts.length - 1)) * pw
|
||||
var ly = pad + ph - ((pts[k] - minP) / range) * ph
|
||||
if (k === 0) ctx.moveTo(lx, ly)
|
||||
else ctx.lineTo(lx, ly)
|
||||
}
|
||||
ctx.strokeStyle = lineColor
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.lineJoin = "round"
|
||||
ctx.stroke()
|
||||
|
||||
var lx2 = pad + pw
|
||||
var ly2 = pad + ph - ((pts[pts.length - 1] - minP) / range) * ph
|
||||
ctx.beginPath()
|
||||
ctx.arc(lx2, ly2, 3, 0, Math.PI * 2)
|
||||
ctx.fillStyle = lineColor
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { Layout.fillWidth: true; height: 1; color: t.calSeparator }
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -500,6 +725,50 @@ ModuleChip {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.localPairs.length > 1
|
||||
width: 26; height: 26; radius: 8
|
||||
color: prevMouse.containsMouse
|
||||
? (prevMouse.pressed ? (t.accent) : (t.cryptoFieldBgFocus))
|
||||
: "transparent"
|
||||
Behavior on color { ColorAnimation { duration: 120 } }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "‹"
|
||||
color: t.cryptoIcon
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: prevMouse
|
||||
anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.rotatePrev()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.localPairs.length > 1
|
||||
width: 26; height: 26; radius: 8
|
||||
color: nextMouse.containsMouse
|
||||
? (nextMouse.pressed ? (t.accent) : (t.cryptoFieldBgFocus))
|
||||
: "transparent"
|
||||
Behavior on color { ColorAnimation { duration: 120 } }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "›"
|
||||
color: t.cryptoIcon
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nextMouse
|
||||
anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.rotateNext()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: refreshBtn
|
||||
width: 26; height: 26; radius: 8
|
||||
@@ -540,6 +809,7 @@ ModuleChip {
|
||||
ColumnLayout {
|
||||
id: settingsContent
|
||||
width: tabContainer.width
|
||||
height: tabContainer.height
|
||||
x: cryptoPopup.activeTab === 1 ? 0 : tabContainer.width
|
||||
opacity: cryptoPopup.activeTab === 1 ? 1.0 : 0.0
|
||||
Behavior on x { NumberAnimation { duration: 260; easing.type: Easing.OutCubic } }
|
||||
@@ -650,7 +920,7 @@ ModuleChip {
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "Pairs to cycle"
|
||||
@@ -661,11 +931,11 @@ ModuleChip {
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
spacing: 8
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
height: 24
|
||||
height: 28
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
@@ -756,6 +1026,7 @@ ModuleChip {
|
||||
root.localPairs = arr
|
||||
if (root.activeCoinIndex >= arr.length)
|
||||
root.activeCoinIndex = 0
|
||||
root.fetchPrice()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -885,41 +1156,51 @@ ModuleChip {
|
||||
root.localPairs = root.localPairs.concat([{ coin: c, currency: cur }])
|
||||
addCoinInput.text = ""
|
||||
addCurrencyInput.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 30; height: 30; radius: 8
|
||||
color: addPairMouse.containsMouse
|
||||
? (addPairMouse.pressed ? (t.accent) : (t.cryptoFieldBgFocus))
|
||||
: (t.cryptoFieldBg)
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
Text {
|
||||
anchors.centerIn: parent; text: "+"
|
||||
font.pixelSize: 14; font.bold: true
|
||||
color: t.cryptoPopupText
|
||||
}
|
||||
MouseArea {
|
||||
id: addPairMouse
|
||||
anchors.fill: parent; hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
const c = addCoinInput.text.trim().toLowerCase()
|
||||
const cur = addCurrencyInput.text.trim().toLowerCase() || root.localVsCur
|
||||
if (c !== "") {
|
||||
root.localPairs = root.localPairs.concat([{ coin: c, currency: cur }])
|
||||
addCoinInput.text = ""
|
||||
addCurrencyInput.text = ""
|
||||
root.fetchPrice()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 4
|
||||
height: 32; radius: 9
|
||||
color: addPairMouse.containsMouse
|
||||
? (addPairMouse.pressed ? (t.accent) : (t.cryptoFieldBgFocus))
|
||||
: (t.cryptoApplyIdleBg)
|
||||
Behavior on color { ColorAnimation { duration: 130 } }
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "+ Add Pair"
|
||||
color: addPairMouse.containsMouse ? (t.cryptoTextOnAccent) : (t.cryptoPopupText)
|
||||
font.pixelSize: 12; font.bold: true
|
||||
Behavior on color { ColorAnimation { duration: 130 } }
|
||||
}
|
||||
MouseArea {
|
||||
id: addPairMouse
|
||||
anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
const c = addCoinInput.text.trim().toLowerCase()
|
||||
const cur = addCurrencyInput.text.trim().toLowerCase() || root.localVsCur
|
||||
if (c !== "") {
|
||||
root.localPairs = root.localPairs.concat([{ coin: c, currency: cur }])
|
||||
addCoinInput.text = ""
|
||||
addCurrencyInput.text = ""
|
||||
root.fetchPrice()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
Rectangle { Layout.fillWidth: true; height: 1; color: t.calSeparator }
|
||||
|
||||
StepRow {
|
||||
label: "Decimals in label"
|
||||
value: root.localDecimals
|
||||
@@ -1015,6 +1296,93 @@ ModuleChip {
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: "Rotate (seconds)"
|
||||
color: t.cryptoPopupDim
|
||||
font.pixelSize: 12
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 24; height: 24; radius: 7
|
||||
color: rotDecMouse.containsMouse
|
||||
? (rotDecMouse.pressed ? (t.accent) : (t.cryptoFieldBgFocus))
|
||||
: (t.cryptoFieldBg)
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
Text { anchors.centerIn: parent; text: "−"; font.pixelSize: 14; font.bold: true; color: t.cryptoPopupText }
|
||||
MouseArea {
|
||||
id: rotDecMouse
|
||||
anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
|
||||
onClicked: if (root.localRotateSec > 1) root.localRotateSec -= 1
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 54; height: 24; radius: 6
|
||||
color: rotateInput.activeFocus ? (t.cryptoFieldBgFocus) : (t.cryptoFieldBg)
|
||||
border.color: rotateInput.activeFocus ? (t.accent) : "transparent"
|
||||
border.width: 1
|
||||
Behavior on color { ColorAnimation { duration: 120 } }
|
||||
Behavior on border.color { ColorAnimation { duration: 120 } }
|
||||
|
||||
TextInput {
|
||||
id: rotateInput
|
||||
anchors { fill: parent; leftMargin: 6; rightMargin: 6; topMargin: 4; bottomMargin: 4 }
|
||||
text: root.localRotateSec.toString()
|
||||
color: t.cryptoPopupText
|
||||
font.pixelSize: 12
|
||||
font.bold: true
|
||||
font.family: "monospace"
|
||||
selectionColor: t.accent
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
validator: IntValidator { bottom: 1; top: 600 }
|
||||
clip: true
|
||||
|
||||
onActiveFocusChanged: if (!activeFocus) {
|
||||
const v = parseInt(text)
|
||||
if (!isNaN(v) && v >= 1 && v <= 600)
|
||||
root.localRotateSec = v
|
||||
else
|
||||
text = root.localRotateSec.toString()
|
||||
}
|
||||
onAccepted: {
|
||||
const v = parseInt(text)
|
||||
if (!isNaN(v) && v >= 1 && v <= 600)
|
||||
root.localRotateSec = v
|
||||
else
|
||||
text = root.localRotateSec.toString()
|
||||
focus = false
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onLocalRotateSecChanged() {
|
||||
if (!rotateInput.activeFocus)
|
||||
rotateInput.text = root.localRotateSec.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 24; height: 24; radius: 7
|
||||
color: rotIncMouse.containsMouse
|
||||
? (rotIncMouse.pressed ? (t.accent) : (t.cryptoFieldBgFocus))
|
||||
: (t.cryptoFieldBg)
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
Text { anchors.centerIn: parent; text: "+"; font.pixelSize: 14; font.bold: true; color: t.cryptoPopupText }
|
||||
MouseArea {
|
||||
id: rotIncMouse
|
||||
anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
|
||||
onClicked: if (root.localRotateSec < 600) root.localRotateSec += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
label: "Show icon on bar"
|
||||
checked: root.localShowIcon
|
||||
@@ -1047,9 +1415,7 @@ ModuleChip {
|
||||
id: applyMouse
|
||||
anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.price = -1
|
||||
root.activeCoinIndex = 0
|
||||
root.coinSymbol = root.localCoinId.toUpperCase().slice(0, 4)
|
||||
root.persistSettings()
|
||||
root.fetchPrice()
|
||||
cryptoPopup.activeTab = 0
|
||||
@@ -1057,30 +1423,6 @@ ModuleChip {
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: "Reset to defaults"
|
||||
color: t.cryptoPopupDim
|
||||
font.pixelSize: 11
|
||||
font.underline: true
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent; cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.localPairs = Array.isArray(cfg.cryptoPairs) && cfg.cryptoPairs.length > 0 ? cfg.cryptoPairs : [{ coin: "bitcoin", currency: "usd" }]
|
||||
root.localVsCur = "usd"
|
||||
root.displayCurrency = root.localPairs.length > 0 ? root.localPairs[0].currency : "usd"
|
||||
root.activeCoinIndex = 0
|
||||
root.localDecimals = cfg.cryptoDecimals
|
||||
root.localRefreshSec = cfg.cryptoRefreshSec
|
||||
root.localShowIcon = cfg.cryptoShowIcon
|
||||
root.persistSettings()
|
||||
root.price = -1
|
||||
cryptoPopup.activeTab = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { height: 2 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ ModuleChip {
|
||||
|
||||
Layout.fillWidth: true
|
||||
height: 50
|
||||
radius: 10
|
||||
radius: 8
|
||||
color: {
|
||||
var active = modelData.connected || false
|
||||
if (active) return root.t.netApActiveBg
|
||||
@@ -407,7 +407,7 @@ ModuleChip {
|
||||
visible: root.wifiUp
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 4
|
||||
height: 36; radius: 10
|
||||
height: 36; radius: 8
|
||||
color: discoHover.hovered
|
||||
? root.t.netDisconnectBg : "transparent"
|
||||
border.color: root.t.netDisconnectBorder
|
||||
|
||||
@@ -186,7 +186,7 @@ ModuleChip {
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
width: 20; height: 20; radius: 10
|
||||
width: 20; height: 20; radius: 8
|
||||
color: closeHover.hovered ? toastRoot.t.notifHistoryHover : "transparent"
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
|
||||
@@ -298,9 +298,9 @@ ModuleChip {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors { right: parent.right; top: parent.top; margins: 6 }
|
||||
width: 18; height: 18; radius: 9
|
||||
color: histCloseHover.hovered ? histRoot.t.notifHistoryHover : "transparent"
|
||||
anchors { right: parent.right; top: parent.top; margins: 6 }
|
||||
width: 18; height: 18; radius: 8
|
||||
color: histCloseHover.hovered ? histRoot.t.notifHistoryHover : "transparent"
|
||||
opacity: histItemHover.hovered ? 1 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: 150 } }
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
@@ -611,7 +611,7 @@ ModuleChip {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
width: dndRow.implicitWidth + 16
|
||||
height: 24
|
||||
radius: 12
|
||||
radius: 8
|
||||
color: root.doNotDisturb
|
||||
? Qt.rgba(t.notifDnd.r, t.notifDnd.g, t.notifDnd.b, 0.18)
|
||||
: (dndHover.hovered ? t.notifHistoryHover : "transparent")
|
||||
@@ -653,7 +653,7 @@ ModuleChip {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
width: clearText.implicitWidth + 16
|
||||
height: 24
|
||||
radius: 12
|
||||
radius: 8
|
||||
color: clearHover.hovered ? t.notifHistoryHover : "transparent"
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ ModuleChip {
|
||||
property real cpuVal: 0
|
||||
property real memVal: 0
|
||||
property real diskVal: 0
|
||||
property real cpuTemp: -1
|
||||
property real gpuTemp: -1
|
||||
|
||||
Timer {
|
||||
interval: 2000
|
||||
@@ -39,7 +41,20 @@ ModuleChip {
|
||||
|
||||
Process {
|
||||
id: statsProc
|
||||
command: ["sh", "-c", "top -bn1 | awk '/^%Cpu/ {print 100-$8}'; free | awk '/Mem:/ {print ($2-$7)/$2 * 100}'; df / --output=pcent | tail -1 | tr -dc '0-9'"]
|
||||
command: ["sh", "-c",
|
||||
"top -bn1 | awk '/^%Cpu/ {print 100-$8}'; " +
|
||||
"free | awk '/Mem:/ {print ($2-$7)/$2 * 100}'; " +
|
||||
"df / --output=pcent | tail -1 | awk '{print $1}'; " +
|
||||
"ct=-1; for d in /sys/class/hwmon/hwmon*; do " +
|
||||
"n=$(cat $d/name 2>/dev/null); " +
|
||||
"if echo \"$n\" | grep -qiE 'coretemp|k10temp|cpu_thermal'; then " +
|
||||
"ct=$(( $(cat $d/temp1_input 2>/dev/null) / 1000 )); break; " +
|
||||
"fi; done; echo $ct; " +
|
||||
"gt=-1; for d in /sys/class/hwmon/hwmon*; do " +
|
||||
"n=$(cat $d/name 2>/dev/null); " +
|
||||
"if echo \"$n\" | grep -qiE 'amdgpu|radeon'; then " +
|
||||
"gt=$(( $(cat $d/temp1_input 2>/dev/null) / 1000 )); break; " +
|
||||
"fi; done; echo $gt"]
|
||||
property string rawOutput: ""
|
||||
stdout: SplitParser {
|
||||
onRead: data => { statsProc.rawOutput += data + "\n" }
|
||||
@@ -47,10 +62,12 @@ ModuleChip {
|
||||
onRunningChanged: {
|
||||
if (!statsProc.running && statsProc.rawOutput) {
|
||||
const lines = statsProc.rawOutput.trim().split("\n")
|
||||
if (lines.length >= 3) {
|
||||
if (lines.length >= 5) {
|
||||
let v = parseFloat(lines[0]); root.cpuVal = isNaN(v) ? 0 : v
|
||||
v = parseFloat(lines[1]); root.memVal = isNaN(v) ? 0 : v
|
||||
v = parseFloat(lines[2]); root.diskVal = isNaN(v) ? 0 : v
|
||||
v = parseFloat(lines[3]); root.cpuTemp = isNaN(v) ? -1 : v
|
||||
v = parseFloat(lines[4]); root.gpuTemp = isNaN(v) ? -1 : v
|
||||
}
|
||||
statsProc.rawOutput = ""
|
||||
}
|
||||
@@ -63,9 +80,11 @@ ModuleChip {
|
||||
|
||||
property real value: 0.0
|
||||
property string icon: ""
|
||||
property string label: ""
|
||||
property real iconXOff: 0
|
||||
property color ringColor: Cfg.Config.theme.accent
|
||||
property color trackColor: Cfg.Config.theme.statsTrackColor
|
||||
property string labelSuffix:"%"
|
||||
|
||||
Behavior on value {
|
||||
NumberAnimation { duration: 800; easing.type: Easing.OutCubic }
|
||||
@@ -75,7 +94,7 @@ ModuleChip {
|
||||
|
||||
Text {
|
||||
id: leftPct
|
||||
text: Math.round(ring.value) + "%"
|
||||
text: Math.round(ring.value) + ring.labelSuffix
|
||||
color: ring.ringColor
|
||||
font.pixelSize: Cfg.Config.statsRingFontSize
|
||||
font.bold: true
|
||||
@@ -148,7 +167,7 @@ ModuleChip {
|
||||
|
||||
Text {
|
||||
id: rightPct
|
||||
text: Math.round(ring.value) + "%"
|
||||
text: Math.round(ring.value) + ring.labelSuffix
|
||||
color: ring.ringColor
|
||||
font.pixelSize: Cfg.Config.statsRingFontSize
|
||||
font.bold: true
|
||||
@@ -171,25 +190,51 @@ ModuleChip {
|
||||
spacing: 8
|
||||
|
||||
StatRing {
|
||||
visible: Cfg.Config.statsShowCpu
|
||||
value: root.cpuVal
|
||||
icon: ""
|
||||
label: "CPU"
|
||||
iconXOff: 0.3
|
||||
ringColor: root.t.statsCpuColor
|
||||
trackColor: root.t.statsTrackColor
|
||||
}
|
||||
|
||||
StatRing {
|
||||
visible: Cfg.Config.statsShowMem
|
||||
value: root.memVal
|
||||
icon: ""
|
||||
label: "RAM"
|
||||
ringColor: root.t.statsMemColor
|
||||
trackColor: root.t.statsTrackColor
|
||||
}
|
||||
|
||||
StatRing {
|
||||
visible: Cfg.Config.statsShowDisk
|
||||
value: root.diskVal
|
||||
icon: ""
|
||||
label: "DISK"
|
||||
ringColor: root.t.statsDiskColor
|
||||
trackColor: root.t.statsTrackColor
|
||||
}
|
||||
|
||||
StatRing {
|
||||
visible: Cfg.Config.statsShowCpuTemp && root.cpuTemp >= 0
|
||||
value: root.cpuTemp
|
||||
icon: ""
|
||||
label: "CPU°C"
|
||||
ringColor: root.t.statsCpuTempColor
|
||||
trackColor: root.t.statsTrackColor
|
||||
labelSuffix: "°C"
|
||||
}
|
||||
|
||||
StatRing {
|
||||
visible: Cfg.Config.statsShowGpuTemp && root.gpuTemp >= 0
|
||||
value: root.gpuTemp
|
||||
icon: ""
|
||||
label: "GPU°C"
|
||||
ringColor: root.t.statsGpuColor
|
||||
trackColor: root.t.statsTrackColor
|
||||
labelSuffix: "°C"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ ModuleChip {
|
||||
visible: !isSep
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
radius: 10
|
||||
radius: 8
|
||||
readonly property bool active: menuMouse.containsMouse
|
||||
|| (submenuCard.visible
|
||||
&& trayMenuPopup._submenuParentRef === menuItem)
|
||||
|
||||
@@ -165,7 +165,7 @@ ModuleChip {
|
||||
Rectangle {
|
||||
id: volCard
|
||||
width: parent.width
|
||||
height: popupCol.implicitHeight + 32
|
||||
height: popupCol.implicitHeight + 30
|
||||
|
||||
anchors.top: root.isTop ? parent.top : undefined
|
||||
anchors.bottom: root.isTop ? undefined : parent.bottom
|
||||
@@ -196,8 +196,8 @@ ModuleChip {
|
||||
id: popupCol
|
||||
anchors { left: parent.left; right: parent.right; top: parent.top }
|
||||
anchors.margins: 16
|
||||
anchors.topMargin: 18
|
||||
spacing: 14
|
||||
anchors.topMargin: 16
|
||||
spacing: 16
|
||||
|
||||
VolumeSection {
|
||||
id: outputSection
|
||||
@@ -251,7 +251,7 @@ ModuleChip {
|
||||
Text {
|
||||
text: "Volume Limits"
|
||||
color: root.t.textDim
|
||||
font.pixelSize: 10
|
||||
font.pixelSize: 11
|
||||
font.bold: true
|
||||
font.capitalization: Font.AllUppercase
|
||||
Layout.fillWidth: true
|
||||
@@ -292,14 +292,14 @@ ModuleChip {
|
||||
onIncrement: root.inputMaxVolume = Math.min(300, root.inputMaxVolume + 10)
|
||||
}
|
||||
}
|
||||
Item { height: 2 }
|
||||
Item { height: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component VolumeSection: ColumnLayout {
|
||||
id: vs
|
||||
spacing: 8
|
||||
spacing: 11
|
||||
required property string label
|
||||
required property string icon
|
||||
required property color iconColor
|
||||
@@ -336,7 +336,7 @@ ModuleChip {
|
||||
Text {
|
||||
text: vs.label
|
||||
color: vs.textColor
|
||||
font.pixelSize: 13
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
renderType: Text.NativeRendering
|
||||
@@ -344,7 +344,7 @@ ModuleChip {
|
||||
Text {
|
||||
text: vs.muted ? "muted" : vs.volume + "%"
|
||||
color: vs.muted ? vs.dimColor : vs.textColor
|
||||
font.pixelSize: 12
|
||||
font.pixelSize: 13
|
||||
font.bold: !vs.muted
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.preferredWidth: 44
|
||||
@@ -352,7 +352,7 @@ ModuleChip {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 22; height: 22; radius: 6
|
||||
width: 24; height: 24; radius: 8
|
||||
color: muteHover.hovered ? (vs.muted ? "#33ffffff" : "#22ffffff") : (vs.muted ? root.t.volMuteBg : "transparent")
|
||||
border.color: vs.muted ? vs.accentColor : vs.dimColor
|
||||
border.width: Cfg.Config.popupBorderWidth
|
||||
@@ -372,15 +372,16 @@ ModuleChip {
|
||||
Item {
|
||||
id: sliderItem
|
||||
Layout.fillWidth: true
|
||||
height: 22
|
||||
height: 28
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width; height: Cfg.Config.volumeSliderHeight; radius: 2
|
||||
width: parent.width; height: 6; radius: 3
|
||||
color: vs.trackColor
|
||||
Rectangle {
|
||||
width: Math.min(parent.width, parent.width * (vs.volume / Math.max(1, vs.maxVolume)))
|
||||
height: parent.height; radius: parent.radius
|
||||
color: vs.muted ? vs.dimColor : vs.accentColor
|
||||
Behavior on width { NumberAnimation { duration: 60; easing.type: Easing.OutCubic } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +428,7 @@ ModuleChip {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 20; height: 20; radius: 5
|
||||
width: 24; height: 24; radius: 8
|
||||
color: chevHover.hovered ? "#22ffffff" : "transparent"
|
||||
border.color: vs.pickerOpen ? vs.accentColor : root.t.volPopupBorder
|
||||
border.width: Cfg.Config.popupBorderWidth
|
||||
@@ -502,7 +503,7 @@ ModuleChip {
|
||||
|
||||
component LimitRow: RowLayout {
|
||||
id: lr
|
||||
spacing: 8
|
||||
spacing: 10
|
||||
required property string icon
|
||||
required property color iconColor
|
||||
required property string labelText
|
||||
@@ -519,7 +520,7 @@ ModuleChip {
|
||||
signal decrement()
|
||||
signal increment()
|
||||
|
||||
Text { text: lr.icon; color: lr.iconColor; font.pixelSize: 13; font.bold: true; renderType: Text.NativeRendering }
|
||||
Text { text: lr.icon; color: lr.iconColor; font.pixelSize: 16; font.bold: true; renderType: Text.NativeRendering }
|
||||
Text {
|
||||
text: lr.labelText
|
||||
color: lr.textColor
|
||||
@@ -531,7 +532,7 @@ ModuleChip {
|
||||
|
||||
Rectangle {
|
||||
id: decBtn
|
||||
width: 22; height: 22; radius: 5
|
||||
width: 24; height: 24; radius: 8
|
||||
color: decMa.containsMouse
|
||||
? (decMa.pressed ? lr.btnPress : lr.btnHover)
|
||||
: lr.btnIdle
|
||||
@@ -568,7 +569,7 @@ ModuleChip {
|
||||
|
||||
Rectangle {
|
||||
id: incBtn
|
||||
width: 22; height: 22; radius: 5
|
||||
width: 24; height: 24; radius: 8
|
||||
color: incMa.containsMouse
|
||||
? (incMa.pressed ? lr.btnPress : lr.btnHover)
|
||||
: lr.btnIdle
|
||||
|
||||
Reference in New Issue
Block a user