Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ set(CMAKE_AUTOMOC ON)
# ---------------------------------------------------------------------------
# Qt6 GUI / QML / i18n dependencies
# ---------------------------------------------------------------------------
find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick QuickDialogs2 Network)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick QuickDialogs2 Network Core5Compat ShaderTools)

# ---------------------------------------------------------------------------
# 添加主源码与头文件路径
Expand Down
189 changes: 189 additions & 0 deletions src/AlbumArtDisplay.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import QtQuick
import Qt5Compat.GraphicalEffects

// AlbumArtDisplay – holographic 3D album art with rotation and reflection.
Item {
id: root

property string source: ""
property bool isPlaying: false
property real parallaxX: 0 // -1..1, from mouse
property real parallaxY: 0 // -1..1, from mouse

// Emitted once the image loads so parent can extract dominant colors
signal imageReady()
// Expose dominant colors extracted from the cover
property color dominantColor1: "#1a0533"
property color dominantColor2: "#0a1628"
property color dominantColor3: "#0f2027"
property color dominantColor4: "#1a0a2e"

implicitWidth: 200
implicitHeight: 200

// 3D perspective wrapper
Item {
id: perspectiveContainer
anchors.centerIn: parent
width: root.width
height: root.height

transform: [
Rotation {
origin.x: perspectiveContainer.width / 2
origin.y: perspectiveContainer.height / 2
axis { x: 1; y: 0; z: 0 }
angle: root.parallaxY * 6 // tilt up to ±6°
Behavior on angle {
SpringAnimation { spring: 2; damping: 0.4 }
}
},
Rotation {
origin.x: perspectiveContainer.width / 2
origin.y: perspectiveContainer.height / 2
axis { x: 0; y: 1; z: 0 }
angle: -root.parallaxX * 6
Behavior on angle {
SpringAnimation { spring: 2; damping: 0.4 }
}
}
]

// Shadow beneath the album art
RectangularGlow {
anchors.centerIn: artClip
width: artClip.width + 4
height: artClip.height + 4
glowRadius: 20
spread: 0.1
color: Qt.rgba(root.dominantColor1.r,
root.dominantColor1.g,
root.dominantColor1.b, 0.4)
cornerRadius: artClip.radius + glowRadius
}

// Round-clipped album art
Rectangle {
id: artClip
anchors.centerIn: parent
width: parent.width * 0.85
height: parent.height * 0.85
radius: 16
color: Qt.rgba(1, 1, 1, 0.05)
clip: true

Image {
id: coverImage
anchors.fill: parent
source: root.source
fillMode: Image.PreserveAspectCrop
asynchronous: true
visible: root.source !== ""

onStatusChanged: {
if (status === Image.Ready) {
root._extractColors()
root.imageReady()
}
}
}

// Placeholder when no cover
Item {
anchors.fill: parent
visible: root.source === "" || coverImage.status !== Image.Ready

Rectangle {
anchors.fill: parent
color: Qt.rgba(1, 1, 1, 0.04)
}

Text {
anchors.centerIn: parent
text: "♪"
font.pixelSize: 48
color: Qt.rgba(1, 1, 1, 0.2)
}
}

// Glass reflection overlay
Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: Qt.rgba(1, 1, 1, 0.12) }
GradientStop { position: 0.35; color: Qt.rgba(1, 1, 1, 0.02) }
GradientStop { position: 0.65; color: "transparent" }
GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.1) }
}
}

// Edge highlight
Rectangle {
anchors.fill: parent
color: "transparent"
radius: parent.radius
border.width: 1
border.color: Qt.rgba(1, 1, 1, 0.15)
}
}

// Slow rotation when playing
RotationAnimation on rotation {
from: 0
to: 360
duration: 30000 // 30 seconds per revolution
loops: Animation.Infinite
running: root.isPlaying && root.source !== ""
paused: !root.isPlaying
}
}

// --- Color extraction via hidden Canvas ---
Canvas {
id: colorExtractor
width: 8
height: 8
visible: false

onPaint: {
var ctx = getContext("2d")
ctx.drawImage(root.source, 0, 0, 8, 8)
}

onPainted: {
var ctx = getContext("2d")
try {
var data = ctx.getImageData(0, 0, 8, 8).data
// Sample corners and center for dominant colors
var samples = [
_sampleAt(data, 8, 1, 1), // top-left
_sampleAt(data, 8, 6, 1), // top-right
_sampleAt(data, 8, 1, 6), // bottom-left
_sampleAt(data, 8, 4, 4) // center
]
root.dominantColor1 = _toColor(samples[0])
root.dominantColor2 = _toColor(samples[1])
root.dominantColor3 = _toColor(samples[2])
root.dominantColor4 = _toColor(samples[3])
} catch(e) {
// Keep defaults on error
}
}
}

function _extractColors() {
if (root.source !== "") {
colorExtractor.loadImage(root.source)
colorExtractor.requestPaint()
}
}

function _sampleAt(data, stride, x, y) {
var idx = (y * stride + x) * 4
return { r: data[idx] / 255, g: data[idx+1] / 255, b: data[idx+2] / 255 }
}

function _toColor(s) {
return Qt.rgba(s.r, s.g, s.b, 1.0)
}
}
10 changes: 9 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,15 @@ qt_add_executable(vibe_coding_player
qt_add_qml_module(vibe_coding_player
URI VibePlayer
VERSION 1.0
QML_FILES main.qml
QML_FILES
main.qml
GlassPanel.qml
MeshGradientBackground.qml
ParticleField.qml
GlowSlider.qml
GlassButton.qml
AlbumArtDisplay.qml
ImmersiveLyrics.qml
RESOURCE_PREFIX /
)

Expand Down
107 changes: 107 additions & 0 deletions src/GlassButton.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects

// GlassButton – a skeuomorphic glass/acrylic button with metallic sheen.
Button {
id: root

property color baseColor: Qt.rgba(1, 1, 1, 0.08)
property color hoverColor: Qt.rgba(1, 1, 1, 0.14)
property color pressColor: Qt.rgba(1, 1, 1, 0.06)
property color borderColor: Qt.rgba(1, 1, 1, 0.15)
property color textColor: "#e0e0e0"
property color accentGlow: "#5599ff"
property real cornerRadius: 12
property string iconText: "" // emoji / unicode icon
property bool isAccent: false // whether this is a primary/accent button

leftPadding: 16
rightPadding: 16
topPadding: 8
bottomPadding: 8

contentItem: Row {
spacing: root.iconText !== "" && root.text !== "" ? 6 : 0

Text {
visible: root.iconText !== ""
text: root.iconText
font.pixelSize: 16
anchors.verticalCenter: parent.verticalCenter
color: root.isAccent ? root.accentGlow : root.textColor
}

Text {
visible: root.text !== ""
text: root.text
font.pixelSize: 13
font.family: "sans-serif"
font.weight: Font.Medium
color: root.textColor
anchors.verticalCenter: parent.verticalCenter
}
}

background: Rectangle {
id: bg
implicitWidth: 60
implicitHeight: 36
radius: root.cornerRadius
color: root.pressed ? root.pressColor
: (root.hovered ? root.hoverColor : root.baseColor)

border.width: 1
border.color: root.hovered ? Qt.rgba(1, 1, 1, 0.3) : root.borderColor

Behavior on color {
ColorAnimation { duration: 150; easing.type: Easing.OutCubic }
}
Behavior on border.color {
ColorAnimation { duration: 200; easing.type: Easing.OutCubic }
}

// Top edge highlight (metallic sheen)
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 1
height: 1
radius: root.cornerRadius
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 0.3; color: Qt.rgba(1, 1, 1, root.hovered ? 0.25 : 0.1) }
GradientStop { position: 0.7; color: Qt.rgba(1, 1, 1, root.hovered ? 0.25 : 0.1) }
GradientStop { position: 1.0; color: "transparent" }
}
}

// Press shadow (inset feel)
Rectangle {
anchors.fill: parent
radius: root.cornerRadius
color: "transparent"
opacity: root.pressed ? 0.15 : 0
border.width: 1
border.color: Qt.rgba(0, 0, 0, 0.4)
Behavior on opacity { NumberAnimation { duration: 100 } }
}

// Accent glow for primary buttons
layer.enabled: root.isAccent && root.hovered
layer.effect: Glow {
radius: 8
samples: 17
color: root.accentGlow
spread: 0.1
}
}

// Jelly press scale
scale: root.pressed ? 0.95 : 1.0
Behavior on scale {
SpringAnimation { spring: 5; damping: 0.3; mass: 0.3 }
}
}
Loading