add noctalia and fuzzel
This commit is contained in:
@@ -0,0 +1,868 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Keyboard
|
||||
import qs.Services.Media
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
// Unified OSD component that displays volume, input volume, and brightness changes
|
||||
Variants {
|
||||
id: osd
|
||||
|
||||
// Do not change the order or it will break settings.
|
||||
enum Type {
|
||||
Volume,
|
||||
InputVolume,
|
||||
Brightness,
|
||||
LockKey
|
||||
}
|
||||
|
||||
model: Quickshell.screens.filter(screen => (Settings.data.osd.monitors.includes(screen.name) || Settings.data.osd.monitors.length === 0) && Settings.data.osd.enabled)
|
||||
|
||||
delegate: Loader {
|
||||
id: root
|
||||
|
||||
required property ShellScreen modelData
|
||||
|
||||
active: false
|
||||
|
||||
// OSD State
|
||||
property int currentOSDType: -1 // OSD.Type enum value, -1 means none
|
||||
property bool startupComplete: false
|
||||
property real currentBrightness: 0
|
||||
property bool suppressInputOSD: false
|
||||
|
||||
// Lock Key States
|
||||
property string lastLockKeyChanged: "" // "caps", "num", "scroll", or ""
|
||||
|
||||
// Current values (computed properties)
|
||||
readonly property real currentVolume: AudioService.volume
|
||||
readonly property bool isMuted: AudioService.muted
|
||||
readonly property real currentInputVolume: AudioService.inputVolume
|
||||
readonly property bool isInputMuted: AudioService.inputMuted
|
||||
readonly property real epsilon: 0.005
|
||||
|
||||
// LockKey OSD enabled state (reactive to settings)
|
||||
readonly property bool lockKeyOSDEnabled: {
|
||||
const enabledTypes = Settings.data.osd.enabledTypes || [];
|
||||
if (enabledTypes.length === 0)
|
||||
return false;
|
||||
return enabledTypes.includes(OSD.Type.LockKey);
|
||||
}
|
||||
|
||||
readonly property var validIcons: {
|
||||
const iconKeys = Object.keys(Icons.icons);
|
||||
const aliasKeys = Object.keys(Icons.aliases);
|
||||
return new Set([...iconKeys, ...aliasKeys]);
|
||||
}
|
||||
|
||||
function getIcon() {
|
||||
switch (currentOSDType) {
|
||||
case OSD.Type.Volume:
|
||||
if (isMuted)
|
||||
return "volume-mute";
|
||||
// Show volume-x icon when volume is effectively 0% (within rounding threshold)
|
||||
if (currentVolume < root.epsilon)
|
||||
return "volume-x";
|
||||
return currentVolume <= 0.5 ? "volume-low" : "volume-high";
|
||||
case OSD.Type.InputVolume:
|
||||
return isInputMuted ? "microphone-off" : "microphone";
|
||||
case OSD.Type.Brightness:
|
||||
// Show sun-off icon when brightness is effectively 0% (within rounding threshold)
|
||||
if (currentBrightness < root.epsilon)
|
||||
return "sun-off";
|
||||
return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high";
|
||||
case OSD.Type.LockKey:
|
||||
return "keyboard";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentValue() {
|
||||
switch (currentOSDType) {
|
||||
case OSD.Type.Volume:
|
||||
return isMuted ? 0 : currentVolume;
|
||||
case OSD.Type.InputVolume:
|
||||
return isInputMuted ? 0 : currentInputVolume;
|
||||
case OSD.Type.Brightness:
|
||||
return currentBrightness;
|
||||
case OSD.Type.LockKey:
|
||||
return 1.0; // Always show 100% when showing lock key status
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function getMaxValue() {
|
||||
if (currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) {
|
||||
return Settings.data.audio.volumeOverdrive ? 1.5 : 1.0;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
function getDisplayPercentage() {
|
||||
if (currentOSDType === OSD.Type.LockKey) {
|
||||
// For lock keys, return the pre-determined status text
|
||||
return lastLockKeyChanged;
|
||||
}
|
||||
|
||||
const value = getCurrentValue();
|
||||
const max = getMaxValue();
|
||||
if ((currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) && Settings.data.audio.volumeOverdrive) {
|
||||
const pct = Math.round(value * 100);
|
||||
return pct + "%";
|
||||
}
|
||||
const pct = Math.round(Math.min(max, value) * 100);
|
||||
return pct + "%";
|
||||
}
|
||||
|
||||
function getProgressColor() {
|
||||
const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted);
|
||||
if (isMutedState) {
|
||||
return Color.mError;
|
||||
}
|
||||
// When volumeOverdrive is enabled, show error color if volume is above 100%
|
||||
if ((currentOSDType === OSD.Type.Volume || currentOSDType === OSD.Type.InputVolume) && Settings.data.audio.volumeOverdrive) {
|
||||
const value = getCurrentValue();
|
||||
if (value > 1.0) {
|
||||
return Color.mError;
|
||||
}
|
||||
}
|
||||
// For lock keys, use a different color to indicate the lock state
|
||||
if (currentOSDType === OSD.Type.LockKey) {
|
||||
// Check the specific lock key that was changed
|
||||
if (lastLockKeyChanged.startsWith("CAPS")) {
|
||||
return LockKeysService.capsLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
} else if (lastLockKeyChanged.startsWith("NUM")) {
|
||||
return LockKeysService.numLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
} else if (lastLockKeyChanged.startsWith("SCROLL")) {
|
||||
return LockKeysService.scrollLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
}
|
||||
}
|
||||
return Color.mPrimary;
|
||||
}
|
||||
|
||||
function getIconColor() {
|
||||
const isMutedState = (currentOSDType === OSD.Type.Volume && isMuted) || (currentOSDType === OSD.Type.InputVolume && isInputMuted);
|
||||
if (isMutedState)
|
||||
return Color.mError;
|
||||
|
||||
if (currentOSDType === OSD.Type.LockKey) {
|
||||
// Check the specific lock key that was changed
|
||||
if (lastLockKeyChanged.startsWith("CAPS")) {
|
||||
return LockKeysService.capsLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
} else if (lastLockKeyChanged.startsWith("NUM")) {
|
||||
return LockKeysService.numLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
} else if (lastLockKeyChanged.startsWith("SCROLL")) {
|
||||
return LockKeysService.scrollLockOn ? Color.mPrimary : Color.mOnSurfaceVariant;
|
||||
}
|
||||
}
|
||||
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
|
||||
// Brightness Handling
|
||||
function connectBrightnessMonitors() {
|
||||
for (var i = 0; i < BrightnessService.monitors.length; i++) {
|
||||
const monitor = BrightnessService.monitors[i];
|
||||
monitor.brightnessUpdated.disconnect(onBrightnessChanged);
|
||||
monitor.brightnessUpdated.connect(onBrightnessChanged);
|
||||
}
|
||||
}
|
||||
|
||||
function onBrightnessChanged(newBrightness) {
|
||||
if (!root)
|
||||
return;
|
||||
|
||||
root.currentBrightness = newBrightness;
|
||||
// Don't show OSD if brightness panel is open
|
||||
var brightnessPanel = PanelService.getPanel("brightnessPanel", root.modelData);
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", root.modelData);
|
||||
|
||||
if (brightnessPanel && brightnessPanel.isPanelOpen)
|
||||
return;
|
||||
if (controlCenterPanel && controlCenterPanel.isPanelOpen) {
|
||||
var cards = Settings.data.controlCenter.cards || [];
|
||||
if (cards.some(c => c.enabled && c.id === "brightness-card"))
|
||||
return;
|
||||
}
|
||||
showOSD(OSD.Type.Brightness);
|
||||
}
|
||||
|
||||
// Check if a specific OSD type is enabled
|
||||
function isTypeEnabled(type) {
|
||||
const enabledTypes = Settings.data.osd.enabledTypes || [];
|
||||
// If enabledTypes is empty, no types are enabled (no OSD will be shown)
|
||||
if (enabledTypes.length === 0)
|
||||
return false;
|
||||
return enabledTypes.includes(type);
|
||||
}
|
||||
|
||||
// OSD Display Control
|
||||
function showOSD(type) {
|
||||
// Ignore all OSD requests during startup period
|
||||
if (!startupComplete)
|
||||
return;
|
||||
|
||||
// Check if this OSD type is enabled
|
||||
if (!isTypeEnabled(type))
|
||||
return;
|
||||
|
||||
// Suppress Audio OSD if Audio Panel or Control Center (with audio card) is open
|
||||
if (type === OSD.Type.Volume || type === OSD.Type.InputVolume) {
|
||||
var audioPanel = PanelService.getPanel("audioPanel", root.modelData);
|
||||
if (audioPanel && audioPanel.isPanelOpen)
|
||||
return;
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", root.modelData);
|
||||
if (controlCenterPanel && controlCenterPanel.isPanelOpen) {
|
||||
var cards = Settings.data.controlCenter.cards || [];
|
||||
if (cards.some(c => c.enabled && c.id === "audio-card"))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
currentOSDType = type;
|
||||
|
||||
if (!root.active) {
|
||||
root.active = true;
|
||||
}
|
||||
|
||||
if (root.item) {
|
||||
root.item.showOSD();
|
||||
} else {
|
||||
Qt.callLater(() => {
|
||||
if (root.item)
|
||||
root.item.showOSD();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function hideOSD() {
|
||||
if (root.item?.osdItem) {
|
||||
root.item.osdItem.hideImmediately();
|
||||
} else if (root.active) {
|
||||
root.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
// AudioService monitoring
|
||||
Connections {
|
||||
target: AudioService
|
||||
|
||||
function onVolumeChanged() {
|
||||
showOSD(OSD.Type.Volume);
|
||||
}
|
||||
|
||||
function onVolumeAtMaximum() {
|
||||
showOSD(OSD.Type.Volume);
|
||||
}
|
||||
|
||||
function onVolumeAtMinimum() {
|
||||
showOSD(OSD.Type.Volume);
|
||||
}
|
||||
|
||||
function onMutedChanged() {
|
||||
if (AudioService.consumeOutputOSDSuppression())
|
||||
return;
|
||||
showOSD(OSD.Type.Volume);
|
||||
}
|
||||
|
||||
function onInputVolumeChanged() {
|
||||
if (suppressInputOSD)
|
||||
return;
|
||||
if (AudioService.hasInput)
|
||||
showOSD(OSD.Type.InputVolume);
|
||||
}
|
||||
|
||||
function onInputMutedChanged() {
|
||||
if (suppressInputOSD)
|
||||
return;
|
||||
if (!AudioService.hasInput)
|
||||
return;
|
||||
if (AudioService.consumeInputOSDSuppression())
|
||||
return;
|
||||
showOSD(OSD.Type.InputVolume);
|
||||
}
|
||||
|
||||
// Refresh OSD when device changes to ensure correct volume is displayed
|
||||
function onSinkChanged() {
|
||||
suppressInputOSD = true;
|
||||
inputSuppressionTimer.restart();
|
||||
// If volume OSD is currently showing, refresh it to show new device's volume
|
||||
if (root.currentOSDType === OSD.Type.Volume) {
|
||||
Qt.callLater(() => {
|
||||
showOSD(OSD.Type.Volume);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onSourceChanged() {
|
||||
// If input volume OSD is currently showing, refresh it to show new device's volume
|
||||
if (root.currentOSDType === OSD.Type.InputVolume) {
|
||||
Qt.callLater(() => {
|
||||
showOSD(OSD.Type.InputVolume);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Brightness monitoring
|
||||
Connections {
|
||||
target: BrightnessService
|
||||
function onMonitorsChanged() {
|
||||
connectBrightnessMonitors();
|
||||
}
|
||||
}
|
||||
|
||||
// Register/unregister LockKeysService polling based on whether LockKey OSD is enabled
|
||||
onLockKeyOSDEnabledChanged: {
|
||||
if (lockKeyOSDEnabled) {
|
||||
LockKeysService.registerComponent("osd:" + (modelData?.name || "unknown"));
|
||||
} else {
|
||||
LockKeysService.unregisterComponent("osd:" + (modelData?.name || "unknown"));
|
||||
}
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (lockKeyOSDEnabled) {
|
||||
LockKeysService.registerComponent("osd:" + (modelData?.name || "unknown"));
|
||||
}
|
||||
}
|
||||
|
||||
// LockKeys monitoring with a cleaner approach
|
||||
// Only connect when LockKey OSD is enabled to avoid starting the service unnecessarily
|
||||
Connections {
|
||||
target: root.lockKeyOSDEnabled ? LockKeysService : null
|
||||
|
||||
function onCapsLockChanged(active) {
|
||||
root.lastLockKeyChanged = active ? "CAPS ON" : "CAPS OFF";
|
||||
root.showOSD(OSD.Type.LockKey);
|
||||
}
|
||||
|
||||
function onNumLockChanged(active) {
|
||||
root.lastLockKeyChanged = active ? "NUM ON" : "NUM OFF";
|
||||
root.showOSD(OSD.Type.LockKey);
|
||||
}
|
||||
|
||||
function onScrollLockChanged(active) {
|
||||
root.lastLockKeyChanged = active ? "SCROLL ON" : "SCROLL OFF";
|
||||
root.showOSD(OSD.Type.LockKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Startup timer - connect brightness monitors and enable OSD after 2 seconds
|
||||
Timer {
|
||||
id: startupTimer
|
||||
interval: 2000
|
||||
running: true
|
||||
onTriggered: {
|
||||
connectBrightnessMonitors();
|
||||
root.startupComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Timer to reset the input volume OSD suppression
|
||||
Timer {
|
||||
id: inputSuppressionTimer
|
||||
interval: 300
|
||||
repeat: false
|
||||
onTriggered: root.suppressInputOSD = false
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
LockKeysService.unregisterComponent("osd:" + (modelData?.name || "unknown"));
|
||||
if (typeof BrightnessService !== "undefined" && BrightnessService.monitors) {
|
||||
for (var i = 0; i < BrightnessService.monitors.length; i++) {
|
||||
try {
|
||||
BrightnessService.monitors[i].brightnessUpdated.disconnect(onBrightnessChanged);
|
||||
} catch (e) {
|
||||
// Ignore errors if already disconnected or not connected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Visual Component
|
||||
sourceComponent: PanelWindow {
|
||||
id: panel
|
||||
screen: modelData
|
||||
|
||||
// Position configuration
|
||||
readonly property string location: Settings.data.osd?.location || "top_right"
|
||||
readonly property bool isTop: location === "top" || location.startsWith("top")
|
||||
readonly property bool isBottom: location === "bottom" || location.startsWith("bottom")
|
||||
readonly property bool isLeft: location.includes("_left") || location === "left"
|
||||
readonly property bool isRight: location.includes("_right") || location === "right"
|
||||
readonly property bool verticalMode: location === "left" || location === "right"
|
||||
|
||||
// Hidden text element for measuring lock key text width
|
||||
NText {
|
||||
id: lockKeyTextMetrics
|
||||
visible: false
|
||||
text: root.getDisplayPercentage()
|
||||
pointSize: Style.fontSizeS
|
||||
family: Settings.data.ui.fontFixed
|
||||
elide: Text.ElideNone
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
// Dimensions
|
||||
readonly property bool isShortMode: root.currentOSDType === OSD.Type.LockKey
|
||||
readonly property int longHWidth: Math.round(320 * Style.uiScaleRatio)
|
||||
readonly property int longHHeight: Math.round(72 * Style.uiScaleRatio)
|
||||
readonly property int shortHWidth: Math.round(180 * Style.uiScaleRatio)
|
||||
readonly property int longVWidth: Math.round(80 * Style.uiScaleRatio)
|
||||
readonly property int longVHeight: Math.round(280 * Style.uiScaleRatio)
|
||||
readonly property int shortVHeight: Math.round(180 * Style.uiScaleRatio)
|
||||
|
||||
// Dynamic width for horizontal lock keys based on text length
|
||||
// Explicitly bind to contentWidth to ensure reactivity
|
||||
readonly property int lockKeyHWidth: {
|
||||
if (root.currentOSDType !== OSD.Type.LockKey || verticalMode) {
|
||||
return shortHWidth;
|
||||
}
|
||||
const text = root.getDisplayPercentage();
|
||||
if (!text) {
|
||||
return shortHWidth;
|
||||
}
|
||||
// Access contentWidth to create binding dependency
|
||||
const textWidth = Math.ceil(lockKeyTextMetrics.contentWidth || 0);
|
||||
if (textWidth === 0) {
|
||||
// Fallback: estimate based on text length if measurement not ready
|
||||
const fontSize = Style.fontSizeS * Settings.data.ui.fontFixedScale * Style.uiScaleRatio;
|
||||
const estimatedWidth = text.length * fontSize * 0.6;
|
||||
const iconWidth = Style.fontSizeXL * Style.uiScaleRatio;
|
||||
const margins = Style.margin2L;
|
||||
const spacing = Style.marginM;
|
||||
const bgMargins = Style.margin2M * 1.5;
|
||||
return Math.max(shortHWidth, Math.round((estimatedWidth + iconWidth + margins + spacing + bgMargins) * 1.1));
|
||||
}
|
||||
const iconWidth = Style.fontSizeXL * Style.uiScaleRatio;
|
||||
const margins = Style.margin2L; // Left and right content margins
|
||||
const spacing = Style.marginM; // Spacing between icon and text
|
||||
const bgMargins = Style.margin2M * 1.5; // Background margins
|
||||
const totalWidth = textWidth + iconWidth + margins + spacing + bgMargins;
|
||||
// Ensure minimum width and add some buffer
|
||||
return Math.max(shortHWidth, Math.round(totalWidth * 1.1));
|
||||
}
|
||||
|
||||
// Dynamic height for vertical lock keys based on text length
|
||||
readonly property int lockKeyVHeight: {
|
||||
if (root.currentOSDType !== OSD.Type.LockKey || !verticalMode) {
|
||||
return shortVHeight;
|
||||
}
|
||||
const text = root.getDisplayPercentage();
|
||||
const charCount = text ? text.length : 0;
|
||||
if (charCount === 0) {
|
||||
return shortVHeight;
|
||||
}
|
||||
// Calculate height: font size * char count + margins + icon space
|
||||
// Font size M (11pt) scaled, plus some spacing between chars
|
||||
const fontSize = Style.fontSizeS * Settings.data.ui.fontFixedScale * Style.uiScaleRatio;
|
||||
const charHeight = fontSize * 1.3; // Add 30% for line height (matches Layout.preferredHeight)
|
||||
const textHeight = charCount * charHeight;
|
||||
// Background margins (Style.marginM * 1.5 * 2 for top and bottom)
|
||||
const bgMargins = Style.marginM * 1.5 * 2;
|
||||
// Content margins (Style.margin2L for top and bottom)
|
||||
const contentMargins = Style.margin2L;
|
||||
// Icon size: fontSizeXL scaled, with extra space for icon rendering and padding
|
||||
const iconSize = Style.fontSizeXL * Style.uiScaleRatio * 1.8; // Add 80% for icon rendering and padding
|
||||
// Spacing between text and icon (Style.marginM for lock keys)
|
||||
const textIconSpacing = Style.marginM;
|
||||
// Add extra buffer to ensure everything fits comfortably
|
||||
const buffer = Style.marginL;
|
||||
const totalHeight = textHeight + bgMargins + contentMargins + iconSize + textIconSpacing + buffer;
|
||||
// Ensure minimum height and add extra padding for safety
|
||||
return Math.max(shortVHeight, Math.round(totalHeight * 1.1));
|
||||
}
|
||||
|
||||
readonly property int barThickness: {
|
||||
const base = Math.max(8, Math.round(8 * Style.uiScaleRatio));
|
||||
return base % 2 === 0 ? base : base + 1;
|
||||
}
|
||||
|
||||
anchors.top: isTop
|
||||
anchors.bottom: isBottom
|
||||
anchors.left: isLeft
|
||||
anchors.right: isRight
|
||||
|
||||
readonly property string screenBarPosition: Settings.getBarPositionForScreen(root.modelData?.name)
|
||||
readonly property real barHeight: Style.getBarHeightForScreen(root.modelData?.name)
|
||||
readonly property bool isFramed: Settings.data.bar.barType === "framed"
|
||||
readonly property real frameThickness: Settings.data.bar.frameThickness ?? 8
|
||||
|
||||
function calculateMargin(isAnchored, position) {
|
||||
if (!isAnchored)
|
||||
return 0;
|
||||
|
||||
let base = Style.marginM;
|
||||
if (screenBarPosition === position) {
|
||||
const isVertical = position === "top" || position === "bottom";
|
||||
const floatExtra = Math.ceil(Settings.data.bar.barType === "floating" ? (isVertical ? Settings.data.bar.marginVertical : Settings.data.bar.marginHorizontal) : 0);
|
||||
return barHeight + base + floatExtra;
|
||||
}
|
||||
|
||||
if (isFramed) {
|
||||
return base + frameThickness;
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
margins.top: calculateMargin(anchors.top, "top")
|
||||
margins.bottom: calculateMargin(anchors.bottom, "bottom")
|
||||
margins.left: calculateMargin(anchors.left, "left")
|
||||
margins.right: calculateMargin(anchors.right, "right")
|
||||
|
||||
implicitWidth: verticalMode ? longVWidth : (isShortMode ? lockKeyHWidth : longHWidth)
|
||||
implicitHeight: verticalMode ? (isShortMode ? lockKeyVHeight : longVHeight) : longHHeight
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.namespace: "noctalia-osd-" + (screen?.name || "unknown")
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.layer: Settings.data.osd?.overlayLayer ? WlrLayer.Overlay : WlrLayer.Top
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
// Click-through — OSD is display-only, no input needed
|
||||
mask: Region {}
|
||||
|
||||
Item {
|
||||
id: osdItem
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
opacity: 0
|
||||
scale: 0.85
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: Settings.data.osd.autoHideMs
|
||||
onTriggered: osdItem.hide()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: visibilityTimer
|
||||
interval: Style.animationNormal + 50
|
||||
onTriggered: {
|
||||
osdItem.visible = false;
|
||||
root.currentOSDType = -1;
|
||||
root.lastLockKeyChanged = "";
|
||||
root.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM * 1.5
|
||||
radius: Style.radiusL
|
||||
color: Qt.alpha(Color.mSurface, Color.adaptiveOpacity(Settings.data.osd.backgroundOpacity) || 1.0)
|
||||
border.color: Qt.alpha(Color.mOutline, Color.adaptiveOpacity(Settings.data.osd.backgroundOpacity) || 1.0)
|
||||
border.width: {
|
||||
const bw = Math.max(2, Style.borderM);
|
||||
return bw % 2 === 0 ? bw : bw + 1;
|
||||
}
|
||||
}
|
||||
|
||||
NDropShadow {
|
||||
anchors.fill: background
|
||||
source: background
|
||||
autoPaddingEnabled: true
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: background
|
||||
anchors.margins: Style.marginM
|
||||
active: true
|
||||
sourceComponent: panel.verticalMode ? verticalContent : horizontalContent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: horizontalContent
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.marginL
|
||||
anchors.rightMargin: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
TextMetrics {
|
||||
id: percentageMetrics
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeS * (Settings.data.ui.fontFixedScale * Style.uiScaleRatio)
|
||||
text: "150%"
|
||||
}
|
||||
|
||||
// Common Icon for all types
|
||||
NIcon {
|
||||
icon: root.getIcon()
|
||||
color: root.getIconColor()
|
||||
pointSize: Style.fontSizeXL
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lock Key Status Text (replaces progress bar)
|
||||
NText {
|
||||
visible: root.currentOSDType === OSD.Type.LockKey
|
||||
text: root.getDisplayPercentage()
|
||||
color: root.getProgressColor()
|
||||
pointSize: Style.fontSizeS
|
||||
elide: Text.ElideNone
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
// Progress Bar for Volume/Brightness
|
||||
Rectangle {
|
||||
visible: root.currentOSDType !== OSD.Type.LockKey
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
height: panel.barThickness
|
||||
radius: Math.min(Style.iRadiusL, panel.barThickness / 2)
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width * Math.min(1.0, root.getCurrentValue() / root.getMaxValue())
|
||||
radius: parent.radius
|
||||
color: root.getProgressColor()
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Percentage Text for Volume/Brightness
|
||||
NText {
|
||||
visible: root.currentOSDType !== OSD.Type.LockKey
|
||||
text: root.getDisplayPercentage()
|
||||
color: Color.mOnSurface
|
||||
pointSize: Style.fontSizeS
|
||||
family: Settings.data.ui.fontFixed
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
horizontalAlignment: Text.AlignRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.fillWidth: false
|
||||
Layout.preferredWidth: Math.ceil(percentageMetrics.width) + Math.round(8 * Style.uiScaleRatio)
|
||||
Layout.maximumWidth: Math.ceil(percentageMetrics.width) + Math.round(8 * Style.uiScaleRatio)
|
||||
Layout.minimumWidth: Math.ceil(percentageMetrics.width)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: verticalContent
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Style.marginL
|
||||
anchors.bottomMargin: Style.marginL
|
||||
spacing: root.currentOSDType === OSD.Type.LockKey ? Style.marginM : Style.marginS
|
||||
clip: root.currentOSDType !== OSD.Type.LockKey
|
||||
|
||||
ColumnLayout {
|
||||
id: textVerticalLayout
|
||||
visible: root.currentOSDType === OSD.Type.LockKey
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 0
|
||||
|
||||
property var verticalTextChars: []
|
||||
|
||||
function updateVerticalTextChars() {
|
||||
const text = root.getDisplayPercentage();
|
||||
const chars = [];
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
chars.push(text[i]);
|
||||
}
|
||||
verticalTextChars = chars;
|
||||
}
|
||||
|
||||
Component.onCompleted: updateVerticalTextChars()
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onLastLockKeyChangedChanged() {
|
||||
if (root.currentOSDType === OSD.Type.LockKey) {
|
||||
textVerticalLayout.updateVerticalTextChars();
|
||||
}
|
||||
}
|
||||
function onCurrentOSDTypeChanged() {
|
||||
if (root.currentOSDType === OSD.Type.LockKey) {
|
||||
textVerticalLayout.updateVerticalTextChars();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: textVerticalLayout.verticalTextChars
|
||||
|
||||
NText {
|
||||
text: modelData || ""
|
||||
color: root.getProgressColor()
|
||||
pointSize: Style.fontSizeS
|
||||
family: Settings.data.ui.fontFixed
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: {
|
||||
const fontSize = Style.fontSizeS * Settings.data.ui.fontFixedScale * Style.uiScaleRatio;
|
||||
return Math.round(fontSize * 1.3);
|
||||
}
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.currentOSDType !== OSD.Type.LockKey
|
||||
text: root.getDisplayPercentage()
|
||||
color: Color.mOnSurface
|
||||
pointSize: Style.fontSizeS
|
||||
family: Settings.data.ui.fontFixed
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.preferredHeight: Math.round(20 * Style.uiScaleRatio)
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: root.currentOSDType !== OSD.Type.LockKey
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: root.currentOSDType !== OSD.Type.LockKey
|
||||
|
||||
Rectangle {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
width: panel.barThickness
|
||||
radius: Math.min(Style.iRadiusL, panel.barThickness / 2)
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: parent.height * Math.min(1.0, root.getCurrentValue() / root.getMaxValue())
|
||||
radius: parent.radius
|
||||
color: root.getProgressColor()
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: root.getIcon()
|
||||
color: root.getIconColor()
|
||||
pointSize: root.currentOSDType === OSD.Type.LockKey ? Style.fontSizeXL : Style.fontSizeL
|
||||
Layout.alignment: root.currentOSDType === OSD.Type.LockKey ? Qt.AlignHCenter : (Qt.AlignHCenter | Qt.AlignBottom)
|
||||
Layout.preferredHeight: root.currentOSDType === OSD.Type.LockKey ? (Style.fontSizeXL * Style.uiScaleRatio * 1.5) : -1
|
||||
Layout.minimumHeight: root.currentOSDType === OSD.Type.LockKey ? (Style.fontSizeXL * Style.uiScaleRatio) : 0
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delay showing the OSD to allow the layout to settle after activation.
|
||||
// Without this, the percentage text renders outside the box on first
|
||||
// show.
|
||||
Timer {
|
||||
id: showDelayTimer
|
||||
interval: 30
|
||||
onTriggered: {
|
||||
osdItem.visible = true;
|
||||
osdItem.opacity = 1;
|
||||
osdItem.scale = 1.0;
|
||||
hideTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
function show() {
|
||||
hideTimer.stop();
|
||||
visibilityTimer.stop();
|
||||
showDelayTimer.start();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
hideTimer.stop();
|
||||
visibilityTimer.stop();
|
||||
osdItem.opacity = 0;
|
||||
osdItem.scale = 0.85;
|
||||
visibilityTimer.start();
|
||||
}
|
||||
|
||||
function hideImmediately() {
|
||||
hideTimer.stop();
|
||||
visibilityTimer.stop();
|
||||
osdItem.opacity = 0;
|
||||
osdItem.scale = 0.85;
|
||||
osdItem.visible = false;
|
||||
root.currentOSDType = -1;
|
||||
root.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showOSD() {
|
||||
osdItem.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user