Initial commit of WhisperVoice
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Particles
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
|
||||
width: 460 * (ui ? ui.uiScale : 1.0)
|
||||
height: 180 * (ui ? ui.uiScale : 1.0)
|
||||
|
||||
visible: true
|
||||
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
|
||||
color: "transparent"
|
||||
|
||||
FontLoader {
|
||||
id: jetBrainsMono
|
||||
source: "fonts/ttf/JetBrainsMono-Bold.ttf"
|
||||
}
|
||||
|
||||
property real shimmerPos: -0.5
|
||||
property real windowOpacity: ui.getSetting("opacity")
|
||||
property real uiScale: Number(ui.getSetting("ui_scale")) // Bind Scale
|
||||
|
||||
Connections {
|
||||
target: ui
|
||||
function onSettingChanged(key, value) {
|
||||
if (key === "opacity") windowOpacity = value
|
||||
if (key === "ui_scale") uiScale = Number(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Visibility Logic
|
||||
property bool isActive: ui.isRecording || ui.isProcessing
|
||||
|
||||
SequentialAnimation {
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
PauseAnimation { duration: 3000 }
|
||||
NumberAnimation {
|
||||
target: root; property: "shimmerPos"
|
||||
from: -0.5; to: 1.5; duration: 1500
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
// Container
|
||||
Item {
|
||||
id: mainContainer
|
||||
width: 380
|
||||
height: 100
|
||||
anchors.centerIn: parent
|
||||
|
||||
// Scale & Opacity Transform
|
||||
scale: root.isActive ? root.uiScale : 0.8
|
||||
opacity: root.isActive ? root.windowOpacity : 0.0
|
||||
visible: opacity > 0.01 // Optimization
|
||||
|
||||
// Motion.dev-like Spring Animation
|
||||
Behavior on scale {
|
||||
SpringAnimation { spring: 3; damping: 0.25; epsilon: 0.005 }
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 300; easing.type: Easing.OutCubic }
|
||||
}
|
||||
|
||||
// --- SHADOW ---
|
||||
Item {
|
||||
anchors.fill: bgRect
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowColor: "#A0000000"
|
||||
shadowBlur: 0.4
|
||||
autoPaddingEnabled: true
|
||||
}
|
||||
}
|
||||
|
||||
// --- CHASSIS VISUALS (Hidden Source) ---
|
||||
Item {
|
||||
id: contentSource
|
||||
anchors.fill: bgRect
|
||||
visible: false
|
||||
|
||||
// A. Gradient Background
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#CC101015" }
|
||||
GradientStop { position: 1.0; color: "#CC050505" }
|
||||
}
|
||||
}
|
||||
|
||||
// B. Animated Gradient Blobs
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
opacity: 0.4
|
||||
property real time: 0
|
||||
fragmentShader: "gradient_blobs.qsb"
|
||||
NumberAnimation on time { from: 0; to: 1000; duration: 100000; loops: Animation.Infinite }
|
||||
}
|
||||
|
||||
// C. Glow Shader
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
opacity: 0.04
|
||||
property real time: 0
|
||||
property real intensity: ui.amplitude
|
||||
fragmentShader: "glow.qsb"
|
||||
NumberAnimation on time { from: 0; to: 100; duration: 10000; loops: Animation.Infinite }
|
||||
}
|
||||
|
||||
// D. Particles
|
||||
ParticleSystem {
|
||||
id: particles
|
||||
anchors.fill: parent
|
||||
ItemParticle {
|
||||
system: particles
|
||||
delegate: Rectangle { width: 2; height: 2; radius: 1; color: "#10ffffff" }
|
||||
}
|
||||
Emitter {
|
||||
anchors.fill: parent; emitRate: 15; lifeSpan: 4000; size: 4; sizeVariation: 2
|
||||
velocity: AngleDirection { angle: -90; angleVariation: 180; magnitude: 5 }
|
||||
acceleration: PointDirection { y: -2 }
|
||||
}
|
||||
}
|
||||
|
||||
// E. Shimmer
|
||||
Rectangle {
|
||||
width: parent.width * 0.4; height: parent.height * 2.5
|
||||
rotation: 25; anchors.centerIn: parent
|
||||
x: (root.shimmerPos * parent.width * 2) - width; y: -height/4
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.0; color: "transparent" }
|
||||
GradientStop { position: 0.5; color: "#15ffffff" }
|
||||
GradientStop { position: 1.0; color: "transparent" }
|
||||
}
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
// F. CRT Shader Effect (Overlay on chassis ONLY)
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
property real time: 0
|
||||
fragmentShader: "crt.qsb"
|
||||
NumberAnimation on time { from: 0; to: 100; duration: 5000; loops: Animation.Infinite }
|
||||
}
|
||||
}
|
||||
|
||||
// --- MASK ---
|
||||
Rectangle {
|
||||
id: contentMask
|
||||
anchors.fill: bgRect
|
||||
radius: height / 2
|
||||
visible: false; color: "white"
|
||||
smooth: true; antialiasing: true
|
||||
}
|
||||
|
||||
// --- COMPOSITED CHASSIS ---
|
||||
OpacityMask {
|
||||
anchors.fill: bgRect
|
||||
source: contentSource
|
||||
maskSource: contentMask
|
||||
}
|
||||
|
||||
// --- BORDER & INTERACTION ---
|
||||
Rectangle {
|
||||
id: bgRect
|
||||
anchors.fill: parent
|
||||
radius: height / 2
|
||||
color: "transparent"
|
||||
border.width: 1
|
||||
border.color: "#40ffffff"
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent; hoverEnabled: true
|
||||
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.PointingHandCursor
|
||||
onPressed: root.startSystemMove()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: ui.isRecording ? -(ui.amplitude * 3) : 0
|
||||
radius: parent.radius
|
||||
color: "transparent"
|
||||
border.width: ui.isRecording ? 3 : 1.5
|
||||
border.color: ui.isRecording ? "#A0ff4b4b" : "#6000f2ff"
|
||||
Behavior on anchors.margins {
|
||||
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
|
||||
}
|
||||
Behavior on border.width {
|
||||
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
|
||||
}
|
||||
SequentialAnimation on border.color {
|
||||
running: ui.isRecording
|
||||
loops: Animation.Infinite
|
||||
ColorAnimation { from: "#A0ff4b4b"; to: "#C0ff6b6b"; duration: 800 }
|
||||
ColorAnimation { from: "#C0ff6b6b"; to: "#A0ff4b4b"; duration: 800 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- MICROPHONE ICON (Enhanced) ---
|
||||
Item {
|
||||
id: micContainer
|
||||
width: 80; height: 80
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// Make entire button scale with amplitude
|
||||
scale: ui.isRecording ? (1.0 + ui.amplitude * 0.12) : 1.0
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
|
||||
}
|
||||
|
||||
// MouseArea at parent level to avoid layer blocking
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
console.log("Microphone clicked! Emitting signal...")
|
||||
ui.toggleRecordingRequested()
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowColor: ui.isRecording ? "#FF00a0ff" : "#FF00f2ff"
|
||||
shadowBlur: 1.0
|
||||
shadowOpacity: ui.isRecording ? 1.0 : 0.5
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: micCircle
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#40ffffff" }
|
||||
GradientStop { position: 1.0; color: "#10ffffff" }
|
||||
}
|
||||
border.width: 2; border.color: "#60ffffff"
|
||||
|
||||
SequentialAnimation on scale {
|
||||
running: ui.isRecording
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { from: 1.0; to: 1.08; duration: 600; easing.type: Easing.InOutQuad }
|
||||
NumberAnimation { from: 1.08; to: 1.0; duration: 600; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
|
||||
Image {
|
||||
id: micIcon
|
||||
anchors.centerIn: parent
|
||||
width: 40
|
||||
height: 40
|
||||
source: "microphone.svg"
|
||||
smooth: true
|
||||
antialiasing: true
|
||||
mipmap: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- RAINBOW WAVEFORM (Shader) ---
|
||||
Item {
|
||||
id: waveformContainer
|
||||
anchors.left: micContainer.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 80
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 90
|
||||
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
property real time: 0
|
||||
property real amplitude: ui.amplitude
|
||||
fragmentShader: "rainbow_wave.qsb"
|
||||
NumberAnimation on time { from: 0; to: 1000; duration: 100000; loops: Animation.Infinite }
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowColor: Qt.hsla((Date.now() / 100) % 1.0, 1.0, 0.6, 1.0)
|
||||
shadowBlur: 1.0; shadowOpacity: 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- RECORDING TIMER ---
|
||||
Item {
|
||||
id: recordingTimerContainer
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 60; height: 30
|
||||
property int recordingSeconds: 0
|
||||
Connections {
|
||||
target: ui
|
||||
function onIsRecordingChanged() {
|
||||
if (!ui.isRecording) recordingTimerContainer.recordingSeconds = 0
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
interval: 1000; running: ui.isRecording; repeat: true
|
||||
onTriggered: recordingTimerContainer.recordingSeconds++
|
||||
}
|
||||
|
||||
// Triple-layer glow for REALLY strong effect
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowColor: ui.isRecording ? "#FFff0000" : "#FFffffff"
|
||||
shadowBlur: 1.0
|
||||
shadowOpacity: 1.0
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 0
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowColor: ui.isRecording ? "#FFff3030" : "#FFe0e0e5"
|
||||
shadowBlur: 0.8
|
||||
shadowOpacity: 1.0
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
var mins = Math.floor(recordingTimerContainer.recordingSeconds / 60)
|
||||
var secs = recordingTimerContainer.recordingSeconds % 60
|
||||
return (mins < 10 ? "0" : "") + mins + ":" + (secs < 10 ? "0" : "") + secs
|
||||
}
|
||||
color: ui.isRecording ? "#ffffff" : "#ffffff"
|
||||
font.family: jetBrainsMono.name; font.pixelSize: 16; font.bold: true; font.letterSpacing: 2
|
||||
style: Text.Outline
|
||||
styleColor: ui.isRecording ? "#ff0000" : "#808085"
|
||||
SequentialAnimation on opacity {
|
||||
running: ui.isRecording; loops: Animation.Infinite
|
||||
NumberAnimation { from: 1.0; to: 0.7; duration: 800 }
|
||||
NumberAnimation { from: 0.7; to: 1.0; duration: 800 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user