Initial commit of WhisperVoice

This commit is contained in:
Your Name
2026-01-24 17:03:52 +02:00
commit 9ff0e8d108
118 changed files with 6102 additions and 0 deletions

10
src/ui/qml/AUTHORS.txt Normal file
View 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
View 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 } }
}
}
}

Binary file not shown.

186
src/ui/qml/Loader.qml Normal file
View 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
}
}
}

View 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 }
}
}
}

View 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
}
}

View 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
}
}

View 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"
}
}
}
}
}
}

View 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
}
}

View 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
}
}

View 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
View 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
View 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
View 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)
}
}
}
}
}
}
}
}
}
}
}
}

View 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
View 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

50
src/ui/qml/glass.frag Normal file
View 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

Binary file not shown.

40
src/ui/qml/glow.frag Normal file
View 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

Binary file not shown.

View 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);
}

Binary file not shown.

BIN
src/ui/qml/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View 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
View 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

Binary file not shown.

3
src/ui/qml/qmldir Normal file
View File

@@ -0,0 +1,3 @@
singleton SettingsStyle 1.0 SettingsStyle.qml
ModernSettingsSection 1.0 ModernSettingsSection.qml
ModernSettingsItem 1.0 ModernSettingsItem.qml

View 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

Binary file not shown.

BIN
src/ui/qml/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

1
src/ui/qml/settings.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

1
src/ui/qml/smart_toy.svg Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

View 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