Initial commit of WhisperVoice
10
src/ui/qml/AUTHORS.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# This is the official list of project authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS.txt file.
|
||||
# See the latter for an explanation.
|
||||
#
|
||||
# Names should be added to this file as:
|
||||
# Name or Organization <email address>
|
||||
|
||||
JetBrains <>
|
||||
Philipp Nurullin <philipp.nurullin@jetbrains.com>
|
||||
Konstantin Bulenkov <kb@jetbrains.com>
|
||||
47
src/ui/qml/GlowButton.qml
Normal file
@@ -0,0 +1,47 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
Button {
|
||||
id: control
|
||||
text: "Button"
|
||||
|
||||
property color accentColor: "#00f2ff"
|
||||
|
||||
contentItem: Text {
|
||||
text: control.text
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
color: control.hovered ? "white" : "#9499b0"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 100
|
||||
implicitHeight: 40
|
||||
opacity: control.down ? 0.7 : 1.0
|
||||
color: control.hovered ? Qt.rgba(1, 1, 1, 0.1) : Qt.rgba(1, 1, 1, 0.05)
|
||||
radius: 8
|
||||
border.color: control.hovered ? control.accentColor : Qt.rgba(1, 1, 1, 0.1)
|
||||
border.width: 1
|
||||
|
||||
Behavior on border.color { ColorAnimation { duration: 200 } }
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
|
||||
// Glow effect
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: "transparent"
|
||||
border.color: control.accentColor
|
||||
border.width: 2
|
||||
opacity: control.hovered ? 0.5 : 0.0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity { NumberAnimation { duration: 300 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/ui/qml/JetBrainsMono.zip
Normal file
186
src/ui/qml/Loader.qml
Normal file
@@ -0,0 +1,186 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
|
||||
FontLoader {
|
||||
id: jetBrainsMono
|
||||
source: "fonts/ttf/JetBrainsMono-Bold.ttf"
|
||||
}
|
||||
width: 400
|
||||
height: 250
|
||||
visible: true
|
||||
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
id: bgRect
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20 // Space for shadow
|
||||
radius: 16
|
||||
color: "#1a1a20"
|
||||
border.color: "#40ffffff"
|
||||
border.width: 1
|
||||
|
||||
// --- SHADOW & GLOW ---
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowColor: "#80000000"
|
||||
shadowBlur: 1.0
|
||||
shadowOpacity: 0.8
|
||||
shadowVerticalOffset: 4
|
||||
autoPaddingEnabled: true
|
||||
}
|
||||
|
||||
// --- CONTENT ---
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 60
|
||||
spacing: 24
|
||||
|
||||
// Logo / Icon Area
|
||||
Item {
|
||||
width: 60; height: 60
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 30
|
||||
color: "transparent"
|
||||
border.width: 2
|
||||
border.color: "#00f2ff"
|
||||
|
||||
// Pulse Animation
|
||||
SequentialAnimation on scale {
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { from: 1.0; to: 1.1; duration: 1000; easing.type: Easing.InOutSine }
|
||||
NumberAnimation { from: 1.1; to: 1.0; duration: 1000; easing.type: Easing.InOutSine }
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
source: "microphone.svg"
|
||||
anchors.centerIn: parent
|
||||
width: 32; height: 32
|
||||
sourceSize: Qt.size(32, 32)
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
|
||||
// Colorize
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
colorization: 1.0
|
||||
colorizationColor: "#00f2ff"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Title
|
||||
Column {
|
||||
spacing: 4
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Text {
|
||||
text: "WHISPER VOICE"
|
||||
color: "#ffffff"
|
||||
font.family: jetBrainsMono.name
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
font.letterSpacing: 3
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "AI TRANSCRIPTION ENGINE"
|
||||
color: "#80ffffff"
|
||||
font.family: jetBrainsMono.name
|
||||
font.pixelSize: 10
|
||||
font.letterSpacing: 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Status & Progress
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
|
||||
// Progress Bar Background
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 4
|
||||
color: "#252530"
|
||||
radius: 2
|
||||
clip: true
|
||||
|
||||
// Progress Fill
|
||||
Rectangle {
|
||||
width: (ui.downloadProgress / 100.0) * parent.width
|
||||
height: parent.height
|
||||
radius: 2
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.0; color: "#00f2ff" }
|
||||
GradientStop { position: 1.0; color: "#00a0ff" }
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 250; easing.type: Easing.OutCubic }
|
||||
}
|
||||
|
||||
// Shimmer effect on bar
|
||||
Rectangle {
|
||||
width: 20; height: parent.height
|
||||
color: "#80ffffff"
|
||||
x: -width
|
||||
opacity: 0.5
|
||||
rotation: 20
|
||||
transformOrigin: Item.Center
|
||||
|
||||
NumberAnimation on x {
|
||||
from: 0; to: parent.width + 50
|
||||
duration: 1000
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Status Text
|
||||
Text {
|
||||
text: ui.loaderStatus
|
||||
color: "#00f2ff"
|
||||
font.family: jetBrainsMono.name
|
||||
font.pixelSize: 11
|
||||
font.bold: true
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
opacity: 0.8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entry Animation
|
||||
Component.onCompleted: {
|
||||
bgRect.scale = 0.9
|
||||
bgRect.opacity = 0
|
||||
entryAnim.start()
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
id: entryAnim
|
||||
|
||||
NumberAnimation {
|
||||
target: bgRect; property: "scale"
|
||||
to: 1.0; duration: 600; easing.type: Easing.OutBack
|
||||
}
|
||||
NumberAnimation {
|
||||
target: bgRect; property: "opacity"
|
||||
to: 1.0; duration: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/ui/qml/ModernComboBox.qml
Normal file
@@ -0,0 +1,127 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
ComboBox {
|
||||
id: control
|
||||
|
||||
// Custom properties
|
||||
property color accentColor: SettingsStyle.accent
|
||||
property color bgColor: "#1a1a20"
|
||||
property color popupColor: "#252530"
|
||||
|
||||
delegate: ItemDelegate {
|
||||
id: delegate
|
||||
width: control.width
|
||||
height: 40
|
||||
padding: 0
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: 8
|
||||
width: parent.width
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
|
||||
Text {
|
||||
text: control.textRole ? (modelData[control.textRole] || modelData) : modelData
|
||||
color: highlighted ? control.accentColor : "#ffffff"
|
||||
font.family: "JetBrains Mono"
|
||||
font.pixelSize: 14
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
scale: highlighted ? 1.05 : 1.0
|
||||
Behavior on scale { NumberAnimation { duration: 100 } }
|
||||
}
|
||||
|
||||
// Indicator for "Downloaded" or "Active"
|
||||
Rectangle {
|
||||
width: 6; height: 6; radius: 3
|
||||
color: control.accentColor
|
||||
visible: ui.isModelDownloaded(modelData)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: highlighted ? Qt.rgba(control.accentColor.r, control.accentColor.g, control.accentColor.b, 0.1) : "transparent"
|
||||
radius: 4
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
}
|
||||
}
|
||||
|
||||
indicator: Canvas {
|
||||
x: control.width - width - control.rightPadding
|
||||
y: control.topPadding + (control.availableHeight - height) / 2
|
||||
width: 12
|
||||
height: 8
|
||||
contextType: "2d"
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onPressedChanged() { control.indicator.requestPaint() }
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
context.reset();
|
||||
context.moveTo(0, 0);
|
||||
context.lineTo(width, 0);
|
||||
context.lineTo(width / 2, height);
|
||||
context.closePath();
|
||||
context.fillStyle = control.pressed ? control.accentColor : "#888888";
|
||||
context.fill();
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: 10
|
||||
rightPadding: control.indicator.width + control.spacing
|
||||
|
||||
text: control.displayText
|
||||
font.family: "JetBrains Mono"
|
||||
font.pixelSize: 14
|
||||
color: control.pressed ? control.accentColor : "#ffffff"
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 140
|
||||
implicitHeight: 40
|
||||
color: control.bgColor
|
||||
border.color: control.pressed || control.activeFocus ? control.accentColor : "#40ffffff"
|
||||
border.width: 1
|
||||
radius: 6
|
||||
|
||||
// Glow effect on focus (Simplified to just border for stability)
|
||||
Behavior on border.color { ColorAnimation { duration: 150 } }
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: control.height - 1
|
||||
width: control.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 5
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: control.popup.visible ? control.delegateModel : null
|
||||
currentIndex: control.highlightedIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator { }
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: control.popupColor
|
||||
border.color: "#40ffffff"
|
||||
border.width: 1
|
||||
radius: 6
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 0.0; to: 1.0; duration: 100 }
|
||||
NumberAnimation { property: "scale"; from: 0.95; to: 1.0; duration: 100 }
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/ui/qml/ModernKeySequenceRecorder.qml
Normal file
@@ -0,0 +1,111 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
Rectangle {
|
||||
id: control
|
||||
implicitWidth: 140
|
||||
implicitHeight: 32
|
||||
color: "#1a1a20"
|
||||
radius: 6
|
||||
border.width: 1
|
||||
border.color: activeFocus || recording ? SettingsStyle.accent : "#40ffffff"
|
||||
|
||||
property string currentSequence: ""
|
||||
signal sequenceChanged(string seq)
|
||||
|
||||
property bool recording: false
|
||||
|
||||
onRecordingChanged: {
|
||||
if (recording) {
|
||||
ui.hotkeysEnabled = false
|
||||
} else {
|
||||
ui.hotkeysEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: control.recording ? "Listening..." : (control.currentSequence || "None")
|
||||
color: control.recording ? SettingsStyle.accent : (control.currentSequence ? "#ffffff" : "#808080")
|
||||
font.family: "JetBrains Mono"
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
control.forceActiveFocus()
|
||||
control.recording = true
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if (!control.recording) return
|
||||
|
||||
// Ignore specific standalone modifiers to allow combos
|
||||
if (event.key === Qt.Key_Control || event.key === Qt.Key_Shift || event.key === Qt.Key_Alt || event.key === Qt.Key_Meta) {
|
||||
return
|
||||
}
|
||||
|
||||
// Build Modifier String
|
||||
var seq = ""
|
||||
if (event.modifiers & Qt.ControlModifier) seq += "ctrl+"
|
||||
if (event.modifiers & Qt.ShiftModifier) seq += "shift+"
|
||||
if (event.modifiers & Qt.AltModifier) seq += "alt+"
|
||||
if (event.modifiers & Qt.MetaModifier) seq += "win+"
|
||||
|
||||
// Get Key Name
|
||||
var keyName = getKeyName(event.key, event.text)
|
||||
seq += keyName
|
||||
|
||||
// Update
|
||||
control.currentSequence = seq
|
||||
control.sequenceChanged(seq)
|
||||
control.recording = false
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (!activeFocus) control.recording = false
|
||||
}
|
||||
|
||||
function getKeyName(key, text) {
|
||||
// F-Keys
|
||||
if (key >= Qt.Key_F1 && key <= Qt.Key_F35) return "f" + (key - Qt.Key_F1 + 1)
|
||||
|
||||
// Common Keys
|
||||
switch (key) {
|
||||
case Qt.Key_Space: return "space"
|
||||
case Qt.Key_Backspace: return "backspace"
|
||||
case Qt.Key_Tab: return "tab"
|
||||
case Qt.Key_Return: return "enter"
|
||||
case Qt.Key_Enter: return "enter"
|
||||
case Qt.Key_Escape: return "esc"
|
||||
case Qt.Key_Delete: return "delete"
|
||||
case Qt.Key_Insert: return "insert"
|
||||
case Qt.Key_Home: return "home"
|
||||
case Qt.Key_End: return "end"
|
||||
case Qt.Key_PageUp: return "pageup"
|
||||
case Qt.Key_PageDown: return "pagedown"
|
||||
case Qt.Key_Up: return "up"
|
||||
case Qt.Key_Down: return "down"
|
||||
case Qt.Key_Left: return "left"
|
||||
case Qt.Key_Right: return "right"
|
||||
case Qt.Key_CapsLock: return "capslock"
|
||||
case Qt.Key_NumLock: return "numlock"
|
||||
case Qt.Key_ScrollLock: return "scrolllock"
|
||||
case Qt.Key_Print: return "printscreen"
|
||||
case Qt.Key_Pause: return "pause"
|
||||
}
|
||||
|
||||
// Letters and Numbers (Use text if available, fallback to manual)
|
||||
if (text && text.length > 0 && text.charCodeAt(0) >= 32) {
|
||||
return text.toLowerCase()
|
||||
}
|
||||
|
||||
return "key" + key
|
||||
}
|
||||
}
|
||||
92
src/ui/qml/ModernSettingsItem.qml
Normal file
@@ -0,0 +1,92 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: SettingsStyle.itemHeight
|
||||
Layout.minimumHeight: SettingsStyle.itemHeight
|
||||
Layout.maximumHeight: SettingsStyle.itemHeight
|
||||
implicitHeight: SettingsStyle.itemHeight
|
||||
|
||||
color: hHandler.hovered ? SettingsStyle.surfaceHover : "transparent"
|
||||
radius: 0 // Continuous list style (clipped by container container)
|
||||
|
||||
// Properties
|
||||
property string label: "Setting Name"
|
||||
property string description: ""
|
||||
property alias control: controlContainer.data
|
||||
property bool showSeparator: true
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
|
||||
HoverHandler {
|
||||
id: hHandler
|
||||
}
|
||||
|
||||
// Label & Description
|
||||
Column {
|
||||
id: labelCol
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 16
|
||||
anchors.right: controlContainer.left
|
||||
anchors.rightMargin: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
id: labelText
|
||||
text: root.label
|
||||
color: SettingsStyle.textPrimary
|
||||
font.family: "JetBrains Mono"
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
|
||||
// Text Layout Normalization
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
}
|
||||
|
||||
Text {
|
||||
id: descText
|
||||
text: root.description
|
||||
color: SettingsStyle.textSecondary
|
||||
font.family: "JetBrains Mono"
|
||||
font.pixelSize: 11
|
||||
width: parent.width
|
||||
visible: text !== ""
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
// Text Layout Normalization
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
}
|
||||
}
|
||||
|
||||
// Control Container
|
||||
Item {
|
||||
id: controlContainer
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
implicitWidth: childrenRect.width
|
||||
implicitHeight: childrenRect.height
|
||||
}
|
||||
|
||||
// Bottom Separator
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
height: 1
|
||||
color: SettingsStyle.borderSubtle
|
||||
visible: root.showSeparator
|
||||
}
|
||||
}
|
||||
61
src/ui/qml/ModernSettingsSection.qml
Normal file
@@ -0,0 +1,61 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 8
|
||||
|
||||
default property alias content: contentColumn.data
|
||||
property string title: ""
|
||||
|
||||
// Section Header
|
||||
Text {
|
||||
text: root.title
|
||||
color: SettingsStyle.accent // Accented header for more color
|
||||
font.family: "JetBrains Mono"
|
||||
font.pixelSize: 12
|
||||
font.bold: true
|
||||
font.capitalization: Font.AllUppercase
|
||||
font.letterSpacing: 1.5
|
||||
Layout.leftMargin: 4
|
||||
Layout.bottomMargin: 4
|
||||
visible: text !== ""
|
||||
}
|
||||
|
||||
// Card Container
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
// Height checks children
|
||||
implicitHeight: contentColumn.implicitHeight
|
||||
|
||||
color: SettingsStyle.surfaceCard
|
||||
radius: SettingsStyle.cardRadius
|
||||
border.color: SettingsStyle.borderSubtle
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1 // Minimal margin to not overlap border
|
||||
spacing: 0 // No gaps, rely on separators
|
||||
|
||||
// Clip content to rounded corners
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1.0
|
||||
maskSource: ShaderEffectSource {
|
||||
sourceItem: Rectangle {
|
||||
width: contentColumn.width
|
||||
height: contentColumn.height
|
||||
radius: SettingsStyle.cardRadius - 1
|
||||
color: "black"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/ui/qml/ModernSlider.qml
Normal file
@@ -0,0 +1,61 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
|
||||
Slider {
|
||||
id: control
|
||||
|
||||
background: Rectangle {
|
||||
x: control.leftPadding
|
||||
y: control.topPadding + control.availableHeight / 2 - height / 2
|
||||
implicitWidth: 200
|
||||
implicitHeight: 4
|
||||
width: control.availableWidth
|
||||
height: implicitHeight
|
||||
radius: 2
|
||||
color: "#2d2d3d"
|
||||
|
||||
Rectangle {
|
||||
width: control.visualPosition * parent.width
|
||||
height: parent.height
|
||||
color: SettingsStyle.accent
|
||||
radius: 2
|
||||
}
|
||||
}
|
||||
|
||||
handle: Rectangle {
|
||||
x: control.leftPadding + control.visualPosition * (control.availableWidth - width)
|
||||
y: control.topPadding + control.availableHeight / 2 - height / 2
|
||||
implicitWidth: 18
|
||||
implicitHeight: 18
|
||||
radius: 9
|
||||
color: "white"
|
||||
border.color: SettingsStyle.accent
|
||||
border.width: 2
|
||||
|
||||
layer.enabled: control.pressed
|
||||
layer.effect: MultiEffect {
|
||||
blurEnabled: true
|
||||
blur: 0.5
|
||||
shadowEnabled: true
|
||||
shadowColor: SettingsStyle.accent
|
||||
}
|
||||
}
|
||||
// Value Readout (Left side to avoid clipping on right edge)
|
||||
Text {
|
||||
anchors.right: parent.left
|
||||
anchors.rightMargin: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignRight
|
||||
|
||||
text: {
|
||||
var val = control.value
|
||||
return (val % 1 === 0) ? val.toFixed(0) : val.toFixed(1)
|
||||
}
|
||||
|
||||
color: SettingsStyle.textSecondary
|
||||
font.family: "JetBrains Mono"
|
||||
font.pixelSize: 12
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
40
src/ui/qml/ModernSwitch.qml
Normal file
@@ -0,0 +1,40 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
Switch {
|
||||
id: control
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 44
|
||||
implicitHeight: 24
|
||||
x: control.leftPadding
|
||||
y: parent.height / 2 - height / 2
|
||||
radius: 12
|
||||
color: control.checked ? SettingsStyle.accent : "#2d2d3d"
|
||||
border.color: control.checked ? SettingsStyle.accent : "#3d3d4d"
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
|
||||
Rectangle {
|
||||
x: control.checked ? parent.width - width - 3 : 3
|
||||
y: 3
|
||||
width: 18
|
||||
height: 18
|
||||
radius: 9
|
||||
color: "white"
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: control.text
|
||||
font: control.font
|
||||
opacity: enabled ? 1.0 : 0.3
|
||||
color: "white"
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: control.indicator.width + control.spacing
|
||||
}
|
||||
}
|
||||
27
src/ui/qml/ModernTextField.qml
Normal file
@@ -0,0 +1,27 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
TextField {
|
||||
id: control
|
||||
|
||||
property color accentColor: "#00f2ff"
|
||||
property color bgColor: "#1a1a20"
|
||||
|
||||
placeholderTextColor: "#606060"
|
||||
color: "#ffffff"
|
||||
font.family: "JetBrains Mono"
|
||||
font.pixelSize: 14
|
||||
selectedTextColor: "#000000"
|
||||
selectionColor: accentColor
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 200
|
||||
implicitHeight: 40
|
||||
color: control.bgColor
|
||||
border.color: control.activeFocus ? control.accentColor : "#40ffffff"
|
||||
border.width: 1
|
||||
radius: 6
|
||||
|
||||
Behavior on border.color { ColorAnimation { duration: 150 } }
|
||||
}
|
||||
}
|
||||
93
src/ui/qml/OFL.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
353
src/ui/qml/Overlay.qml
Normal file
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
905
src/ui/qml/Settings.qml
Normal file
@@ -0,0 +1,905 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
|
||||
Window {
|
||||
id: root
|
||||
|
||||
width: 850 * (ui ? ui.uiScale : 1.0)
|
||||
height: 620 * (ui ? ui.uiScale : 1.0)
|
||||
|
||||
visible: false
|
||||
flags: Qt.FramelessWindowHint | Qt.Window
|
||||
color: "transparent"
|
||||
title: "Settings"
|
||||
|
||||
// Explicit sizing for Python to read
|
||||
|
||||
// Prevent destruction on close
|
||||
onClosing: (close) => {
|
||||
close.accepted = false
|
||||
root.visible = false
|
||||
}
|
||||
|
||||
// Load Font
|
||||
FontLoader {
|
||||
id: jetBrainsMono
|
||||
source: "fonts/ttf/JetBrainsMono-Bold.ttf"
|
||||
}
|
||||
|
||||
readonly property string mainFont: jetBrainsMono.name
|
||||
property bool isLoaded: false
|
||||
|
||||
Component.onCompleted: {
|
||||
isLoaded = true
|
||||
}
|
||||
|
||||
// --- REUSABLE COMPONENTS ---
|
||||
component StatBox: Rectangle {
|
||||
property string label: ""
|
||||
property string value: ""
|
||||
property string unit: ""
|
||||
property color accent: SettingsStyle.accent
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 80
|
||||
color: "#16161a"
|
||||
radius: 12
|
||||
border.color: SettingsStyle.borderSubtle
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
Text { text: label; color: SettingsStyle.textSecondary; font.pixelSize: 11; font.bold: true; anchors.horizontalCenter: parent.horizontalCenter }
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 2
|
||||
Text { text: value; color: accent; font.pixelSize: 22; font.bold: true; font.family: "JetBrains Mono" }
|
||||
Text { text: unit; color: SettingsStyle.textSecondary; font.pixelSize: 12; anchors.baseline: parent.bottom; anchors.baselineOffset: -4 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main Container
|
||||
Rectangle {
|
||||
id: mainContainer
|
||||
anchors.fill: parent
|
||||
radius: 16
|
||||
color: SettingsStyle.background
|
||||
border.color: SettingsStyle.borderSubtle
|
||||
border.width: 1
|
||||
|
||||
opacity: 0
|
||||
transform: Translate {
|
||||
id: entryTranslate
|
||||
y: 20
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
entryAnim.start()
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
id: entryAnim
|
||||
NumberAnimation { target: mainContainer; property: "opacity"; to: 1; duration: 400; easing.type: Easing.OutCubic }
|
||||
NumberAnimation { target: entryTranslate; property: "y"; to: 0; duration: 500; easing.type: Easing.OutBack; easing.overshoot: 0.8 }
|
||||
}
|
||||
|
||||
// --- TITLE BAR ---
|
||||
Item {
|
||||
id: titleBar
|
||||
height: 60
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: root.startSystemMove()
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 24
|
||||
anchors.rightMargin: 24
|
||||
|
||||
Image {
|
||||
source: "microphone.svg"
|
||||
sourceSize.width: 18
|
||||
sourceSize.height: 18
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
opacity: 0.7
|
||||
// Colorize the icon
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
colorization: 1.0
|
||||
colorizationColor: SettingsStyle.accent
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "SETTINGS"
|
||||
color: SettingsStyle.textSecondary
|
||||
font.family: mainFont
|
||||
font.pixelSize: 13
|
||||
font.letterSpacing: 2
|
||||
font.bold: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
// Improved Close Button
|
||||
Rectangle {
|
||||
width: 32; height: 32
|
||||
radius: 8
|
||||
color: closeMa.containsMouse ? "#20ff4b4b" : "transparent"
|
||||
border.color: closeMa.containsMouse ? "#40ff4b4b" : "transparent"
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "×"
|
||||
color: closeMa.containsMouse ? "#ff4b4b" : SettingsStyle.textSecondary
|
||||
font.family: mainFont
|
||||
font.pixelSize: 20
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeMa
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
Behavior on border.color { ColorAnimation { duration: 150 } }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: SettingsStyle.borderSubtle
|
||||
}
|
||||
}
|
||||
|
||||
// --- CONTENT AREA ---
|
||||
RowLayout {
|
||||
anchors.top: titleBar.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: 0
|
||||
|
||||
// --- SIDEBAR ---
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 220
|
||||
Layout.minimumWidth: 220
|
||||
Layout.maximumWidth: 220
|
||||
color: Qt.rgba(1, 1, 1, 0.02) // Very subtle separation
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 8
|
||||
|
||||
ListModel {
|
||||
id: navModel
|
||||
ListElement { name: "General"; icon: "settings.svg" }
|
||||
ListElement { name: "Audio"; icon: "microphone.svg" }
|
||||
ListElement { name: "Visuals"; icon: "visibility.svg" }
|
||||
ListElement { name: "AI Engine"; icon: "smart_toy.svg" }
|
||||
ListElement { name: "Debug"; icon: "terminal.svg" }
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: navModel
|
||||
delegate: Rectangle {
|
||||
id: navBtnRoot
|
||||
Layout.fillWidth: true
|
||||
height: 38
|
||||
color: stack.currentIndex === index ? SettingsStyle.surfaceHover : (ma.containsMouse ? Qt.rgba(1,1,1,0.03) : "transparent")
|
||||
radius: 6
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
|
||||
// Left active stripe
|
||||
Rectangle {
|
||||
width: 3
|
||||
height: 20
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: 2
|
||||
color: SettingsStyle.accent
|
||||
visible: stack.currentIndex === index
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
spacing: 12
|
||||
|
||||
Image {
|
||||
source: icon
|
||||
sourceSize.width: 16
|
||||
sourceSize.height: 16
|
||||
fillMode: Image.PreserveAspectFit
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
opacity: stack.currentIndex === index ? 1.0 : 0.5
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
colorization: 1.0
|
||||
colorizationColor: stack.currentIndex === index ? SettingsStyle.accent : SettingsStyle.textSecondary
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: name
|
||||
color: stack.currentIndex === index ? SettingsStyle.textPrimary : SettingsStyle.textSecondary
|
||||
font.family: mainFont
|
||||
font.pixelSize: 13
|
||||
font.weight: stack.currentIndex === index ? Font.Bold : Font.Normal
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: ma
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: stack.currentIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
}
|
||||
|
||||
// Vertical Divider
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
width: 1
|
||||
color: SettingsStyle.borderSubtle
|
||||
}
|
||||
}
|
||||
|
||||
// --- MAIN CONTENT STACK ---
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
StackLayout {
|
||||
id: stack
|
||||
anchors.fill: parent
|
||||
currentIndex: 0
|
||||
|
||||
// --- TAB: GENERAL ---
|
||||
ScrollView {
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 24
|
||||
anchors.margins: 32
|
||||
|
||||
// Header
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
Text { text: "General"; color: SettingsStyle.textPrimary; font.family: mainFont; font.pixelSize: 24; font.bold: true }
|
||||
Text { text: "System behavior and shortcuts"; color: SettingsStyle.textSecondary; font.family: mainFont; font.pixelSize: 14 }
|
||||
}
|
||||
|
||||
ModernSettingsSection {
|
||||
title: "Application"
|
||||
Layout.margins: 32
|
||||
Layout.topMargin: 0
|
||||
|
||||
content: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Global Hotkey"
|
||||
description: "Press to record a new shortcut (e.g. Ctrl+Space)"
|
||||
control: ModernKeySequenceRecorder {
|
||||
Layout.preferredWidth: 200
|
||||
currentSequence: ui.getSetting("hotkey")
|
||||
onSequenceChanged: (seq) => ui.setSetting("hotkey", seq)
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Run on Startup"
|
||||
description: "Automatically launch when you log in"
|
||||
control: ModernSwitch {
|
||||
checked: ui.getSetting("run_on_startup")
|
||||
onToggled: ui.setSetting("run_on_startup", checked)
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Input Method"
|
||||
description: "How text is sent to the active window"
|
||||
control: ModernComboBox {
|
||||
width: 160
|
||||
model: ["Clipboard Paste", "Simulate Typing"]
|
||||
currentIndex: model.indexOf(ui.getSetting("input_method"))
|
||||
onActivated: ui.setSetting("input_method", currentText)
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Typing Speed"
|
||||
description: "Chars/min"
|
||||
showSeparator: false
|
||||
control: ModernSlider {
|
||||
Layout.preferredWidth: 200
|
||||
from: 10; to: 6000
|
||||
stepSize: 10
|
||||
snapMode: Slider.SnapAlways
|
||||
value: ui.getSetting("typing_speed")
|
||||
onMoved: ui.setSetting("typing_speed", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- TAB: AUDIO ---
|
||||
ScrollView {
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 24
|
||||
anchors.margins: 32
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
Text { text: "Audio"; color: SettingsStyle.textPrimary; font.family: mainFont; font.pixelSize: 24; font.bold: true }
|
||||
Text { text: "Input devices and signal processing"; color: SettingsStyle.textSecondary; font.family: mainFont; font.pixelSize: 14 }
|
||||
}
|
||||
|
||||
ModernSettingsSection {
|
||||
title: "Devices"
|
||||
Layout.margins: 32
|
||||
Layout.topMargin: 0
|
||||
|
||||
content: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Microphone"
|
||||
description: "Select your primary input device"
|
||||
control: ModernComboBox {
|
||||
Layout.preferredWidth: 280
|
||||
textRole: "name"
|
||||
valueRole: "id"
|
||||
model: ui.getAudioDevices()
|
||||
onActivated: ui.setSetting("input_device", model[currentIndex].id) // Explicitly use model index
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Save Recordings"
|
||||
description: "Save .wav files to ./recordings folder"
|
||||
showSeparator: false
|
||||
control: ModernSwitch {
|
||||
checked: ui.getSetting("save_recordings")
|
||||
onToggled: ui.setSetting("save_recordings", checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsSection {
|
||||
title: "Signal Processing"
|
||||
Layout.margins: 32
|
||||
Layout.topMargin: 0
|
||||
|
||||
content: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "VAD Threshold"
|
||||
description: "Silence detection sensitivity (" + (ui.getSetting("silence_threshold") * 100).toFixed(0) + "%)"
|
||||
control: ModernSlider {
|
||||
Layout.preferredWidth: 200
|
||||
from: 1; to: 100
|
||||
value: ui.getSetting("silence_threshold") * 100
|
||||
onMoved: ui.setSetting("silence_threshold", Number((value / 100.0).toFixed(2)))
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Silence Timeout"
|
||||
description: "Stop recording after " + (ui.getSetting("silence_duration")).toFixed(1) + "s of silence"
|
||||
showSeparator: false
|
||||
control: ModernSlider {
|
||||
Layout.preferredWidth: 200
|
||||
from: 0.5; to: 5.0
|
||||
value: ui.getSetting("silence_duration")
|
||||
onMoved: ui.setSetting("silence_duration", Number(value.toFixed(1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- TAB: VISUALS ---
|
||||
ScrollView {
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 24
|
||||
anchors.margins: 32
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
Text { text: "Visuals"; color: SettingsStyle.textPrimary; font.family: mainFont; font.pixelSize: 24; font.bold: true }
|
||||
Text { text: "Customize the overlay appearance"; color: SettingsStyle.textSecondary; font.family: mainFont; font.pixelSize: 14 }
|
||||
}
|
||||
|
||||
ModernSettingsSection {
|
||||
title: "Overlay"
|
||||
Layout.margins: 32
|
||||
Layout.topMargin: 0
|
||||
|
||||
content: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "UI Scale"
|
||||
description: "Global interface scaling factor (" + ui.getSetting("ui_scale").toFixed(2) + "x)"
|
||||
control: ModernSlider {
|
||||
Layout.preferredWidth: 200
|
||||
from: 0.75; to: 1.5
|
||||
value: ui.getSetting("ui_scale")
|
||||
onMoved: ui.setSetting("ui_scale", Number(value.toFixed(2)))
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Always on Top"
|
||||
description: "Keep the overlay visible above other windows"
|
||||
control: ModernSwitch {
|
||||
checked: ui.getSetting("always_on_top")
|
||||
onToggled: ui.setSetting("always_on_top", checked)
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Window Opacity"
|
||||
description: "Transparency level"
|
||||
showSeparator: false
|
||||
control: ModernSlider {
|
||||
Layout.preferredWidth: 200
|
||||
from: 0.1; to: 1.0
|
||||
value: ui.getSetting("opacity")
|
||||
onMoved: ui.setSetting("opacity", Number(value.toFixed(2)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsSection {
|
||||
title: "Window Position"
|
||||
Layout.margins: 32
|
||||
Layout.topMargin: 0
|
||||
|
||||
content: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Anchor Position"
|
||||
description: "Where the overlay snaps to on screen"
|
||||
control: ModernComboBox {
|
||||
width: 160
|
||||
model: ["Bottom Center", "Top Center", "Bottom Right", "Top Right", "Bottom Left", "Top Left"]
|
||||
currentIndex: model.indexOf(ui.getSetting("overlay_position"))
|
||||
onActivated: ui.setSetting("overlay_position", currentText)
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Horizontal Offset"
|
||||
description: "Fine-tune X position (" + ui.getSetting("overlay_offset_x") + "px)"
|
||||
control: ModernSlider {
|
||||
Layout.preferredWidth: 200
|
||||
from: -500; to: 500
|
||||
value: ui.getSetting("overlay_offset_x")
|
||||
onMoved: ui.setSetting("overlay_offset_x", value)
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Vertical Offset"
|
||||
description: "Fine-tune Y position (" + ui.getSetting("overlay_offset_y") + "px)"
|
||||
showSeparator: false
|
||||
control: ModernSlider {
|
||||
Layout.preferredWidth: 200
|
||||
from: -500; to: 500
|
||||
value: ui.getSetting("overlay_offset_y")
|
||||
onMoved: ui.setSetting("overlay_offset_y", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- TAB: AI ENGINE ---
|
||||
ScrollView {
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 24
|
||||
anchors.margins: 32
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
Text { text: "AI Engine"; color: SettingsStyle.textPrimary; font.family: mainFont; font.pixelSize: 24; font.bold: true }
|
||||
Text { text: "Model configuration and performance"; color: SettingsStyle.textSecondary; font.family: mainFont; font.pixelSize: 14 }
|
||||
}
|
||||
|
||||
ModernSettingsSection {
|
||||
title: "Model Config"
|
||||
Layout.margins: 32
|
||||
Layout.topMargin: 0
|
||||
|
||||
content: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
ListModel {
|
||||
id: modelDetailsModel
|
||||
ListElement { name: "tiny"; info: "39M params • <1GB VRAM • 32x speed. Fastest, best for weak hardware." }
|
||||
ListElement { name: "base"; info: "74M params • ~1GB VRAM • 16x speed. Efficient for simple commands." }
|
||||
ListElement { name: "small"; info: "244M params • ~2GB VRAM • 6x speed. Recommended for most users." }
|
||||
ListElement { name: "medium"; info: "769M params • ~5GB VRAM • Accurate, high fidelity. Mid-range GPU req." }
|
||||
ListElement { name: "large-v3"; info: "1.5B params • ~10GB VRAM • Pro quality. Requires high-end hardware." }
|
||||
ListElement { name: "turbo"; info: "800M params • ~6GB VRAM • Large-v3 quality with 8x speed boost." }
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Model Size"
|
||||
description: "Larger models are smarter but slower"
|
||||
control: ModernComboBox {
|
||||
id: modelSizeCombo
|
||||
width: 140
|
||||
model: ["tiny", "base", "small", "medium", "large-v3", "turbo"]
|
||||
currentIndex: model.indexOf(ui.getSetting("model_size"))
|
||||
onActivated: ui.setSetting("model_size", currentText)
|
||||
}
|
||||
}
|
||||
|
||||
// Model Info Card
|
||||
Rectangle {
|
||||
id: modelInfoCard
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 12
|
||||
Layout.topMargin: 0
|
||||
Layout.bottomMargin: 16
|
||||
height: 54
|
||||
color: "#0a0a0f"
|
||||
radius: 6
|
||||
border.color: SettingsStyle.borderSubtle
|
||||
border.width: 1
|
||||
|
||||
// Improved reactive check
|
||||
property bool isDownloaded: false
|
||||
|
||||
Timer {
|
||||
id: checkTimer
|
||||
interval: 1000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: parent.checkStatus()
|
||||
}
|
||||
|
||||
function checkStatus() {
|
||||
if (modelSizeCombo && modelSizeCombo.currentText) {
|
||||
isDownloaded = ui.isModelDownloaded(modelSizeCombo.currentText)
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh when notified by Python
|
||||
Connections {
|
||||
target: ui
|
||||
function onModelStatesChanged() {
|
||||
checkStatus()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: checkStatus()
|
||||
|
||||
// Also check when selection changes
|
||||
Connections {
|
||||
target: modelSizeCombo
|
||||
function onActivated() { modelInfoCard.checkStatus() }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
spacing: 12
|
||||
|
||||
Image {
|
||||
source: "smart_toy.svg"
|
||||
sourceSize: Qt.size(16, 16)
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
colorization: 1.0
|
||||
colorizationColor: modelInfoCard.isDownloaded ? SettingsStyle.accent : "#808080"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
Text {
|
||||
text: {
|
||||
if (ui.isDownloading) return "Downloading AI Core..."
|
||||
for (var i = 0; i < modelDetailsModel.count; i++) {
|
||||
if (modelDetailsModel.get(i).name === modelSizeCombo.currentText) {
|
||||
return modelDetailsModel.get(i).info
|
||||
}
|
||||
}
|
||||
return "Select a model."
|
||||
}
|
||||
color: "#ffffff"
|
||||
font.family: "JetBrains Mono"
|
||||
font.pixelSize: 10
|
||||
opacity: 0.7
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: downloaderBar
|
||||
visible: ui.isDownloading
|
||||
Layout.fillWidth: true
|
||||
height: 2
|
||||
color: "#20ffffff"
|
||||
Rectangle {
|
||||
width: downloaderBar.width * 0.5
|
||||
height: downloaderBar.height
|
||||
color: SettingsStyle.accent
|
||||
SequentialAnimation on x {
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { from: -width; to: downloaderBar.width; duration: 1500 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: downloadBtn
|
||||
text: "Download"
|
||||
visible: !modelInfoCard.isDownloaded && !ui.isDownloading
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 80
|
||||
|
||||
contentItem: Text {
|
||||
text: "DOWNLOAD"
|
||||
font.pixelSize: 10; font.bold: true; color: "#000000"; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
background: Rectangle {
|
||||
color: downloadBtn.hovered ? "#ffffff" : SettingsStyle.accent; radius: 4
|
||||
}
|
||||
onClicked: ui.downloadModel(modelSizeCombo.currentText)
|
||||
}
|
||||
|
||||
// Status tag
|
||||
Rectangle {
|
||||
id: statusTag
|
||||
visible: modelInfoCard.isDownloaded && !ui.isDownloading
|
||||
height: 18; width: 64; radius: 4; color: "#1000f2ff"
|
||||
border.color: "#3000f2ff"; border.width: 1
|
||||
Text {
|
||||
anchors.centerIn: statusTag; text: "INSTALLED"; font.pixelSize: 9
|
||||
font.bold: true; color: SettingsStyle.accent; opacity: 0.9
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Language"
|
||||
description: "Force language or Auto-detect"
|
||||
control: ModernComboBox {
|
||||
width: 140
|
||||
model: ["auto", "en", "fr", "de", "es", "it", "ja", "zh", "ru"]
|
||||
currentIndex: model.indexOf(ui.getSetting("language"))
|
||||
onActivated: ui.setSetting("language", currentText)
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Compute Device"
|
||||
description: "Hardware acceleration (CUDA requires NVidia GPU)"
|
||||
control: ModernComboBox {
|
||||
width: 140
|
||||
model: ["auto", "cuda", "cpu"]
|
||||
currentIndex: model.indexOf(ui.getSetting("compute_device"))
|
||||
onActivated: ui.setSetting("compute_device", currentText)
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Precision"
|
||||
description: "Quantization type (int8 is faster, float16 is accurate)"
|
||||
showSeparator: false
|
||||
control: ModernComboBox {
|
||||
width: 140
|
||||
model: ["int8", "float16", "float32"]
|
||||
currentIndex: model.indexOf(ui.getSetting("compute_type"))
|
||||
onActivated: ui.setSetting("compute_type", currentText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsSection {
|
||||
title: "Advanced Decoding"
|
||||
Layout.margins: 32
|
||||
Layout.topMargin: 0
|
||||
|
||||
content: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Beam Size"
|
||||
description: "Search width (Higher = Better Accuracy, Slower)"
|
||||
control: ModernSlider {
|
||||
Layout.preferredWidth: 200
|
||||
from: 1; to: 10
|
||||
value: ui.getSetting("beam_size")
|
||||
onMoved: ui.setSetting("beam_size", value)
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "VAD Filter"
|
||||
description: "Skip silent audio segments (Speeds up processing)"
|
||||
control: ModernSwitch {
|
||||
checked: ui.getSetting("vad_filter")
|
||||
onToggled: ui.setSetting("vad_filter", checked)
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Hallucination Check"
|
||||
description: "Prevent repetitive text loops (No Repeat N-Gram)"
|
||||
control: ModernSwitch {
|
||||
checked: ui.getSetting("no_repeat_ngram_size") > 0
|
||||
onToggled: ui.setSetting("no_repeat_ngram_size", checked ? 3 : 0)
|
||||
}
|
||||
}
|
||||
|
||||
ModernSettingsItem {
|
||||
label: "Context History"
|
||||
description: "Use previous text to improve coherence"
|
||||
showSeparator: false
|
||||
control: ModernSwitch {
|
||||
checked: ui.getSetting("condition_on_previous_text")
|
||||
onToggled: ui.setSetting("condition_on_previous_text", checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- TAB: DEBUG ---
|
||||
ScrollView {
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 16
|
||||
anchors.margins: 32
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
Text { text: "System Diagnostics"; color: SettingsStyle.textPrimary; font.family: mainFont; font.pixelSize: 24; font.bold: true }
|
||||
Text { text: "Live performance and logs"; color: SettingsStyle.textSecondary; font.family: mainFont; font.pixelSize: 14 }
|
||||
}
|
||||
|
||||
// --- PERFORMANCE STATS ---
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
spacing: 16
|
||||
|
||||
StatBox { label: "APP CPU"; value: ui.appCpu; unit: "%"; accent: "#00f2ff" }
|
||||
StatBox { label: "APP RAM"; value: ui.appRamMb; unit: "MB"; accent: "#bd93f9" }
|
||||
StatBox { label: "GPU VRAM"; value: ui.appVramMb; unit: "MB"; accent: "#ff79c6" }
|
||||
StatBox { label: "GPU LOAD"; value: ui.appVramPercent; unit: "%"; accent: "#ff5555" }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 300
|
||||
Layout.margins: 32
|
||||
Layout.topMargin: 0
|
||||
color: "#0d0d10"
|
||||
radius: 8
|
||||
border.color: SettingsStyle.borderSubtle
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
||||
|
||||
TextArea {
|
||||
id: logArea
|
||||
text: ui.allLogs
|
||||
readOnly: true
|
||||
color: "#cccccc"
|
||||
font.family: "JetBrains Mono"
|
||||
font.pixelSize: 11
|
||||
wrapMode: TextArea.Wrap
|
||||
background: null
|
||||
selectByMouse: true
|
||||
|
||||
Connections {
|
||||
target: ui
|
||||
function onLogAppended(line) {
|
||||
logArea.append(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/ui/qml/SettingsStyle.qml
Normal file
@@ -0,0 +1,25 @@
|
||||
import QtQuick
|
||||
|
||||
pragma Singleton
|
||||
|
||||
QtObject {
|
||||
// Colors
|
||||
readonly property color background: "#F2121212" // Deep Obsidian with 95% opacity
|
||||
readonly property color surfaceCard: "#1A1A1A" // Layer 1
|
||||
readonly property color surfaceHover: "#2A2A2A" // Layer 2 (Lighter for better contrast)
|
||||
readonly property color borderSubtle: Qt.rgba(1, 1, 1, 0.08)
|
||||
|
||||
readonly property color textPrimary: "#FAFAFA" // Brighter white
|
||||
readonly property color textSecondary: "#999999"
|
||||
|
||||
readonly property color accentPurple: "#7000FF"
|
||||
readonly property color accentCyan: "#00F2FF"
|
||||
|
||||
// Configurable active accent
|
||||
property color accent: accentPurple
|
||||
|
||||
// Dimensions
|
||||
readonly property int cardRadius: 16
|
||||
readonly property int itemRadius: 8
|
||||
readonly property int itemHeight: 60 // Even taller for more breathing room
|
||||
}
|
||||
56
src/ui/qml/crt.frag
Normal file
@@ -0,0 +1,56 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
float time;
|
||||
};
|
||||
|
||||
layout(binding = 1) uniform sampler2D source;
|
||||
|
||||
void main() {
|
||||
vec2 uv = qt_TexCoord0;
|
||||
|
||||
// CRT curvature distortion
|
||||
vec2 crtUV = uv - 0.5;
|
||||
float curvature = 0.03;
|
||||
crtUV *= 1.0 + curvature * (crtUV.x * crtUV.x + crtUV.y * crtUV.y);
|
||||
crtUV += 0.5;
|
||||
|
||||
// Clamp to avoid sampling outside
|
||||
if (crtUV.x < 0.0 || crtUV.x > 1.0 || crtUV.y < 0.0 || crtUV.y > 1.0) {
|
||||
fragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sample the texture
|
||||
vec4 color = texture(source, crtUV);
|
||||
|
||||
// Scanlines effect (fine and subtle)
|
||||
float scanlineIntensity = 0.04;
|
||||
float scanline = sin(crtUV.y * 2400.0) * scanlineIntensity;
|
||||
color.rgb -= scanline;
|
||||
|
||||
// RGB color separation (chromatic aberration)
|
||||
float aberration = 0.001;
|
||||
float r = texture(source, crtUV + vec2(aberration, 0.0)).r;
|
||||
float g = color.g;
|
||||
float b = texture(source, crtUV - vec2(aberration, 0.0)).b;
|
||||
color.rgb = vec3(r, g, b);
|
||||
|
||||
// Subtle vignette
|
||||
float vignette = 1.0 - 0.3 * length(crtUV - 0.5);
|
||||
color.rgb *= vignette;
|
||||
|
||||
// Slight green/cyan phosphor tint
|
||||
color.rgb *= vec3(0.95, 1.0, 0.98);
|
||||
|
||||
// Flicker (very subtle)
|
||||
float flicker = 0.98 + 0.02 * sin(time * 15.0);
|
||||
color.rgb *= flicker;
|
||||
|
||||
fragColor = color * qt_Opacity;
|
||||
}
|
||||
BIN
src/ui/qml/crt.qsb
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-Bold.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-BoldItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-ExtraBold.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-ExtraBoldItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-ExtraLight.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-ExtraLightItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-Italic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-Light.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-LightItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-Medium.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-MediumItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-Regular.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-SemiBold.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-SemiBoldItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-Thin.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMono-ThinItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-Bold.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-BoldItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-ExtraBold.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-ExtraBoldItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-ExtraLight.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-ExtraLightItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-Italic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-Light.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-LightItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-Medium.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-MediumItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-Regular.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-SemiBold.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-SemiBoldItalic.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-Thin.ttf
Normal file
BIN
src/ui/qml/fonts/ttf/JetBrainsMonoNL-ThinItalic.ttf
Normal file
BIN
src/ui/qml/fonts/variable/JetBrainsMono-Italic[wght].ttf
Normal file
BIN
src/ui/qml/fonts/variable/JetBrainsMono[wght].ttf
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-Bold.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-BoldItalic.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-ExtraBold.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-ExtraBoldItalic.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-ExtraLight.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-ExtraLightItalic.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-Italic.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-Light.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-LightItalic.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-Medium.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-MediumItalic.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-Regular.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-SemiBold.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-SemiBoldItalic.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-Thin.woff2
Normal file
BIN
src/ui/qml/fonts/webfonts/JetBrainsMono-ThinItalic.woff2
Normal file
50
src/ui/qml/glass.frag
Normal file
@@ -0,0 +1,50 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
float time;
|
||||
float aberration; // 0.0 to 1.0, controlled by Audio Amplitude
|
||||
};
|
||||
|
||||
float rand(vec2 co) {
|
||||
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// 1. Calculate Distortion Offset based on Amplitude (aberration)
|
||||
// We warp the UVs slightly away from center
|
||||
vec2 uv = qt_TexCoord0;
|
||||
vec2 dist = uv - 0.5;
|
||||
|
||||
// 2. Chromatic Aberration
|
||||
// Red Channel shifts OUT
|
||||
// Blue Channel shifts IN
|
||||
float strength = aberration * 0.02; // Max shift 2% of texture size
|
||||
|
||||
vec2 rUV = uv + (dist * strength);
|
||||
vec2 bUV = uv - (dist * strength);
|
||||
|
||||
// Sample texture? We don't have a texture input (source is empty Item), we are generating visuals.
|
||||
// Wait, ShaderEffect usually works on sourceItem.
|
||||
// Here we are generating NOISE on top of a gradient.
|
||||
// So we apply Aberration to the NOISE function?
|
||||
// Or do we want to aberrate the pixels UNDERNEATH?
|
||||
// ShaderEffect with no source property renders purely procedural content.
|
||||
|
||||
// Let's create layered procedural noise with channel offsets
|
||||
float nR = rand(rUV + vec2(time * 0.01, 0.0));
|
||||
float nG = rand(uv + vec2(time * 0.01, 0.0)); // Green is anchor
|
||||
float nB = rand(bUV + vec2(time * 0.01, 0.0));
|
||||
|
||||
// Also modulate alpha by aberration - higher volume = more intense grain?
|
||||
// Or maybe just pure glitch.
|
||||
|
||||
vec4 grainColor = vec4(nR, nG, nB, 1.0);
|
||||
|
||||
// Mix it with opacity
|
||||
fragColor = grainColor * qt_Opacity;
|
||||
}
|
||||
BIN
src/ui/qml/glass.qsb
Normal file
40
src/ui/qml/glow.frag
Normal file
@@ -0,0 +1,40 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
float time;
|
||||
float intensity; // 0.0 to 1.0, controlled by Audio Amplitude
|
||||
};
|
||||
|
||||
float rand(vec2 co) {
|
||||
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = qt_TexCoord0;
|
||||
|
||||
// 1. Base Noise (subtle grain)
|
||||
float noise = rand(uv + vec2(time * 0.01, 0.0));
|
||||
|
||||
// 2. Radial Glow (bright center, fading to edges)
|
||||
vec2 center = vec2(0.5, 0.5);
|
||||
float dist = distance(uv, center);
|
||||
|
||||
// Glow strength based on intensity (audio amplitude)
|
||||
// Higher intensity = brighter, wider glow
|
||||
float glowRadius = 0.3 + (intensity * 0.4); // Radius grows with volume
|
||||
float glow = 1.0 - smoothstep(0.0, glowRadius, dist);
|
||||
glow = pow(glow, 2.0); // Sharpen the falloff
|
||||
|
||||
// 3. Color the glow (warm white/cyan tint)
|
||||
vec3 glowColor = vec3(0.8, 0.9, 1.0); // Slight cyan tint
|
||||
|
||||
// 4. Combine noise + glow
|
||||
vec3 finalColor = mix(vec3(noise), glowColor, glow * intensity);
|
||||
|
||||
fragColor = vec4(finalColor, 1.0) * qt_Opacity;
|
||||
}
|
||||
BIN
src/ui/qml/glow.qsb
Normal file
87
src/ui/qml/gradient_blobs.frag
Normal file
@@ -0,0 +1,87 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
float time;
|
||||
};
|
||||
|
||||
// Smooth noise function
|
||||
float noise(vec2 p) {
|
||||
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
// Smooth interpolation
|
||||
float smoothNoise(vec2 p) {
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
f = f * f * (3.0 - 2.0 * f); // Smoothstep
|
||||
|
||||
float a = noise(i);
|
||||
float b = noise(i + vec2(1.0, 0.0));
|
||||
float c = noise(i + vec2(0.0, 1.0));
|
||||
float d = noise(i + vec2(1.0, 1.0));
|
||||
|
||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
||||
}
|
||||
|
||||
// Fractal Brownian Motion
|
||||
float fbm(vec2 p) {
|
||||
float value = 0.0;
|
||||
float amplitude = 0.5;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
value += amplitude * smoothNoise(p);
|
||||
p *= 2.0;
|
||||
amplitude *= 0.5;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// HSV to RGB conversion
|
||||
vec3 hsv2rgb(vec3 c) {
|
||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = qt_TexCoord0;
|
||||
float t = time * 0.05;
|
||||
|
||||
// Multiple moving blobs
|
||||
vec2 p1 = uv * 3.0 + vec2(t * 0.3, t * 0.2);
|
||||
vec2 p2 = uv * 2.5 + vec2(-t * 0.4, t * 0.3);
|
||||
vec2 p3 = uv * 4.0 + vec2(t * 0.2, -t * 0.25);
|
||||
|
||||
float blob1 = fbm(p1);
|
||||
float blob2 = fbm(p2);
|
||||
float blob3 = fbm(p3);
|
||||
|
||||
// Combine blobs
|
||||
float combined = (blob1 + blob2 + blob3) / 3.0;
|
||||
|
||||
// Live hue shifting - slowly cycle through hues over time
|
||||
float hueShift = time * 0.02; // Slow hue rotation
|
||||
|
||||
// Create colors using HSV with shifting hue
|
||||
vec3 color1 = hsv2rgb(vec3(fract(0.75 + hueShift), 0.6, 0.35)); // Purple range
|
||||
vec3 color2 = hsv2rgb(vec3(fract(0.85 + hueShift), 0.7, 0.35)); // Magenta range
|
||||
vec3 color3 = hsv2rgb(vec3(fract(0.55 + hueShift), 0.7, 0.35)); // Blue range
|
||||
vec3 color4 = hsv2rgb(vec3(fract(0.08 + hueShift), 0.7, 0.35)); // Orange range
|
||||
vec3 color5 = hsv2rgb(vec3(fract(0.50 + hueShift), 0.6, 0.30)); // Teal range
|
||||
|
||||
// Mix colors based on blob values for visible multi-color effect
|
||||
vec3 color = mix(color1, color2, blob1);
|
||||
color = mix(color, color3, blob2);
|
||||
color = mix(color, color4, blob3);
|
||||
color = mix(color, color5, combined);
|
||||
|
||||
// Moderate brightness for visible but dark background accent
|
||||
float brightness = 0.25 + combined * 0.25;
|
||||
color *= brightness;
|
||||
|
||||
fragColor = vec4(color, qt_Opacity);
|
||||
}
|
||||
BIN
src/ui/qml/gradient_blobs.qsb
Normal file
BIN
src/ui/qml/icon.ico
Normal file
|
After Width: | Height: | Size: 73 KiB |
1
src/ui/qml/microphone.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2024 Fonticons, Inc. --><path fill="#ffffff" d="M192 0C139 0 96 43 96 96l0 160c0 53 43 96 96 96s96-43 96-96l0-160c0-53-43-96-96-96zM64 216c0-13.3-10.7-24-24-24s-24 10.7-24 24l0 40c0 89.1 66.2 162.7 152 174.4l0 33.6-48 0c-13.3 0-24 10.7-24 24s10.7 24 24 24l72 0 72 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-48 0 0-33.6c85.8-11.7 152-85.3 152-174.4l0-40c0-13.3-10.7-24-24-24s-24 10.7-24 24l0 40c0 70.7-57.3 128-128 128s-128-57.3-128-128l0-40z"/></svg>
|
||||
|
After Width: | Height: | Size: 695 B |
25
src/ui/qml/noise.frag
Normal file
@@ -0,0 +1,25 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
float time;
|
||||
};
|
||||
|
||||
// High-quality pseudo-random function
|
||||
float rand(vec2 co) {
|
||||
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Dynamic Noise based on Time
|
||||
// We add 'time' to the coordinate to animate the grain
|
||||
float noise = rand(qt_TexCoord0 + vec2(time * 0.01, time * 0.02));
|
||||
|
||||
// Output grayscale noise with alpha modulation
|
||||
// We want white noise, applied with qt_Opacity
|
||||
fragColor = vec4(noise, noise, noise, 1.0) * qt_Opacity;
|
||||
}
|
||||
BIN
src/ui/qml/noise.qsb
Normal file
3
src/ui/qml/qmldir
Normal file
@@ -0,0 +1,3 @@
|
||||
singleton SettingsStyle 1.0 SettingsStyle.qml
|
||||
ModernSettingsSection 1.0 ModernSettingsSection.qml
|
||||
ModernSettingsItem 1.0 ModernSettingsItem.qml
|
||||
65
src/ui/qml/rainbow_wave.frag
Normal file
@@ -0,0 +1,65 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
float time;
|
||||
float amplitude; // Audio amplitude 0.0 to 1.0
|
||||
};
|
||||
|
||||
// Smooth rainbow gradient
|
||||
vec3 rainbow(float t) {
|
||||
t = fract(t);
|
||||
float r = abs(t * 6.0 - 3.0) - 1.0;
|
||||
float g = 2.0 - abs(t * 6.0 - 2.0);
|
||||
float b = 2.0 - abs(t * 6.0 - 4.0);
|
||||
return clamp(vec3(r, g, b), 0.0, 1.0);
|
||||
}
|
||||
|
||||
// Smooth waveform function
|
||||
float wave(float x, float t, float amp) {
|
||||
// Multiple sine waves for organic movement
|
||||
float w1 = sin(x * 3.0 + t * 2.0) * 0.3;
|
||||
float w2 = sin(x * 5.0 - t * 1.5) * 0.2;
|
||||
float w3 = sin(x * 7.0 + t * 3.0) * 0.1;
|
||||
return (w1 + w2 + w3) * amp;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = qt_TexCoord0;
|
||||
vec2 p = uv * 2.0 - 1.0; // Center coordinates
|
||||
|
||||
float t = time * 0.1;
|
||||
float amp = amplitude * 1.92 + 0.1; // Reduced by another 20% from 2.4 to 1.92
|
||||
|
||||
// Calculate distance to waveform (dual waves, mirrored)
|
||||
float wave1 = wave(uv.x * 3.14159, t, amp);
|
||||
float wave2 = -wave1; // Mirror
|
||||
|
||||
float dist1 = abs(p.y - wave1);
|
||||
float dist2 = abs(p.y - wave2);
|
||||
float dist = min(dist1, dist2);
|
||||
|
||||
// Wide cinematic glow
|
||||
float glow = 1.0 / (dist * 20.0 + 1.0);
|
||||
glow = pow(glow, 1.5); // Sharpen the glow
|
||||
|
||||
// Rainbow color based on position and time
|
||||
float colorPos = uv.x + t * 0.2;
|
||||
vec3 color = rainbow(colorPos);
|
||||
|
||||
// EVEN longer fade zones for maximum gradual edges (0-45% and 55-100%)
|
||||
float fadePos = uv.x;
|
||||
float leftFade = smoothstep(0.0, 0.45, fadePos);
|
||||
float rightFade = smoothstep(1.0, 0.55, fadePos);
|
||||
float fade = leftFade * rightFade;
|
||||
|
||||
// Combine everything
|
||||
vec3 finalColor = color * glow * fade;
|
||||
float alpha = glow * fade * qt_Opacity;
|
||||
|
||||
fragColor = vec4(finalColor, alpha);
|
||||
}
|
||||
BIN
src/ui/qml/rainbow_wave.qsb
Normal file
BIN
src/ui/qml/settings.png
Normal file
|
After Width: | Height: | Size: 492 KiB |
1
src/ui/qml/settings.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2024 Fonticons, Inc. --><path fill="#ffffff" d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/ui/qml/smart_toy.png
Normal file
|
After Width: | Height: | Size: 490 KiB |
1
src/ui/qml/smart_toy.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2024 Fonticons, Inc. --><path fill="#ffffff" d="M184 0c30.9 0 56 25.1 56 56l0 400c0 30.9-25.1 56-56 56c-28.9 0-52.7-21.9-55.7-50.1c-5.2 1.4-10.7 2.1-16.3 2.1c-35.3 0-64-28.7-64-64c0-7.4 1.3-14.6 3.6-21.2C21.4 367.4 0 338.2 0 304c0-31.9 18.7-59.5 45.8-72.3C37.1 220.8 32 207 32 192c0-30.7 21.6-56.3 50.4-62.6C80.8 123.9 80 118 80 112c0-29.9 20.6-55.1 48.3-62.1C131.3 21.9 155.1 0 184 0zM328 0c28.9 0 52.6 21.9 55.7 49.9c27.8 7 48.3 32.1 48.3 62.1c0 6-.8 11.9-2.4 17.4c28.8 6.2 50.4 31.9 50.4 62.6c0 15-5.1 28.8-13.8 39.7C493.3 244.5 512 272.1 512 304c0 34.2-21.4 63.4-51.6 74.8c2.3 6.6 3.6 13.8 3.6 21.2c0 35.3-28.7 64-64 64c-5.6 0-11.1-.7-16.3-2.1c-3 28.2-26.8 50.1-55.7 50.1c-30.9 0-56-25.1-56-56l0-400c0-30.9 25.1-56 56-56z"/></svg>
|
||||
|
After Width: | Height: | Size: 983 B |
1
src/ui/qml/terminal.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="#ffffff" d="M257.981 272.971L63.638 467.314c-9.373 9.373-24.569 9.373-33.941 0L7.029 444.647c-9.357-9.357-9.375-24.522-.04-33.901L161.011 256 6.99 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L257.981 239.03c9.373 9.372 9.373 24.568 0 33.941zM640 456v-32c0-13.255-10.745-24-24-24H312c-13.255 0-24 10.745-24 24v32c0 13.255 10.745 24 24 24h304c13.255 0 24-10.745 24-24z"/></svg>
|
||||
|
After Width: | Height: | Size: 676 B |
BIN
src/ui/qml/visibility.png
Normal file
|
After Width: | Height: | Size: 464 KiB |
1
src/ui/qml/visibility.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2024 Fonticons, Inc. --><path fill="#ffffff" d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM144 256a144 144 0 1 1 288 0 144 144 0 1 1 -288 0zm144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3z"/></svg>
|
||||
|
After Width: | Height: | Size: 878 B |