forked from Shinonome/dots-hyprland
Merge branch 'end-4:main' into keybinds-settings
This commit is contained in:
@@ -41,7 +41,7 @@ bind = Shift+Super+Alt, Slash, exec, qs -p ~/.config/quickshell/$qsConfig/welcom
|
||||
|
||||
bindle=, XF86MonBrightnessUp, exec, qs -c $qsConfig ipc call brightness increment || brightnessctl s 5%+ # [hidden]
|
||||
bindle=, XF86MonBrightnessDown, exec, qs -c $qsConfig ipc call brightness decrement || brightnessctl s 5%- # [hidden]
|
||||
bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%+ # [hidden]
|
||||
bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%+ -l 1.5# [hidden]
|
||||
bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%- # [hidden]
|
||||
|
||||
bindl = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_SINK@ toggle # [hidden]
|
||||
|
||||
@@ -144,6 +144,7 @@ layerrule = animation slide bottom, quickshell:osk
|
||||
layerrule = noanim, quickshell:polkit
|
||||
layerrule = xray 0, quickshell:popup # No weird color for bar tooltips (this in theory should suffice)
|
||||
layerrule = ignorealpha 1, quickshell:popup # No weird color for bar tooltips (but somehow this is necessary)
|
||||
layerrule = ignorealpha 1, quickshell:mediaControls # Same as above
|
||||
layerrule = noanim, quickshell:regionSelector
|
||||
layerrule = noanim, quickshell:screenshot
|
||||
layerrule = blur, quickshell:session
|
||||
|
||||
@@ -7,13 +7,16 @@ if pgrep -f 'geoclue-2.0/demos/agent' > /dev/null; then
|
||||
fi
|
||||
|
||||
# List of known possible GeoClue agent paths
|
||||
AGENT_PATHS="
|
||||
/usr/libexec/geoclue-2.0/demos/agent
|
||||
/usr/lib/geoclue-2.0/demos/agent
|
||||
"
|
||||
AGENT_PATHS=(
|
||||
/usr/libexec/geoclue-2.0/demos/agent
|
||||
/usr/lib/geoclue-2.0/demos/agent
|
||||
"$HOME/.nix-profile/libexec/geoclue-2.0/demos/agent"
|
||||
"$HOME/.nix-profile/lib/geoclue-2.0/demos/agent"
|
||||
/run/current-system/sw/libexec/geoclue-2.0/demos/agent
|
||||
)
|
||||
|
||||
# Find the first valid agent path
|
||||
for path in $AGENT_PATHS; do
|
||||
for path in "${AGENT_PATHS[@]}"; do
|
||||
if [ -x "$path" ]; then
|
||||
echo "Starting GeoClue agent from: $path"
|
||||
"$path" & # starts in the background
|
||||
|
||||
@@ -334,7 +334,6 @@ Variants {
|
||||
font {
|
||||
pixelSize: Appearance.font.pixelSize.normal
|
||||
weight: 350
|
||||
italic: true
|
||||
}
|
||||
color: bgRoot.colText
|
||||
style: Text.Raised
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.models
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import QtQuick
|
||||
@@ -163,12 +164,12 @@ Item {
|
||||
horizontalCenter: vertical ? parent.horizontalCenter : undefined
|
||||
}
|
||||
|
||||
// idx1 is the "leading" indicator position, idx2 is the "following" one
|
||||
// The former animates faster than the latter, see the NumberAnimations below
|
||||
property real idx1: workspaceIndexInGroup
|
||||
property real idx2: workspaceIndexInGroup
|
||||
property real indicatorPosition: Math.min(idx1, idx2) * workspaceButtonWidth + root.activeWorkspaceMargin
|
||||
property real indicatorLength: Math.abs(idx1 - idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2
|
||||
AnimatedTabIndexPair {
|
||||
id: idxPair
|
||||
index: root.workspaceIndexInGroup
|
||||
}
|
||||
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * workspaceButtonWidth + root.activeWorkspaceMargin
|
||||
property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2
|
||||
property real indicatorThickness: workspaceButtonWidth - root.activeWorkspaceMargin * 2
|
||||
|
||||
x: root.vertical ? null : indicatorPosition
|
||||
@@ -176,18 +177,6 @@ Item {
|
||||
y: root.vertical ? indicatorPosition : null
|
||||
implicitHeight: root.vertical ? indicatorLength : indicatorThickness
|
||||
|
||||
Behavior on idx1 {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
easing.type: Easing.OutSine
|
||||
}
|
||||
}
|
||||
Behavior on idx2 {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutSine
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Workspaces - numbers
|
||||
|
||||
@@ -31,7 +31,6 @@ Scope { // Scope
|
||||
sourceComponent: PanelWindow { // Window
|
||||
id: cheatsheetRoot
|
||||
visible: cheatsheetLoader.active
|
||||
property int selectedTab: 0
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
@@ -76,7 +75,7 @@ Scope { // Scope
|
||||
border.width: 1
|
||||
border.color: Appearance.colors.colLayer0Border
|
||||
radius: Appearance.rounding.windowRounding
|
||||
property real padding: 30
|
||||
property real padding: 20
|
||||
implicitWidth: cheatsheetColumnLayout.implicitWidth + padding * 2
|
||||
implicitHeight: cheatsheetColumnLayout.implicitHeight + padding * 2
|
||||
|
||||
@@ -86,16 +85,16 @@ Scope { // Scope
|
||||
}
|
||||
if (event.modifiers === Qt.ControlModifier) {
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
cheatsheetRoot.selectedTab = Math.min(cheatsheetRoot.selectedTab + 1, root.tabButtonList.length - 1);
|
||||
tabBar.incrementCurrentIndex();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_PageUp) {
|
||||
cheatsheetRoot.selectedTab = Math.max(cheatsheetRoot.selectedTab - 1, 0);
|
||||
tabBar.decrementCurrentIndex();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab + 1) % root.tabButtonList.length;
|
||||
tabBar.setCurrentIndex((tabBar.currentIndex + 1) % root.tabButtonList.length);
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
cheatsheetRoot.selectedTab = (cheatsheetRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
|
||||
tabBar.setCurrentIndex((tabBar.currentIndex - 1 + root.tabButtonList.length) % root.tabButtonList.length);
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
@@ -129,23 +128,15 @@ Scope { // Scope
|
||||
ColumnLayout { // Real content
|
||||
id: cheatsheetColumnLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: 20
|
||||
spacing: 10
|
||||
|
||||
StyledText {
|
||||
id: cheatsheetTitle
|
||||
Toolbar {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
font {
|
||||
family: Appearance.font.family.title
|
||||
pixelSize: Appearance.font.pixelSize.title
|
||||
variableAxes: Appearance.font.variableAxes.title
|
||||
}
|
||||
text: Translation.tr("Cheat sheet")
|
||||
}
|
||||
PrimaryTabBar { // Tab strip
|
||||
id: tabBar
|
||||
tabButtonList: root.tabButtonList
|
||||
Synchronizer on currentIndex {
|
||||
property alias source: cheatsheetRoot.selectedTab
|
||||
enableShadow: false
|
||||
ToolbarTabBar {
|
||||
id: tabBar
|
||||
tabButtonList: root.tabButtonList
|
||||
currentIndex: swipeView.currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,26 +145,11 @@ Scope { // Scope
|
||||
Layout.topMargin: 5
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
currentIndex: tabBar.currentIndex
|
||||
spacing: 10
|
||||
|
||||
Behavior on implicitWidth {
|
||||
id: contentWidthBehavior
|
||||
enabled: false
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on implicitHeight {
|
||||
id: contentHeightBehavior
|
||||
enabled: false
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
currentIndex: cheatsheetRoot.selectedTab
|
||||
onCurrentIndexChanged: {
|
||||
contentWidthBehavior.enabled = true;
|
||||
contentHeightBehavior.enabled = true;
|
||||
tabBar.enableIndicatorAnimation = true;
|
||||
cheatsheetRoot.selectedTab = currentIndex;
|
||||
}
|
||||
implicitWidth: Math.max.apply(null, contentChildren.map(child => child.implicitWidth || 0))
|
||||
implicitHeight: Math.max.apply(null, contentChildren.map(child => child.implicitHeight || 0))
|
||||
|
||||
clip: true
|
||||
layer.enabled: true
|
||||
|
||||
@@ -11,6 +11,7 @@ Item {
|
||||
|
||||
Column {
|
||||
id: mainLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: root.spacing
|
||||
|
||||
Repeater { // Main table rows
|
||||
|
||||
@@ -7,8 +7,8 @@ RippleButton {
|
||||
id: root
|
||||
required property var element
|
||||
opacity: element.type != "empty" ? 1 : 0
|
||||
implicitHeight: 60
|
||||
implicitWidth: 60
|
||||
implicitHeight: 70
|
||||
implicitWidth: 70
|
||||
colBackground: Appearance.colors.colLayer2
|
||||
buttonRadius: Appearance.rounding.small
|
||||
|
||||
|
||||
@@ -373,6 +373,7 @@ Singleton {
|
||||
property real scale: 0.18 // Relative to screen size
|
||||
property real rows: 2
|
||||
property real columns: 5
|
||||
property bool centerIcons: true
|
||||
}
|
||||
|
||||
property JsonObject regionSelector: JsonObject {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import QtQuick
|
||||
|
||||
// idx1 is the "leading" indicator position, idx2 is the "following" one
|
||||
// The former animates faster than the latter, see the NumberAnimations below
|
||||
QtObject {
|
||||
id: root
|
||||
required property int index
|
||||
|
||||
property real idx1: index
|
||||
property real idx2: index
|
||||
property int idx1Duration: 100
|
||||
property int idx2Duration: 300
|
||||
|
||||
Behavior on idx1 {
|
||||
NumberAnimation {
|
||||
duration: root.idx1Duration
|
||||
easing.type: Easing.OutSine
|
||||
}
|
||||
}
|
||||
Behavior on idx2 {
|
||||
NumberAnimation {
|
||||
duration: root.idx2Duration
|
||||
easing.type: Easing.OutSine
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ ToolbarButton {
|
||||
|
||||
contentItem: Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
spacing: 4
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.synchronizer
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
required property var tabButtonList // Something like [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}]
|
||||
property int currentIndex
|
||||
property bool enableIndicatorAnimation: false
|
||||
property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F"
|
||||
property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0"
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
enableIndicatorAnimation = true
|
||||
}
|
||||
|
||||
property bool centerTabBar: parent.width > 500
|
||||
Layout.fillWidth: !centerTabBar
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: Math.max(tabBar.implicitWidth, 600)
|
||||
|
||||
TabBar {
|
||||
id: tabBar
|
||||
Layout.fillWidth: true
|
||||
Synchronizer on currentIndex {
|
||||
property alias source: root.currentIndex
|
||||
}
|
||||
|
||||
background: Item {
|
||||
WheelHandler {
|
||||
onWheel: (event) => {
|
||||
if (event.angleDelta.y < 0)
|
||||
tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1)
|
||||
else if (event.angleDelta.y > 0)
|
||||
tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0)
|
||||
}
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.tabButtonList
|
||||
delegate: PrimaryTabButton {
|
||||
selected: (index == root.currentIndex)
|
||||
buttonText: modelData.name
|
||||
buttonIcon: modelData.icon
|
||||
minimumWidth: 160
|
||||
onClicked: root.currentIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { // Tab indicator
|
||||
id: tabIndicator
|
||||
Layout.fillWidth: true
|
||||
height: 3
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
property int tabCount: root.tabButtonList.length
|
||||
property real fullTabSize: root.width / tabCount;
|
||||
property real targetWidth: tabBar.contentItem?.children[0]?.children[tabBar.currentIndex]?.tabContentWidth ?? 0
|
||||
|
||||
implicitWidth: targetWidth
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2
|
||||
|
||||
color: root.colIndicator
|
||||
radius: Appearance?.rounding.full ?? 9999
|
||||
|
||||
Behavior on x {
|
||||
animation: Appearance?.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
animation: Appearance?.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // Tabbar bottom border
|
||||
id: tabBarBottomBorder
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 1
|
||||
color: root.colBorder
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
TabButton {
|
||||
id: button
|
||||
property string buttonText
|
||||
property string buttonIcon
|
||||
property real minimumWidth: 110
|
||||
property bool selected: false
|
||||
property int tabContentWidth: contentItem.children[0].implicitWidth
|
||||
property int rippleDuration: 1200
|
||||
height: buttonBackground.height
|
||||
implicitWidth: Math.max(tabContentWidth, buttonBackground.implicitWidth, minimumWidth)
|
||||
|
||||
property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent"
|
||||
property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED"
|
||||
property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
|
||||
property color colActive: Appearance?.colors.colPrimary ?? "#65558F"
|
||||
property color colInactive: Appearance?.colors.colOnLayer1 ?? "#45464F"
|
||||
|
||||
component RippleAnim: NumberAnimation {
|
||||
duration: rippleDuration
|
||||
easing.type: Appearance?.animation.elementMoveEnter.type
|
||||
easing.bezierCurve: Appearance?.animationCurves.standardDecel
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: (event) => {
|
||||
button.click() // Because the MouseArea already consumed the event
|
||||
const {x,y} = event
|
||||
const stateY = buttonBackground.y;
|
||||
rippleAnim.x = x;
|
||||
rippleAnim.y = y - stateY;
|
||||
|
||||
const dist = (ox,oy) => ox*ox + oy*oy
|
||||
const stateEndY = stateY + buttonBackground.height
|
||||
rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)))
|
||||
|
||||
rippleFadeAnim.complete();
|
||||
rippleAnim.restart();
|
||||
}
|
||||
onReleased: (event) => {
|
||||
rippleFadeAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
RippleAnim {
|
||||
id: rippleFadeAnim
|
||||
duration: rippleDuration * 2
|
||||
target: ripple
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: rippleAnim
|
||||
|
||||
property real x
|
||||
property real y
|
||||
property real radius
|
||||
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "x"
|
||||
value: rippleAnim.x
|
||||
}
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "y"
|
||||
value: rippleAnim.y
|
||||
}
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "opacity"
|
||||
value: 1
|
||||
}
|
||||
ParallelAnimation {
|
||||
RippleAnim {
|
||||
target: ripple
|
||||
properties: "implicitWidth,implicitHeight"
|
||||
from: 0
|
||||
to: rippleAnim.radius * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: buttonBackground
|
||||
radius: Appearance?.rounding.small
|
||||
implicitHeight: 50
|
||||
color: (button.hovered ? button.colBackgroundHover : button.colBackground)
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: buttonBackground.width
|
||||
height: buttonBackground.height
|
||||
radius: buttonBackground.radius
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
|
||||
Item {
|
||||
id: ripple
|
||||
width: ripple.implicitWidth
|
||||
height: ripple.implicitHeight
|
||||
opacity: 0
|
||||
|
||||
property real implicitWidth: 0
|
||||
property real implicitHeight: 0
|
||||
visible: width > 0 && height > 0
|
||||
|
||||
Behavior on opacity {
|
||||
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
|
||||
RadialGradient {
|
||||
anchors.fill: parent
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: button.colRipple }
|
||||
GradientStop { position: 0.3; color: button.colRipple }
|
||||
GradientStop { position: 0.5 ; color: Qt.rgba(button.colRipple.r, button.colRipple.g, button.colRipple.b, 0) }
|
||||
}
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
x: -ripple.width / 2
|
||||
y: -ripple.height / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
anchors.centerIn: buttonBackground
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
MaterialSymbol {
|
||||
visible: buttonIcon?.length > 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: buttonIcon
|
||||
iconSize: Appearance?.font.pixelSize.hugeass ?? 25
|
||||
fill: selected ? 1 : 0
|
||||
color: selected ? button.colActive : button.colInactive
|
||||
Behavior on color {
|
||||
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
id: buttonTextWidget
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: Appearance?.font.pixelSize.small
|
||||
color: selected ? button.colActive : button.colInactive
|
||||
text: buttonText
|
||||
Behavior on color {
|
||||
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import qs.modules.common.widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool enableShadow: true
|
||||
property real padding: 8
|
||||
property alias colBackground: background.color
|
||||
property alias spacing: toolbarLayout.spacing
|
||||
@@ -18,15 +19,20 @@ Item {
|
||||
implicitHeight: background.implicitHeight
|
||||
property alias radius: background.radius
|
||||
|
||||
StyledRectangularShadow {
|
||||
target: background
|
||||
Loader {
|
||||
active: root.enableShadow
|
||||
anchors.fill: background
|
||||
sourceComponent: StyledRectangularShadow {
|
||||
target: background
|
||||
anchors.fill: undefined
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
implicitHeight: Math.max(toolbarLayout.implicitHeight + root.padding * 2, 56)
|
||||
implicitHeight: 56
|
||||
implicitWidth: toolbarLayout.implicitWidth + root.padding * 2
|
||||
radius: height / 2
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import qs.modules.common
|
||||
import qs.modules.common.models
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.synchronizer
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property alias currentIndex: tabBar.currentIndex
|
||||
required property var tabButtonList
|
||||
|
||||
function incrementCurrentIndex() {
|
||||
tabBar.incrementCurrentIndex()
|
||||
}
|
||||
function decrementCurrentIndex() {
|
||||
tabBar.decrementCurrentIndex()
|
||||
}
|
||||
function setCurrentIndex(index) {
|
||||
tabBar.setCurrentIndex(index)
|
||||
}
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
implicitWidth: contentItem.implicitWidth
|
||||
implicitHeight: 40
|
||||
|
||||
Row {
|
||||
id: contentItem
|
||||
z: 1
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: root.tabButtonList
|
||||
delegate: ToolbarTabButton {
|
||||
required property int index
|
||||
required property var modelData
|
||||
current: index == root.currentIndex
|
||||
text: modelData.name
|
||||
materialSymbol: modelData.icon
|
||||
onClicked: {
|
||||
root.setCurrentIndex(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: activeIndicator
|
||||
z: 0
|
||||
color: Appearance.colors.colSecondaryContainer
|
||||
implicitWidth: contentItem.children[root.currentIndex]?.implicitWidth ?? 0
|
||||
implicitHeight: contentItem.children[root.currentIndex]?.implicitHeight ?? 0
|
||||
radius: height / 2
|
||||
// Animation
|
||||
property Item targetItem: contentItem.children[root.currentIndex]
|
||||
AnimatedTabIndexPair {
|
||||
id: leftBound
|
||||
idx1Duration: 50
|
||||
idx2Duration: 200
|
||||
index: activeIndicator.targetItem.x
|
||||
}
|
||||
AnimatedTabIndexPair {
|
||||
id: rightBound
|
||||
idx1Duration: 50
|
||||
idx2Duration: 200
|
||||
index: activeIndicator.targetItem.x + activeIndicator.targetItem.width
|
||||
}
|
||||
x: Math.min(leftBound.idx1, leftBound.idx2)
|
||||
width: Math.max(rightBound.idx1, rightBound.idx2) - x
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: 2
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onWheel: (event) => {
|
||||
if (event.angleDelta.y < 0) {
|
||||
root.incrementCurrentIndex();
|
||||
}
|
||||
else {
|
||||
root.decrementCurrentIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TabBar doesn't allow tabs to be of different sizes. Literally unusable.
|
||||
// We use it only for the logic and draw stuff manually
|
||||
TabBar {
|
||||
id: tabBar
|
||||
z: -1
|
||||
background: null
|
||||
Repeater { // This is to fool the TabBar that it has tabs so it does the indices properly
|
||||
model: root.tabButtonList.length
|
||||
delegate: TabButton {
|
||||
background: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
RippleButton {
|
||||
id: root
|
||||
required property string materialSymbol
|
||||
required property bool current
|
||||
horizontalPadding: 10
|
||||
|
||||
implicitHeight: 40
|
||||
implicitWidth: implicitContentWidth + horizontalPadding * 2
|
||||
buttonRadius: height / 2
|
||||
|
||||
colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
|
||||
colBackgroundHover: ColorUtils.transparentize(Appearance.colors.colOnSurface, current ? 1 : 0.95)
|
||||
colRipple: ColorUtils.transparentize(Appearance.colors.colOnSurface, 0.95)
|
||||
|
||||
contentItem: Row {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
|
||||
MaterialSymbol {
|
||||
id: icon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconSize: 22
|
||||
text: root.materialSymbol
|
||||
}
|
||||
StyledText {
|
||||
id: label
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.text
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,16 @@ import Quickshell.Hyprland
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
Process {
|
||||
id: unlockKeyringProc
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
KeyringStorage.fetchKeyringData();
|
||||
}
|
||||
}
|
||||
function unlockKeyring() {
|
||||
Quickshell.execDetached({
|
||||
unlockKeyringProc.exec({
|
||||
environment: ({
|
||||
UNLOCK_PASSWORD: root.currentText
|
||||
"UNLOCK_PASSWORD": lockContext.currentText
|
||||
}),
|
||||
command: ["bash", "-c", Quickshell.shellPath("scripts/keyring/unlock.sh")]
|
||||
})
|
||||
@@ -24,7 +30,7 @@ Scope {
|
||||
|
||||
property var windowData: []
|
||||
function saveWindowPositionAndTile() {
|
||||
Hyprland.dispatch(`keyword dwindle:pseudotile true`)
|
||||
Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "true"])
|
||||
root.windowData = HyprlandData.windowList.filter(w => (w.floating && w.workspace.id === HyprlandData.activeWorkspace.id))
|
||||
root.windowData.forEach(w => {
|
||||
Hyprland.dispatch(`pseudo address:${w.address}`)
|
||||
@@ -38,7 +44,7 @@ Scope {
|
||||
Hyprland.dispatch(`movewindowpixel exact ${w.at[0]} ${w.at[1]}, address:${w.address}`)
|
||||
Hyprland.dispatch(`pseudo address:${w.address}`)
|
||||
})
|
||||
Hyprland.dispatch(`keyword dwindle:pseudotile false`)
|
||||
Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "false"])
|
||||
}
|
||||
|
||||
// This stores all the information shared between the lock surfaces on each screen.
|
||||
@@ -156,20 +162,24 @@ Scope {
|
||||
}
|
||||
}
|
||||
|
||||
function initIfReady() {
|
||||
if (!Config.ready || !Persistent.ready) return;
|
||||
if (Config.options.lock.launchOnStartup && Persistent.isNewHyprlandInstance) {
|
||||
Hyprland.dispatch("global quickshell:lock")
|
||||
} else {
|
||||
KeyringStorage.fetchKeyringData();
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: Config
|
||||
function onReadyChanged() {
|
||||
if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) {
|
||||
Hyprland.dispatch("global quickshell:lock")
|
||||
}
|
||||
root.initIfReady();
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: Persistent
|
||||
function onReadyChanged() {
|
||||
if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) {
|
||||
Hyprland.dispatch("global quickshell:lock")
|
||||
}
|
||||
root.initIfReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ Scope {
|
||||
root.resetTargetAction();
|
||||
root.clearText();
|
||||
root.unlockInProgress = false;
|
||||
stopFingerPam();
|
||||
}
|
||||
|
||||
Timer {
|
||||
@@ -69,7 +70,9 @@ Scope {
|
||||
}
|
||||
|
||||
function stopFingerPam() {
|
||||
fingerPam.abort();
|
||||
if (fingerPam.running) {
|
||||
fingerPam.abort();
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
|
||||
@@ -119,6 +119,7 @@ MouseArea {
|
||||
|
||||
ToolbarTextField {
|
||||
id: passwordBox
|
||||
Layout.rightMargin: -Layout.leftMargin
|
||||
placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password")
|
||||
|
||||
// Style
|
||||
@@ -156,11 +157,11 @@ MouseArea {
|
||||
// Shake when wrong password
|
||||
SequentialAnimation {
|
||||
id: wrongPasswordShakeAnim
|
||||
NumberAnimation { target: passwordBox; property: "x"; to: -30; duration: 50 }
|
||||
NumberAnimation { target: passwordBox; property: "x"; to: 30; duration: 50 }
|
||||
NumberAnimation { target: passwordBox; property: "x"; to: -15; duration: 40 }
|
||||
NumberAnimation { target: passwordBox; property: "x"; to: 15; duration: 40 }
|
||||
NumberAnimation { target: passwordBox; property: "x"; to: 0; duration: 30 }
|
||||
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: -30; duration: 50 }
|
||||
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 30; duration: 50 }
|
||||
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: -15; duration: 40 }
|
||||
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 15; duration: 40 }
|
||||
NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 0; duration: 30 }
|
||||
}
|
||||
Connections {
|
||||
target: GlobalStates
|
||||
|
||||
@@ -101,7 +101,7 @@ Item { // Player instance
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.sizes.elevationMargin
|
||||
color: blendedColors.colLayer0
|
||||
color: ColorUtils.applyAlpha(blendedColors.colLayer0, 1)
|
||||
radius: root.radius
|
||||
|
||||
layer.enabled: true
|
||||
|
||||
@@ -43,10 +43,12 @@ Item { // Window
|
||||
property bool hovered: false
|
||||
property bool pressed: false
|
||||
|
||||
property var iconToWindowRatio: 0.35
|
||||
property var xwaylandIndicatorToIconRatio: 0.35
|
||||
property var iconToWindowRatioCompact: 0.6
|
||||
property var iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing")
|
||||
property bool centerIcons: Config.options.overview.centerIcons
|
||||
property real iconGapRatio: 0.06
|
||||
property real iconToWindowRatio: centerIcons ? 0.35 : 0.15
|
||||
property real xwaylandIndicatorToIconRatio: 0.35
|
||||
property real iconToWindowRatioCompact: 0.6
|
||||
property string iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing")
|
||||
property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth
|
||||
|
||||
property bool indicateXWayland: windowData?.xwayland ?? false
|
||||
@@ -109,14 +111,20 @@ Item { // Window
|
||||
|
||||
Image {
|
||||
id: windowIcon
|
||||
anchors.centerIn: parent
|
||||
property real baseSize: Math.min(root.targetWindowWidth, root.targetWindowHeight)
|
||||
anchors {
|
||||
top: root.centerIcons ? undefined : parent.top
|
||||
left: root.centerIcons ? undefined : parent.left
|
||||
centerIn: root.centerIcons ? parent : undefined
|
||||
margins: baseSize * root.iconGapRatio
|
||||
}
|
||||
property var iconSize: {
|
||||
// console.log("-=-=-", root.toplevel.title, "-=-=-")
|
||||
// console.log("Target window size:", targetWindowWidth, targetWindowHeight)
|
||||
// console.log("Icon ratio:", root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
|
||||
// console.log("Scale:", root.monitorData.scale)
|
||||
// console.log("Final:", Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / root.monitorData.scale)
|
||||
return Math.min(root.targetWindowWidth, root.targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio);
|
||||
return baseSize * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio);
|
||||
}
|
||||
// mipmap: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
@@ -65,18 +65,16 @@ Toolbar {
|
||||
}
|
||||
}
|
||||
|
||||
IconAndTextToolbarButton {
|
||||
iconText: "activity_zone"
|
||||
text: Translation.tr("Rect")
|
||||
toggled: root.selectionMode === RegionSelection.SelectionMode.RectCorners
|
||||
onClicked: root.selectionMode = RegionSelection.SelectionMode.RectCorners
|
||||
}
|
||||
|
||||
IconAndTextToolbarButton {
|
||||
iconText: "gesture"
|
||||
text: Translation.tr("Circle")
|
||||
toggled: root.selectionMode === RegionSelection.SelectionMode.Circle
|
||||
onClicked: root.selectionMode = RegionSelection.SelectionMode.Circle
|
||||
ToolbarTabBar {
|
||||
id: tabBar
|
||||
tabButtonList: [
|
||||
{"icon": "activity_zone", "name": Translation.tr("Rect")},
|
||||
{"icon": "gesture", "name": Translation.tr("Circle")}
|
||||
]
|
||||
currentIndex: root.selectionMode === RegionSelection.SelectionMode.RectCorners ? 0 : 1
|
||||
onCurrentIndexChanged: {
|
||||
root.selectionMode = currentIndex === 0 ? RegionSelection.SelectionMode.RectCorners : RegionSelection.SelectionMode.Circle;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ PanelWindow {
|
||||
color: "transparent"
|
||||
WlrLayershell.namespace: "quickshell:regionSelector"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
anchors {
|
||||
left: true
|
||||
|
||||
@@ -961,6 +961,14 @@ ContentPage {
|
||||
Config.options.overview.enable = checked;
|
||||
}
|
||||
}
|
||||
ConfigSwitch {
|
||||
buttonIcon: "center_focus_strong"
|
||||
text: Translation.tr("Center icons")
|
||||
checked: Config.options.overview.centerIcons
|
||||
onCheckedChanged: {
|
||||
Config.options.overview.centerIcons = checked;
|
||||
}
|
||||
}
|
||||
ConfigSpinBox {
|
||||
icon: "loupe"
|
||||
text: Translation.tr("Scale (%)")
|
||||
|
||||
@@ -13,27 +13,28 @@ import Quickshell.Io
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property real padding: 4
|
||||
property var inputField: messageInputField
|
||||
property string commandPrefix: "/"
|
||||
|
||||
property var suggestionQuery: ""
|
||||
property var suggestionList: []
|
||||
|
||||
onFocusChanged: (focus) => {
|
||||
onFocusChanged: focus => {
|
||||
if (focus) {
|
||||
root.inputField.forceActiveFocus()
|
||||
root.inputField.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
messageInputField.forceActiveFocus()
|
||||
Keys.onPressed: event => {
|
||||
messageInputField.forceActiveFocus();
|
||||
if (event.modifiers === Qt.NoModifier) {
|
||||
if (event.key === Qt.Key_PageUp) {
|
||||
messageListView.contentY = Math.max(0, messageListView.contentY - messageListView.height / 2)
|
||||
event.accepted = true
|
||||
messageListView.contentY = Math.max(0, messageListView.contentY - messageListView.height / 2);
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_PageDown) {
|
||||
messageListView.contentY = Math.min(messageListView.contentHeight - messageListView.height / 2, messageListView.contentY + messageListView.height / 2)
|
||||
event.accepted = true
|
||||
messageListView.contentY = Math.min(messageListView.contentHeight - messageListView.height / 2, messageListView.contentY + messageListView.height / 2);
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
if ((event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier) && event.key === Qt.Key_O) {
|
||||
@@ -45,21 +46,21 @@ Item {
|
||||
{
|
||||
name: "attach",
|
||||
description: Translation.tr("Attach a file. Only works with Gemini."),
|
||||
execute: (args) => {
|
||||
execute: args => {
|
||||
Ai.attachFile(args.join(" ").trim());
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "model",
|
||||
description: Translation.tr("Choose model"),
|
||||
execute: (args) => {
|
||||
execute: args => {
|
||||
Ai.setModel(args[0]);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "tool",
|
||||
description: Translation.tr("Set the tool to use for the model."),
|
||||
execute: (args) => {
|
||||
execute: args => {
|
||||
// console.log(args)
|
||||
if (args.length == 0 || args[0] == "get") {
|
||||
Ai.addMessage(Translation.tr("Usage: %1tool TOOL_NAME").arg(root.commandPrefix), Ai.interfaceRole);
|
||||
@@ -75,7 +76,7 @@ Item {
|
||||
{
|
||||
name: "prompt",
|
||||
description: Translation.tr("Set the system prompt for the model."),
|
||||
execute: (args) => {
|
||||
execute: args => {
|
||||
if (args.length === 0 || args[0] === "get") {
|
||||
Ai.printPrompt();
|
||||
return;
|
||||
@@ -86,9 +87,9 @@ Item {
|
||||
{
|
||||
name: "key",
|
||||
description: Translation.tr("Set API key"),
|
||||
execute: (args) => {
|
||||
execute: args => {
|
||||
if (args[0] == "get") {
|
||||
Ai.printApiKey()
|
||||
Ai.printApiKey();
|
||||
} else {
|
||||
Ai.setApiKey(args[0]);
|
||||
}
|
||||
@@ -97,25 +98,25 @@ Item {
|
||||
{
|
||||
name: "save",
|
||||
description: Translation.tr("Save chat"),
|
||||
execute: (args) => {
|
||||
const joinedArgs = args.join(" ")
|
||||
execute: args => {
|
||||
const joinedArgs = args.join(" ");
|
||||
if (joinedArgs.trim().length == 0) {
|
||||
Ai.addMessage(Translation.tr("Usage: %1save CHAT_NAME").arg(root.commandPrefix), Ai.interfaceRole);
|
||||
return;
|
||||
}
|
||||
Ai.saveChat(joinedArgs)
|
||||
Ai.saveChat(joinedArgs);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "load",
|
||||
description: Translation.tr("Load chat"),
|
||||
execute: (args) => {
|
||||
const joinedArgs = args.join(" ")
|
||||
execute: args => {
|
||||
const joinedArgs = args.join(" ");
|
||||
if (joinedArgs.trim().length == 0) {
|
||||
Ai.addMessage(Translation.tr("Usage: %1load CHAT_NAME").arg(root.commandPrefix), Ai.interfaceRole);
|
||||
return;
|
||||
}
|
||||
Ai.loadChat(joinedArgs)
|
||||
Ai.loadChat(joinedArgs);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -128,10 +129,10 @@ Item {
|
||||
{
|
||||
name: "temp",
|
||||
description: Translation.tr("Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5."),
|
||||
execute: (args) => {
|
||||
execute: args => {
|
||||
// console.log(args)
|
||||
if (args.length == 0 || args[0] == "get") {
|
||||
Ai.printTemperature()
|
||||
Ai.printTemperature();
|
||||
} else {
|
||||
const temp = parseFloat(args[0]);
|
||||
Ai.setTemperature(temp);
|
||||
@@ -191,8 +192,7 @@ Inline w/ double dollar signs: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\p
|
||||
Inline w/ backslash and square brackets \\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\]
|
||||
|
||||
Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
`,
|
||||
Ai.interfaceRole);
|
||||
`, Ai.interfaceRole);
|
||||
}
|
||||
},
|
||||
]
|
||||
@@ -208,13 +208,12 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
} else {
|
||||
Ai.addMessage(Translation.tr("Unknown command: ") + command, Ai.interfaceRole);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Ai.sendUserMessage(inputText);
|
||||
}
|
||||
|
||||
|
||||
// Always scroll to bottom when user sends a message
|
||||
messageListView.positionViewAtEnd()
|
||||
messageListView.positionViewAtEnd();
|
||||
}
|
||||
|
||||
Process {
|
||||
@@ -223,16 +222,14 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
property string imageDecodeFileName: "image"
|
||||
property string imageDecodeFilePath: `${imageDecodePath}/${imageDecodeFileName}`
|
||||
function handleEntry(entry: string) {
|
||||
imageDecodeFileName = parseInt(entry.match(/^(\d+)\t/)[1])
|
||||
decodeImageAndAttachProc.exec(["bash", "-c",
|
||||
`[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`
|
||||
])
|
||||
imageDecodeFileName = parseInt(entry.match(/^(\d+)\t/)[1]);
|
||||
decodeImageAndAttachProc.exec(["bash", "-c", `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`]);
|
||||
}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode === 0) {
|
||||
Ai.attachFile(imageDecodeFilePath);
|
||||
} else {
|
||||
console.error("[AiChat] Failed to decode image in clipboard content")
|
||||
console.error("[AiChat] Failed to decode image in clipboard content");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,37 +275,14 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
|
||||
ColumnLayout {
|
||||
id: columnLayout
|
||||
anchors.fill: parent
|
||||
|
||||
RowLayout { // Status
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 10
|
||||
|
||||
StatusItem {
|
||||
icon: Ai.currentModelHasApiKey ? "key" : "key_off"
|
||||
statusText: ""
|
||||
description: Ai.currentModelHasApiKey ? Translation.tr("API key is set\nChange with /key YOUR_API_KEY") : Translation.tr("No API key\nSet it with /key YOUR_API_KEY")
|
||||
}
|
||||
StatusSeparator {}
|
||||
StatusItem {
|
||||
icon: "device_thermostat"
|
||||
statusText: Ai.temperature.toFixed(1)
|
||||
description: Translation.tr("Temperature\nChange with /temp VALUE")
|
||||
}
|
||||
StatusSeparator {
|
||||
visible: Ai.tokenCount.total > 0
|
||||
}
|
||||
StatusItem {
|
||||
visible: Ai.tokenCount.total > 0
|
||||
icon: "token"
|
||||
statusText: Ai.tokenCount.total
|
||||
description: Translation.tr("Total token count\nInput: %1\nOutput: %2")
|
||||
.arg(Ai.tokenCount.input)
|
||||
.arg(Ai.tokenCount.output)
|
||||
}
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: root.padding
|
||||
}
|
||||
spacing: root.padding
|
||||
|
||||
Item { // Messages
|
||||
Item {
|
||||
// Messages
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
layer.enabled: true
|
||||
@@ -320,6 +294,55 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
}
|
||||
}
|
||||
|
||||
StyledRectangularShadow {
|
||||
z: 1
|
||||
target: statusBg
|
||||
opacity: messageListView.atYBeginning ? 0 : 1
|
||||
visible: opacity > 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
id: statusBg
|
||||
z: 2
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.top
|
||||
topMargin: 4
|
||||
}
|
||||
implicitWidth: statusRowLayout.implicitWidth + 10 * 2
|
||||
implicitHeight: Math.max(statusRowLayout.implicitHeight, 38)
|
||||
radius: Appearance.rounding.normal - root.padding
|
||||
color: Appearance.colors.colLayer2
|
||||
RowLayout {
|
||||
id: statusRowLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
|
||||
StatusItem {
|
||||
icon: Ai.currentModelHasApiKey ? "key" : "key_off"
|
||||
statusText: ""
|
||||
description: Ai.currentModelHasApiKey ? Translation.tr("API key is set\nChange with /key YOUR_API_KEY") : Translation.tr("No API key\nSet it with /key YOUR_API_KEY")
|
||||
}
|
||||
StatusSeparator {}
|
||||
StatusItem {
|
||||
icon: "device_thermostat"
|
||||
statusText: Ai.temperature.toFixed(1)
|
||||
description: Translation.tr("Temperature\nChange with /temp VALUE")
|
||||
}
|
||||
StatusSeparator {
|
||||
visible: Ai.tokenCount.total > 0
|
||||
}
|
||||
StatusItem {
|
||||
visible: Ai.tokenCount.total > 0
|
||||
icon: "token"
|
||||
statusText: Ai.tokenCount.total
|
||||
description: Translation.tr("Total token count\nInput: %1\nOutput: %2").arg(Ai.tokenCount.input).arg(Ai.tokenCount.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollEdgeFade {
|
||||
z: 1
|
||||
target: messageListView
|
||||
@@ -332,16 +355,20 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
popin: false
|
||||
topMargin: statusBg.implicitHeight + statusBg.anchors.topMargin * 2
|
||||
|
||||
touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4
|
||||
mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
|
||||
|
||||
property int lastResponseLength: 0
|
||||
onContentHeightChanged: {
|
||||
if (atYEnd) Qt.callLater(positionViewAtEnd);
|
||||
if (atYEnd)
|
||||
Qt.callLater(positionViewAtEnd);
|
||||
}
|
||||
onCountChanged: { // Auto-scroll when new messages are added
|
||||
if (atYEnd) Qt.callLater(positionViewAtEnd);
|
||||
onCountChanged: {
|
||||
// Auto-scroll when new messages are added
|
||||
if (atYEnd)
|
||||
Qt.callLater(positionViewAtEnd);
|
||||
}
|
||||
|
||||
add: null // Prevent function calls from being janky
|
||||
@@ -357,7 +384,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
required property int index
|
||||
messageIndex: index
|
||||
messageData: {
|
||||
Ai.messageByID[modelData]
|
||||
Ai.messageByID[modelData];
|
||||
}
|
||||
messageInputField: root.inputField
|
||||
}
|
||||
@@ -393,8 +420,8 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
Repeater {
|
||||
id: suggestionRepeater
|
||||
model: {
|
||||
suggestions.selectedIndex = 0
|
||||
return root.suggestionList.slice(0, 10)
|
||||
suggestions.selectedIndex = 0;
|
||||
return root.suggestionList.slice(0, 10);
|
||||
}
|
||||
delegate: ApiCommandButton {
|
||||
id: commandButton
|
||||
@@ -413,7 +440,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
suggestions.acceptSuggestion(modelData.name)
|
||||
suggestions.acceptSuggestion(modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -443,14 +470,10 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
id: inputWrapper
|
||||
property real spacing: 5
|
||||
Layout.fillWidth: true
|
||||
radius: Appearance.rounding.small
|
||||
color: Appearance.colors.colLayer1
|
||||
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
|
||||
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + spacing, 45)
|
||||
+ (attachedFileIndicator.implicitHeight + spacing + attachedFileIndicator.anchors.topMargin)
|
||||
radius: Appearance.rounding.normal - root.padding
|
||||
color: Appearance.colors.colLayer2
|
||||
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + spacing, 45) + (attachedFileIndicator.implicitHeight + spacing + attachedFileIndicator.anchors.topMargin)
|
||||
clip: true
|
||||
border.color: Appearance.colors.colOutlineVariant
|
||||
border.width: 1
|
||||
|
||||
Behavior on implicitHeight {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
@@ -488,121 +511,122 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
|
||||
background: null
|
||||
|
||||
onTextChanged: { // Handle suggestions
|
||||
onTextChanged: {
|
||||
// Handle suggestions
|
||||
if (messageInputField.text.length === 0) {
|
||||
root.suggestionQuery = ""
|
||||
root.suggestionList = []
|
||||
return
|
||||
root.suggestionQuery = "";
|
||||
root.suggestionList = [];
|
||||
return;
|
||||
} else if (messageInputField.text.startsWith(`${root.commandPrefix}model`)) {
|
||||
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""
|
||||
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
|
||||
const modelResults = Fuzzy.go(root.suggestionQuery, Ai.modelList.map(model => {
|
||||
return {
|
||||
name: Fuzzy.prepare(model),
|
||||
obj: model,
|
||||
}
|
||||
obj: model
|
||||
};
|
||||
}), {
|
||||
all: true,
|
||||
key: "name"
|
||||
})
|
||||
});
|
||||
root.suggestionList = modelResults.map(model => {
|
||||
return {
|
||||
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "model ") : ""}${model.target}`,
|
||||
displayName: `${Ai.models[model.target].name}`,
|
||||
description: `${Ai.models[model.target].description}`,
|
||||
}
|
||||
})
|
||||
description: `${Ai.models[model.target].description}`
|
||||
};
|
||||
});
|
||||
} else if (messageInputField.text.startsWith(`${root.commandPrefix}prompt`)) {
|
||||
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""
|
||||
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
|
||||
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.promptFiles.map(file => {
|
||||
return {
|
||||
name: Fuzzy.prepare(file),
|
||||
obj: file,
|
||||
}
|
||||
obj: file
|
||||
};
|
||||
}), {
|
||||
all: true,
|
||||
key: "name"
|
||||
})
|
||||
});
|
||||
root.suggestionList = promptFileResults.map(file => {
|
||||
return {
|
||||
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "prompt ") : ""}${file.target}`,
|
||||
displayName: `${FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target))}`,
|
||||
description: Translation.tr("Load prompt from %1").arg(file.target),
|
||||
}
|
||||
})
|
||||
description: Translation.tr("Load prompt from %1").arg(file.target)
|
||||
};
|
||||
});
|
||||
} else if (messageInputField.text.startsWith(`${root.commandPrefix}save`)) {
|
||||
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""
|
||||
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
|
||||
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => {
|
||||
return {
|
||||
name: Fuzzy.prepare(file),
|
||||
obj: file,
|
||||
}
|
||||
obj: file
|
||||
};
|
||||
}), {
|
||||
all: true,
|
||||
key: "name"
|
||||
})
|
||||
});
|
||||
root.suggestionList = promptFileResults.map(file => {
|
||||
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim()
|
||||
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim();
|
||||
return {
|
||||
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "save ") : ""}${chatName}`,
|
||||
displayName: `${chatName}`,
|
||||
description: Translation.tr("Save chat to %1").arg(chatName),
|
||||
}
|
||||
})
|
||||
description: Translation.tr("Save chat to %1").arg(chatName)
|
||||
};
|
||||
});
|
||||
} else if (messageInputField.text.startsWith(`${root.commandPrefix}load`)) {
|
||||
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""
|
||||
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
|
||||
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => {
|
||||
return {
|
||||
name: Fuzzy.prepare(file),
|
||||
obj: file,
|
||||
}
|
||||
obj: file
|
||||
};
|
||||
}), {
|
||||
all: true,
|
||||
key: "name"
|
||||
})
|
||||
});
|
||||
root.suggestionList = promptFileResults.map(file => {
|
||||
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim()
|
||||
const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim();
|
||||
return {
|
||||
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "load ") : ""}${chatName}`,
|
||||
displayName: `${chatName}`,
|
||||
description: Translation.tr(`Load chat from %1`).arg(file.target),
|
||||
}
|
||||
})
|
||||
description: Translation.tr(`Load chat from %1`).arg(file.target)
|
||||
};
|
||||
});
|
||||
} else if (messageInputField.text.startsWith(`${root.commandPrefix}tool`)) {
|
||||
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""
|
||||
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "";
|
||||
const toolResults = Fuzzy.go(root.suggestionQuery, Ai.availableTools.map(tool => {
|
||||
return {
|
||||
name: Fuzzy.prepare(tool),
|
||||
obj: tool,
|
||||
}
|
||||
obj: tool
|
||||
};
|
||||
}), {
|
||||
all: true,
|
||||
key: "name"
|
||||
})
|
||||
});
|
||||
root.suggestionList = toolResults.map(tool => {
|
||||
const toolName = tool.target
|
||||
const toolName = tool.target;
|
||||
return {
|
||||
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "tool ") : ""}${tool.target}`,
|
||||
displayName: toolName,
|
||||
description: Ai.toolDescriptions[toolName],
|
||||
}
|
||||
})
|
||||
} else if(messageInputField.text.startsWith(root.commandPrefix)) {
|
||||
root.suggestionQuery = messageInputField.text
|
||||
description: Ai.toolDescriptions[toolName]
|
||||
};
|
||||
});
|
||||
} else if (messageInputField.text.startsWith(root.commandPrefix)) {
|
||||
root.suggestionQuery = messageInputField.text;
|
||||
root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(messageInputField.text.substring(1))).map(cmd => {
|
||||
return {
|
||||
name: `${root.commandPrefix}${cmd.name}`,
|
||||
description: `${cmd.description}`,
|
||||
}
|
||||
})
|
||||
description: `${cmd.description}`
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function accept() {
|
||||
root.handleInput(text)
|
||||
text = ""
|
||||
root.handleInput(text);
|
||||
text = "";
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Tab) {
|
||||
suggestions.acceptSelectedWord();
|
||||
event.accepted = true;
|
||||
@@ -615,35 +639,41 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
} else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {
|
||||
if (event.modifiers & Qt.ShiftModifier) {
|
||||
// Insert newline
|
||||
messageInputField.insert(messageInputField.cursorPosition, "\n")
|
||||
event.accepted = true
|
||||
} else { // Accept text
|
||||
const inputText = messageInputField.text
|
||||
messageInputField.clear()
|
||||
root.handleInput(inputText)
|
||||
event.accepted = true
|
||||
messageInputField.insert(messageInputField.cursorPosition, "\n");
|
||||
event.accepted = true;
|
||||
} else {
|
||||
// Accept text
|
||||
const inputText = messageInputField.text;
|
||||
messageInputField.clear();
|
||||
root.handleInput(inputText);
|
||||
event.accepted = true;
|
||||
}
|
||||
} else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) { // Intercept Ctrl+V to handle image/file pasting
|
||||
if (event.modifiers & Qt.ShiftModifier) { // Let Shift+Ctrl+V = plain paste
|
||||
messageInputField.text += Quickshell.clipboardText
|
||||
} else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) {
|
||||
// Intercept Ctrl+V to handle image/file pasting
|
||||
if (event.modifiers & Qt.ShiftModifier) {
|
||||
// Let Shift+Ctrl+V = plain paste
|
||||
messageInputField.text += Quickshell.clipboardText;
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
// Try image paste first
|
||||
const currentClipboardEntry = Cliphist.entries[0]
|
||||
const cleanCliphistEntry = StringUtils.cleanCliphistEntry(currentClipboardEntry)
|
||||
if (/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(currentClipboardEntry)) { // First entry = currently copied entry = image?
|
||||
decodeImageAndAttachProc.handleEntry(currentClipboardEntry)
|
||||
const currentClipboardEntry = Cliphist.entries[0];
|
||||
const cleanCliphistEntry = StringUtils.cleanCliphistEntry(currentClipboardEntry);
|
||||
if (/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(currentClipboardEntry)) {
|
||||
// First entry = currently copied entry = image?
|
||||
decodeImageAndAttachProc.handleEntry(currentClipboardEntry);
|
||||
event.accepted = true;
|
||||
return;
|
||||
} else if (cleanCliphistEntry.startsWith("file://")) { // First entry = currently copied entry = image?
|
||||
const fileName = decodeURIComponent(cleanCliphistEntry)
|
||||
} else if (cleanCliphistEntry.startsWith("file://")) {
|
||||
// First entry = currently copied entry = image?
|
||||
const fileName = decodeURIComponent(cleanCliphistEntry);
|
||||
Ai.attachFile(fileName);
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
event.accepted = false; // No image, let text pasting proceed
|
||||
} else if (event.key === Qt.Key_Escape) { // Esc to detach file
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
// Esc to detach file
|
||||
if (Ai.pendingFilePath.length > 0) {
|
||||
Ai.attachFile("");
|
||||
event.accepted = true;
|
||||
@@ -668,19 +698,18 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
anchors.fill: parent
|
||||
cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
const inputText = messageInputField.text
|
||||
root.handleInput(inputText)
|
||||
messageInputField.clear()
|
||||
const inputText = messageInputField.text;
|
||||
root.handleInput(inputText);
|
||||
messageInputField.clear();
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
// fill: sendButton.enabled ? 1 : 0
|
||||
iconSize: 22
|
||||
color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled
|
||||
text: "send"
|
||||
text: "arrow_upward"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -699,59 +728,58 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
{
|
||||
name: "",
|
||||
sendDirectly: false,
|
||||
dontAddSpace: true,
|
||||
},
|
||||
dontAddSpace: true
|
||||
},
|
||||
{
|
||||
name: "clear",
|
||||
sendDirectly: true,
|
||||
},
|
||||
sendDirectly: true
|
||||
},
|
||||
]
|
||||
|
||||
ApiInputBoxIndicator { // Model indicator
|
||||
ApiInputBoxIndicator {
|
||||
// Model indicator
|
||||
icon: "api"
|
||||
text: Ai.getModel().name
|
||||
tooltipText: Translation.tr("Current model: %1\nSet it with %2model MODEL")
|
||||
.arg(Ai.getModel().name)
|
||||
.arg(root.commandPrefix)
|
||||
tooltipText: Translation.tr("Current model: %1\nSet it with %2model MODEL").arg(Ai.getModel().name).arg(root.commandPrefix)
|
||||
}
|
||||
|
||||
ApiInputBoxIndicator { // Tool indicator
|
||||
ApiInputBoxIndicator {
|
||||
// Tool indicator
|
||||
icon: "service_toolbox"
|
||||
text: Ai.currentTool.charAt(0).toUpperCase() + Ai.currentTool.slice(1)
|
||||
tooltipText: Translation.tr("Current tool: %1\nSet it with %2tool TOOL")
|
||||
.arg(Ai.currentTool)
|
||||
.arg(root.commandPrefix)
|
||||
tooltipText: Translation.tr("Current tool: %1\nSet it with %2tool TOOL").arg(Ai.currentTool).arg(root.commandPrefix)
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ButtonGroup { // Command buttons
|
||||
ButtonGroup {
|
||||
// Command buttons
|
||||
padding: 0
|
||||
|
||||
Repeater { // Command buttons
|
||||
Repeater {
|
||||
// Command buttons
|
||||
model: commandButtonsRow.commandsShown
|
||||
delegate: ApiCommandButton {
|
||||
property string commandRepresentation: `${root.commandPrefix}${modelData.name}`
|
||||
buttonText: commandRepresentation
|
||||
downAction: () => {
|
||||
if (modelData.sendDirectly) {
|
||||
root.handleInput(commandRepresentation)
|
||||
root.handleInput(commandRepresentation);
|
||||
} else {
|
||||
messageInputField.text = commandRepresentation + (modelData.dontAddSpace ? "" : " ")
|
||||
messageInputField.cursorPosition = messageInputField.text.length
|
||||
messageInputField.forceActiveFocus()
|
||||
messageInputField.text = commandRepresentation + (modelData.dontAddSpace ? "" : " ");
|
||||
messageInputField.cursorPosition = messageInputField.text.length;
|
||||
messageInputField.forceActiveFocus();
|
||||
}
|
||||
if (modelData.name === "clear") {
|
||||
messageInputField.text = ""
|
||||
messageInputField.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import Quickshell
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property real padding: 4
|
||||
|
||||
property var inputField: tagInputField
|
||||
readonly property var responses: Booru.responses
|
||||
property string previewDownloadPath: Directories.booruPreviews
|
||||
@@ -141,7 +143,11 @@ Item {
|
||||
|
||||
ColumnLayout {
|
||||
id: columnLayout
|
||||
anchors.fill: parent
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: root.padding
|
||||
}
|
||||
spacing: root.padding
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
@@ -317,14 +323,12 @@ Item {
|
||||
id: tagInputContainer
|
||||
property real columnSpacing: 5
|
||||
Layout.fillWidth: true
|
||||
radius: Appearance.rounding.small
|
||||
color: Appearance.colors.colLayer1
|
||||
radius: Appearance.rounding.normal - root.padding
|
||||
color: Appearance.colors.colLayer2
|
||||
implicitWidth: tagInputField.implicitWidth
|
||||
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
|
||||
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
|
||||
clip: true
|
||||
border.color: Appearance.colors.colOutlineVariant
|
||||
border.width: 1
|
||||
|
||||
Behavior on implicitHeight {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
@@ -456,10 +460,9 @@ Item {
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
// fill: sendButton.enabled ? 1 : 0
|
||||
iconSize: 22
|
||||
color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled
|
||||
text: "send"
|
||||
text: "arrow_upward"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ Scope { // Scope
|
||||
sourceComponent: FloatingWindow {
|
||||
id: detachedSidebarRoot
|
||||
property var contentParent: detachedSidebarBackground
|
||||
color: "transparent"
|
||||
|
||||
visible: GlobalStates.sidebarLeftOpen
|
||||
onVisibleChanged: {
|
||||
|
||||
@@ -21,7 +21,6 @@ Item {
|
||||
...(root.translatorEnabled ? [{"icon": "translate", "name": Translation.tr("Translator")}] : []),
|
||||
...((root.animeEnabled && !root.animeCloset) ? [{"icon": "bookmark_heart", "name": Translation.tr("Anime")}] : [])
|
||||
]
|
||||
property int selectedTab: 0
|
||||
property int tabCount: swipeView.count
|
||||
|
||||
function focusActiveItem() {
|
||||
@@ -31,67 +30,64 @@ Item {
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.modifiers === Qt.ControlModifier) {
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
root.selectedTab = Math.min(root.selectedTab + 1, root.tabCount - 1)
|
||||
swipeView.incrementCurrentIndex()
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
else if (event.key === Qt.Key_PageUp) {
|
||||
root.selectedTab = Math.max(root.selectedTab - 1, 0)
|
||||
event.accepted = true;
|
||||
}
|
||||
else if (event.key === Qt.Key_Tab) {
|
||||
root.selectedTab = (root.selectedTab + 1) % root.tabCount;
|
||||
event.accepted = true;
|
||||
}
|
||||
else if (event.key === Qt.Key_Backtab) {
|
||||
root.selectedTab = (root.selectedTab - 1 + root.tabCount) % root.tabCount;
|
||||
swipeView.decrementCurrentIndex()
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: sidebarPadding
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: sidebarPadding
|
||||
}
|
||||
spacing: sidebarPadding
|
||||
|
||||
PrimaryTabBar { // Tab strip
|
||||
id: tabBar
|
||||
visible: root.tabButtonList.length > 1
|
||||
tabButtonList: root.tabButtonList
|
||||
Synchronizer on currentIndex {
|
||||
property alias source: root.selectedTab
|
||||
Toolbar {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
enableShadow: false
|
||||
ToolbarTabBar {
|
||||
id: tabBar
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
tabButtonList: root.tabButtonList
|
||||
currentIndex: swipeView.currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
SwipeView { // Content pages
|
||||
id: swipeView
|
||||
Layout.topMargin: 5
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 10
|
||||
|
||||
currentIndex: root.selectedTab
|
||||
onCurrentIndexChanged: {
|
||||
tabBar.enableIndicatorAnimation = true
|
||||
root.selectedTab = currentIndex
|
||||
}
|
||||
implicitWidth: swipeView.implicitWidth
|
||||
implicitHeight: swipeView.implicitHeight
|
||||
radius: Appearance.rounding.normal
|
||||
color: Appearance.colors.colLayer1
|
||||
|
||||
clip: true
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: swipeView.width
|
||||
height: swipeView.height
|
||||
radius: Appearance.rounding.small
|
||||
SwipeView { // Content pages
|
||||
id: swipeView
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
currentIndex: tabBar.currentIndex
|
||||
|
||||
clip: true
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: swipeView.width
|
||||
height: swipeView.height
|
||||
radius: Appearance.rounding.small
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentChildren: [
|
||||
...((root.aiChatEnabled || (!root.translatorEnabled && !root.animeEnabled)) ? [aiChat.createObject()] : []),
|
||||
...(root.translatorEnabled ? [translator.createObject()] : []),
|
||||
...(root.animeEnabled ? [anime.createObject()] : [])
|
||||
]
|
||||
contentChildren: [
|
||||
...((root.aiChatEnabled || (!root.translatorEnabled && !root.animeEnabled)) ? [aiChat.createObject()] : []),
|
||||
...(root.translatorEnabled ? [translator.createObject()] : []),
|
||||
...(root.animeEnabled ? [anime.createObject()] : [])
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
|
||||
@@ -13,17 +13,24 @@ import Quickshell.Io
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Sizes
|
||||
property real padding: 4
|
||||
|
||||
// Widgets
|
||||
property var inputField: inputCanvas.inputTextArea
|
||||
|
||||
// Widget variables
|
||||
property bool translationFor: false // Indicates if the translation is for an autocorrected text
|
||||
property string translatedText: ""
|
||||
property list<string> languages: []
|
||||
|
||||
// Options
|
||||
property string targetLanguage: Config.options.language.translator.targetLanguage
|
||||
property string sourceLanguage: Config.options.language.translator.sourceLanguage
|
||||
property string hostLanguage: targetLanguage
|
||||
|
||||
// States
|
||||
property bool showLanguageSelector: false
|
||||
property bool languageSelectorTarget: false // true for target language, false for source language
|
||||
|
||||
@@ -99,7 +106,11 @@ Item {
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: root.padding
|
||||
}
|
||||
|
||||
StyledFlickable {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -17,10 +17,8 @@ Rectangle {
|
||||
default property alias actionButtons: actions.data
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: Math.max(150, inputColumn.implicitHeight)
|
||||
color: isInput ? Appearance.colors.colLayer1 : Appearance.colors.colSurfaceContainer
|
||||
color: Appearance.colors.colLayer2
|
||||
radius: Appearance.rounding.normal
|
||||
border.color: isInput ? Appearance.colors.colOutlineVariant : "transparent"
|
||||
border.width: isInput ? 1 : 0
|
||||
|
||||
signal inputTextChanged(); // Signal emitted when text changes
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ WindowDialog {
|
||||
right: parent.right
|
||||
}
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
buttonIcon: "destruction"
|
||||
buttonIcon: "flash_off"
|
||||
text: Translation.tr("Enable")
|
||||
checked: Config.options.light.antiFlashbang.enable
|
||||
onCheckedChanged: {
|
||||
|
||||
@@ -29,7 +29,7 @@ AbstractQuickPanel {
|
||||
readonly property real baseCellHeight: 56
|
||||
|
||||
// Toggles
|
||||
readonly property list<string> availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile","musicRecognition"]
|
||||
readonly property list<string> availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile","musicRecognition", "antiFlashbang"]
|
||||
readonly property int columns: Config.options.sidebar.quickToggles.android.columns
|
||||
readonly property list<var> toggles: Config.ready ? Config.options.sidebar.quickToggles.android.toggles : []
|
||||
readonly property list<var> toggleRows: toggleRowsForList(toggles)
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
AndroidQuickToggleButton {
|
||||
id: root
|
||||
|
||||
property bool auto: Config.options.light.night.automatic
|
||||
|
||||
name: Translation.tr("Anti-flashbang")
|
||||
|
||||
toggled: Config.options.light.antiFlashbang.enable
|
||||
buttonIcon: "flash_off"
|
||||
|
||||
mainAction: () => {
|
||||
Config.options.light.antiFlashbang.enable = !Config.options.light.antiFlashbang.enable;
|
||||
}
|
||||
|
||||
altAction: () => {
|
||||
root.openMenu()
|
||||
}
|
||||
|
||||
StyledToolTip {
|
||||
text: Translation.tr("Anti-flashbang")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ AndroidQuickToggleButton {
|
||||
statusText: toggled ? Translation.tr("Unmuted") : Translation.tr("Muted")
|
||||
toggled: !Audio.sink?.audio?.muted
|
||||
buttonIcon: Audio.sink?.audio?.muted ? "volume_off" : "volume_up"
|
||||
onClicked: {
|
||||
mainAction: () => {
|
||||
Audio.sink.audio.muted = !Audio.sink.audio.muted
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ AndroidQuickToggleButton {
|
||||
|
||||
toggled: BluetoothStatus.enabled
|
||||
buttonIcon: BluetoothStatus.connected ? "bluetooth_connected" : BluetoothStatus.enabled ? "bluetooth" : "bluetooth_disabled"
|
||||
onClicked: {
|
||||
mainAction: () => {
|
||||
Bluetooth.defaultAdapter.enabled = !Bluetooth.defaultAdapter?.enabled
|
||||
}
|
||||
altAction: () => {
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ AndroidQuickToggleButton {
|
||||
toggled: false
|
||||
buttonIcon: "cloud_lock"
|
||||
|
||||
onClicked: {
|
||||
mainAction: () => {
|
||||
if (toggled) {
|
||||
root.toggled = false
|
||||
Quickshell.execDetached(["warp-cli", "disconnect"])
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ AndroidQuickToggleButton {
|
||||
toggled: false
|
||||
buttonIcon: "colorize"
|
||||
|
||||
onClicked: {
|
||||
mainAction: () => {
|
||||
GlobalStates.sidebarRightOpen = false;
|
||||
delayedActionTimer.start()
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ AndroidQuickToggleButton {
|
||||
toggled: Appearance.m3colors.darkmode
|
||||
buttonIcon: "contrast"
|
||||
|
||||
onClicked: event => {
|
||||
mainAction: () => {
|
||||
if (Appearance.m3colors.darkmode) {
|
||||
Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, "--mode", "light", "--noswitch"]);
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ AndroidQuickToggleButton {
|
||||
EasyEffects.fetchActiveState()
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
mainAction: () => {
|
||||
EasyEffects.toggle()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ AndroidQuickToggleButton {
|
||||
toggled: toggled
|
||||
buttonIcon: "gamepad"
|
||||
|
||||
onClicked: {
|
||||
mainAction: () => {
|
||||
root.toggled = !root.toggled
|
||||
if (root.toggled) {
|
||||
Quickshell.execDetached(["bash", "-c", `hyprctl --batch "keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1"`])
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ AndroidQuickToggleButton {
|
||||
|
||||
toggled: Idle.inhibit
|
||||
buttonIcon: "coffee"
|
||||
onClicked: {
|
||||
mainAction: () => {
|
||||
Idle.toggleInhibit()
|
||||
}
|
||||
StyledToolTip {
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ AndroidQuickToggleButton {
|
||||
statusText: toggled ? Translation.tr("Enabled") : Translation.tr("Muted")
|
||||
toggled: !Audio.source?.audio?.muted
|
||||
buttonIcon: Audio.source?.audio?.muted ? "mic_off" : "mic"
|
||||
onClicked: {
|
||||
mainAction: () => {
|
||||
Audio.source.audio.muted = !Audio.source.audio.muted
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ AndroidQuickToggleButton {
|
||||
text: Translation.tr("Recognize music | Right-click to toggle source")
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
mainAction: () => {
|
||||
SongRec.toggleRunning()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ AndroidQuickToggleButton {
|
||||
|
||||
toggled: Network.wifiStatus !== "disabled"
|
||||
buttonIcon: Network.materialSymbol
|
||||
onClicked: Network.toggleWifi()
|
||||
mainAction: () => Network.toggleWifi()
|
||||
altAction: () => {
|
||||
root.openMenu()
|
||||
}
|
||||
|
||||
+2
-1
@@ -14,7 +14,8 @@ AndroidQuickToggleButton {
|
||||
|
||||
toggled: Hyprsunset.active
|
||||
buttonIcon: auto ? "night_sight_auto" : "bedtime"
|
||||
onClicked: {
|
||||
|
||||
mainAction: () => {
|
||||
Hyprsunset.toggle()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ AndroidQuickToggleButton {
|
||||
toggled: !Notifications.silent
|
||||
buttonIcon: toggled ? "notifications_active" : "notifications_paused"
|
||||
|
||||
onClicked: {
|
||||
mainAction: () => {
|
||||
Notifications.silent = !Notifications.silent;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -11,7 +11,8 @@ AndroidQuickToggleButton {
|
||||
name: Translation.tr("Virtual Keyboard")
|
||||
toggled: GlobalStates.oskOpen
|
||||
buttonIcon: toggled ? "keyboard_hide" : "keyboard"
|
||||
onClicked: {
|
||||
|
||||
mainAction: () => {
|
||||
GlobalStates.oskOpen = !GlobalStates.oskOpen
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ AndroidQuickToggleButton {
|
||||
case PowerProfile.Performance: return "Performance"
|
||||
}
|
||||
|
||||
onClicked: (event) => {
|
||||
mainAction: () => {
|
||||
if (PowerProfiles.hasPerformanceProfile) {
|
||||
switch(PowerProfiles.profile) {
|
||||
case PowerProfile.PowerSaver: PowerProfiles.profile = PowerProfile.Balanced
|
||||
|
||||
+56
-21
@@ -13,6 +13,7 @@ GroupButton {
|
||||
required property bool expandedSize
|
||||
required property string buttonIcon
|
||||
required property string name
|
||||
required property var mainAction
|
||||
property string statusText: toggled ? Translation.tr("Active") : Translation.tr("Inactive")
|
||||
|
||||
required property real baseCellWidth
|
||||
@@ -54,6 +55,11 @@ GroupButton {
|
||||
property color colText: (toggled && !(altAction && expandedSize)) ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer2
|
||||
property color colIcon: expandedSize ? (root.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer3) : colText
|
||||
|
||||
onClicked: {
|
||||
if (root.expandedSize && root.altAction) root.altAction();
|
||||
else root.mainAction();
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
id: contentItem
|
||||
spacing: 4
|
||||
@@ -64,35 +70,63 @@ GroupButton {
|
||||
rightMargin: root.horizontalPadding
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
// Icon
|
||||
MouseArea {
|
||||
id: iconMouseArea
|
||||
hoverEnabled: true
|
||||
acceptedButtons: (root.expandedSize && root.altAction) ? Qt.LeftButton : Qt.NoButton
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: root.verticalPadding
|
||||
Layout.bottomMargin: root.verticalPadding
|
||||
implicitWidth: height
|
||||
radius: root.radius - root.verticalPadding
|
||||
color: {
|
||||
const baseColor = root.toggled ? Appearance.colors.colPrimary : Appearance.colors.colLayer3
|
||||
const transparentizeAmount = (root.altAction && root.expandedSize) ? 0 : 1
|
||||
return ColorUtils.transparentize(baseColor, transparentizeAmount)
|
||||
}
|
||||
implicitHeight: iconBackground.implicitHeight
|
||||
implicitWidth: iconBackground.implicitWidth
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
Behavior on radius {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on color {
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
onClicked: root.mainAction()
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
fill: root.toggled ? 1 : 0
|
||||
iconSize: root.expandedSize ? 22 : 24
|
||||
color: root.colIcon
|
||||
text: root.buttonIcon
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
anchors.fill: parent
|
||||
implicitWidth: height
|
||||
radius: root.radius - root.verticalPadding
|
||||
color: {
|
||||
const baseColor = root.toggled ? Appearance.colors.colPrimary : Appearance.colors.colLayer3
|
||||
const transparentizeAmount = (root.altAction && root.expandedSize) ? 0 : 1
|
||||
return ColorUtils.transparentize(baseColor, transparentizeAmount)
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on color {
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
fill: root.toggled ? 1 : 0
|
||||
iconSize: root.expandedSize ? 22 : 24
|
||||
color: root.colIcon
|
||||
text: root.buttonIcon
|
||||
}
|
||||
|
||||
// State layer
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: (root.expandedSize && root.altAction)
|
||||
sourceComponent: Rectangle {
|
||||
radius: iconBackground.radius
|
||||
color: ColorUtils.transparentize(root.colIcon, iconMouseArea.containsPress ? 0.88 : iconMouseArea.containsMouse ? 0.95 : 1)
|
||||
Behavior on color {
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text column for expanded size
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
@@ -119,8 +153,9 @@ GroupButton {
|
||||
}
|
||||
font {
|
||||
pixelSize: Appearance.font.pixelSize.smaller
|
||||
weight: 100
|
||||
}
|
||||
color: Appearance.colors.colSubtext
|
||||
color: root.colText
|
||||
elide: Text.ElideRight
|
||||
text: root.statusText
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ AndroidQuickToggleButton {
|
||||
toggled: false
|
||||
buttonIcon: "screenshot_region"
|
||||
|
||||
onClicked: {
|
||||
mainAction: () => {
|
||||
GlobalStates.sidebarRightOpen = false;
|
||||
delayedActionTimer.start()
|
||||
}
|
||||
|
||||
+16
@@ -245,4 +245,20 @@ DelegateChooser {
|
||||
cellSize: modelData.size
|
||||
} }
|
||||
|
||||
DelegateChoice { roleValue: "antiFlashbang"; AndroidAntiFlashbangToggle {
|
||||
required property int index
|
||||
required property var modelData
|
||||
buttonIndex: root.startingIndex + index
|
||||
buttonData: modelData
|
||||
editMode: root.editMode
|
||||
expandedSize: modelData.size > 1
|
||||
baseCellWidth: root.baseCellWidth
|
||||
baseCellHeight: root.baseCellHeight
|
||||
cellSpacing: root.spacing
|
||||
cellSize: modelData.size
|
||||
onOpenMenu: {
|
||||
root.openNightLightDialog()
|
||||
}
|
||||
} }
|
||||
|
||||
}
|
||||
|
||||
@@ -186,6 +186,8 @@ MouseArea {
|
||||
colBackgroundToggled: Appearance.colors.colSecondaryContainer
|
||||
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
|
||||
colRippleToggled: Appearance.colors.colSecondaryContainerActive
|
||||
buttonRadius: height / 2
|
||||
implicitHeight: 38
|
||||
|
||||
contentItem: RowLayout {
|
||||
MaterialSymbol {
|
||||
|
||||
@@ -30,12 +30,7 @@ def image_colorfulness(image):
|
||||
# scheme-content respects the image's colors very well, but it might
|
||||
# look too saturated, so we only use it for not very colorful images to be safe
|
||||
def pick_scheme(colorfulness):
|
||||
if colorfulness < 10:
|
||||
# return "scheme-monochrome"
|
||||
return "scheme-content"
|
||||
elif colorfulness < 20:
|
||||
return "scheme-content"
|
||||
elif colorfulness < 50:
|
||||
if colorfulness < 40:
|
||||
return "scheme-neutral"
|
||||
else:
|
||||
return "scheme-tonal-spot"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
locked_state=$(busctl --user get-property org.freedesktop.secrets \
|
||||
/org/freedesktop/secrets/collection/login \
|
||||
org.freedesktop.Secret.Collection Locked)
|
||||
if [[ "${locked_state}" == "b false" ]]; then
|
||||
echo 'Keyring is unlocked' >&2
|
||||
exit 0
|
||||
else
|
||||
echo 'Keyring is locked' >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
data=$(secret-tool lookup 'application' 'illogical-impulse')
|
||||
if [[ -z "$data" ]]; then
|
||||
if "${SCRIPT_DIR}/is_unlocked.sh"; then
|
||||
echo 'not found'
|
||||
exit 1
|
||||
else
|
||||
echo 'locked'
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
echo "$data"
|
||||
@@ -1,12 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Based on https://unix.stackexchange.com/a/602935
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Skip if already unlocked
|
||||
locked_state=$(busctl --user get-property org.freedesktop.secrets \
|
||||
/org/freedesktop/secrets/collection/login \
|
||||
org.freedesktop.Secret.Collection Locked)
|
||||
if [[ "${locked_state}" == "b false" ]]; then
|
||||
echo 'Keyring is already unlocked.' >&2
|
||||
if "${SCRIPT_DIR}/is_unlocked.sh"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -532,8 +532,6 @@ Singleton {
|
||||
modelId = modelId.toLowerCase()
|
||||
if (modelList.indexOf(modelId) !== -1) {
|
||||
const model = models[modelId]
|
||||
// Fetch API keys if needed
|
||||
if (model?.requires_key) KeyringStorage.fetchKeyringData();
|
||||
// See if policy prevents online models
|
||||
if (Config.options.policies.ai === 2 && !model.endpoint.includes("localhost")) {
|
||||
root.addMessage(
|
||||
@@ -641,6 +639,10 @@ Singleton {
|
||||
|
||||
function makeRequest() {
|
||||
const model = models[currentModelId];
|
||||
|
||||
// Fetch API keys if needed
|
||||
if (model?.requires_key && !KeyringStorage.loaded) KeyringStorage.fetchKeyringData();
|
||||
|
||||
requester.currentStrategy = root.currentApiStrategy;
|
||||
requester.currentStrategy.reset(); // Reset strategy state
|
||||
|
||||
|
||||
@@ -86,6 +86,10 @@ Singleton {
|
||||
return str.toLowerCase().replace(/\s+/g, "-");
|
||||
}
|
||||
|
||||
function getUndescoreToKebabAppName(str) {
|
||||
return str.toLowerCase().replace(/_/g, "-");
|
||||
}
|
||||
|
||||
function guessIcon(str) {
|
||||
if (!str || str.length == 0) return "image-missing";
|
||||
|
||||
@@ -124,6 +128,8 @@ Singleton {
|
||||
const kebabNormalizedGuess = getKebabNormalizedAppName(str);
|
||||
if (iconExists(kebabNormalizedGuess)) return kebabNormalizedGuess;
|
||||
|
||||
const undescoreToKebabGuess = getUndescoreToKebabAppName(str);
|
||||
if (iconExists(undescoreToKebabGuess)) return undescoreToKebabGuess;
|
||||
|
||||
// Search in desktop entries
|
||||
const iconSearchResults = Fuzzy.go(str, preppedIcons, {
|
||||
|
||||
@@ -47,7 +47,7 @@ Singleton {
|
||||
}
|
||||
})
|
||||
},
|
||||
"tagSearchTemplate": "https://yande.re/tag.json?order=count&name={{query}}*",
|
||||
"tagSearchTemplate": "https://yande.re/tag.json?order=count&limit=10&name={{query}}*",
|
||||
"tagMapFunc": (response) => {
|
||||
return response.map(item => {
|
||||
return {
|
||||
@@ -81,7 +81,7 @@ Singleton {
|
||||
}
|
||||
})
|
||||
},
|
||||
"tagSearchTemplate": "https://konachan.net/tag.json?order=count&name={{query}}*",
|
||||
"tagSearchTemplate": "https://konachan.net/tag.json?order=count&limit=10&name={{query}}*",
|
||||
"tagMapFunc": (response) => {
|
||||
return response.map(item => {
|
||||
return {
|
||||
@@ -142,7 +142,7 @@ Singleton {
|
||||
}
|
||||
})
|
||||
},
|
||||
"tagSearchTemplate": "https://danbooru.donmai.us/tags.json?search[name_matches]={{query}}*",
|
||||
"tagSearchTemplate": "https://danbooru.donmai.us/tags.json?limit=10&search[name_matches]={{query}}*",
|
||||
"tagMapFunc": (response) => {
|
||||
return response.map(item => {
|
||||
return {
|
||||
@@ -151,7 +151,6 @@ Singleton {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
"gelbooru": {
|
||||
"name": "Gelbooru",
|
||||
@@ -178,7 +177,7 @@ Singleton {
|
||||
}
|
||||
})
|
||||
},
|
||||
"tagSearchTemplate": "https://gelbooru.com/index.php?page=dapi&s=tag&q=index&json=1&orderby=count&name_pattern={{query}}%",
|
||||
"tagSearchTemplate": "https://gelbooru.com/index.php?page=dapi&s=tag&q=index&json=1&orderby=count&limit=10&name_pattern={{query}}%",
|
||||
"tagMapFunc": (response) => {
|
||||
return response.tag.map(item => {
|
||||
return {
|
||||
|
||||
@@ -86,7 +86,7 @@ Singleton {
|
||||
property int rawMaxBrightness: 100
|
||||
property real brightness
|
||||
property real brightnessMultiplier: 1.0
|
||||
property real multipliedBrightness: Math.max(0, Math.min(1, brightness * brightnessMultiplier))
|
||||
property real multipliedBrightness: Math.max(0, Math.min(1, brightness * (Config.options.light.antiFlashbang.enable ? brightnessMultiplier : 1)))
|
||||
property bool ready: false
|
||||
property bool animateChanges: !monitor.isDdc
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import Quickshell;
|
||||
@@ -89,11 +90,13 @@ Singleton {
|
||||
Process {
|
||||
id: getData
|
||||
command: [ // We need to use echo for a newline so splitparser does parse
|
||||
"bash", "-c", `echo $(secret-tool lookup 'application' 'illogical-impulse')`,
|
||||
"bash", "-c", `${Directories.scriptPath}/keyring/try_lookup.sh 2> /dev/null`,
|
||||
]
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
if(data.length === 0) return;
|
||||
stdout: StdioCollector {
|
||||
id: keyringDataOutputCollector
|
||||
onStreamFinished: {
|
||||
const data = keyringDataOutputCollector.text;
|
||||
if (data.length === 0 || !data.startsWith("{")) return;
|
||||
try {
|
||||
root.keyringData = JSON.parse(data);
|
||||
// console.log("[KeyringStorage] Keyring data fetched:", JSON.stringify(root.keyringData));
|
||||
@@ -105,13 +108,15 @@ Singleton {
|
||||
}
|
||||
}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
// console.log("[KeyringStorage] Keyring data fetch process exited with code:", exitCode);
|
||||
if (exitCode !== 0) {
|
||||
console.error("[KeyringStorage] Failed to get keyring data, reinitializing.");
|
||||
console.log("[KeyringStorage] Keyring data fetch process exited with code:", exitCode);
|
||||
if (exitCode === 1) {
|
||||
console.error("[KeyringStorage] Entry not found, initializing.");
|
||||
root.keyringData = {};
|
||||
saveKeyringData()
|
||||
}
|
||||
root.loaded = true;
|
||||
if (exitCode !== 2) {
|
||||
root.loaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user