fab menu and model selector
This commit is contained in:
@@ -36,7 +36,7 @@ Item {
|
|||||||
CustomListView {
|
CustomListView {
|
||||||
id: list
|
id: list
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: modelsContainer.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: header.bottom
|
anchors.top: header.bottom
|
||||||
@@ -85,10 +85,161 @@ Item {
|
|||||||
onClicked: content => root.loadChatRequest(content, index)
|
onClicked: content => root.loadChatRequest(content, index)
|
||||||
onRemove: content => root.deleteChatRequest(content)
|
onRemove: content => root.deleteChatRequest(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
onTapped: {
|
||||||
|
root.focus = true;
|
||||||
|
ChatState.fabExpanded = false;
|
||||||
|
modelsContainer.expanded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomClippingRect {
|
||||||
|
id: modelsContainer
|
||||||
|
|
||||||
|
property bool expanded: false
|
||||||
|
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: fabRoot.left
|
||||||
|
anchors.margins: Tokens.padding.small
|
||||||
|
anchors.rightMargin: Tokens.spacing.small
|
||||||
|
|
||||||
|
implicitHeight: expanded ? 400 : fabRoot.implicitHeight
|
||||||
|
radius: modelLayer.pressed ? Tokens.rounding.small : modelName.implicitHeight / 2
|
||||||
|
|
||||||
|
function prettyModelName(): string {
|
||||||
|
const pathList = Chat.model.split("\/");
|
||||||
|
const name = pathList[pathList.length - 1];
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on radius {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: modelName
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
implicitHeight: fabRoot.implicitHeight
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
text: modelsContainer.prettyModelName()
|
||||||
|
color: Colors.palette.m3onSurfaceVariant
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Tokens.padding.large
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
color: Colors.palette.m3onSurfaceVariant
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Tokens.padding.large
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: modelsContainer.expanded ? "unfold_less" : "unfold_more"
|
||||||
|
animate: true
|
||||||
|
font.pointSize: Tokens.font.size.large
|
||||||
|
}
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
id: modelLayer
|
||||||
|
onClicked: {
|
||||||
|
ChatState.fabExpanded = false;
|
||||||
|
modelsContainer.expanded = !modelsContainer.expanded;
|
||||||
|
}
|
||||||
|
radius: modelsContainer.radius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalFadeListView {
|
||||||
|
id: modelsList
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.top: modelName.bottom
|
||||||
|
anchors.margins: Tokens.padding.medium
|
||||||
|
anchors.topMargin: Tokens.spacing.medium
|
||||||
|
model: Chat.availableModels
|
||||||
|
opacity: modelsContainer.expanded ? 1 : 0
|
||||||
|
currentIndex: Chat.availableModels.indexOf(Chat.model)
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomScrollBar.vertical: CustomScrollBar {
|
||||||
|
flickable: modelsList
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: CustomRect {
|
||||||
|
id: model
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
readonly property bool selected: model.ListView.view.currentIndex === model.index
|
||||||
|
|
||||||
|
implicitWidth: ListView.view.width
|
||||||
|
implicitHeight: layout.implicitHeight + Tokens.padding.small * 2
|
||||||
|
radius: stateLayer.pressed ? Tokens.rounding.extraSmall : selected ? Tokens.rounding.largeIncreased : Tokens.rounding.medium
|
||||||
|
color: Qt.alpha(Colors.palette.m3tertiaryContainer, selected ? 1 : 0)
|
||||||
|
|
||||||
|
Behavior on radius {
|
||||||
|
Anim {
|
||||||
|
type: Anim.SlowEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: layout
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Tokens.padding.small
|
||||||
|
anchors.leftMargin: Tokens.padding.large
|
||||||
|
anchors.rightMargin: Tokens.padding.large
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
text: model.modelData
|
||||||
|
color: model.selected ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
visible: model.selected
|
||||||
|
color: Colors.palette.m3onTertiaryContainer
|
||||||
|
font.pointSize: Tokens.font.size.normal
|
||||||
|
opacity: model.selected ? 1 : 0
|
||||||
|
text: "check"
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
type: Anim.SlowEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
id: stateLayer
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
Chat.selectModel(model.modelData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton {
|
IconButton {
|
||||||
id: newChatBtn
|
id: fabRoot
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -96,17 +247,168 @@ Item {
|
|||||||
padding: 8
|
padding: 8
|
||||||
font.pointSize: Math.round(18 * 1.2)
|
font.pointSize: Math.round(18 * 1.2)
|
||||||
icon: "add"
|
icon: "add"
|
||||||
isRound: true
|
isRound: ChatState.fabExpanded
|
||||||
|
|
||||||
|
label.transform: Rotation {
|
||||||
|
origin.y: fabRoot.label.height / 2
|
||||||
|
origin.x: fabRoot.label.width / 2
|
||||||
|
angle: ChatState.fabExpanded ? 135 : 0
|
||||||
|
|
||||||
|
Behavior on angle {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.newChatRequest();
|
modelsContainer.expanded = false;
|
||||||
|
ChatState.fabExpanded = !ChatState.fabExpanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
Elevation {
|
Elevation {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
level: newChatBtn.stateLayer.containsMouse ? 4 : 3
|
level: fabRoot.stateLayer.containsMouse ? 4 : 3
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
z: -1
|
z: -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: fabMenu
|
||||||
|
|
||||||
|
anchors.bottom: fabRoot.top
|
||||||
|
anchors.right: fabRoot.right
|
||||||
|
anchors.bottomMargin: Tokens.padding.medium
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: fabRep
|
||||||
|
|
||||||
|
model: ListModel {
|
||||||
|
ListElement {
|
||||||
|
name: "new chat"
|
||||||
|
icon: "add"
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
name: "pop out in window"
|
||||||
|
icon: "open_in_new"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomClippingRect {
|
||||||
|
id: fabMenuItem
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
implicitHeight: fabMenuItemInner.implicitHeight + Tokens.padding.medium * 2
|
||||||
|
|
||||||
|
radius: sLayer.pressed ? Tokens.rounding.small : implicitHeight / 2
|
||||||
|
color: Colors.palette.m3primaryContainer
|
||||||
|
visible: !(modelData.name === "pop out in window" && ChatState.isWindow)
|
||||||
|
|
||||||
|
Behavior on radius {
|
||||||
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
states: State {
|
||||||
|
name: "visible"
|
||||||
|
when: ChatState.fabExpanded
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
fabMenuItem.implicitWidth: fabMenuItemInner.implicitWidth + Tokens.padding.large * 2
|
||||||
|
fabMenuItem.opacity: 1
|
||||||
|
fabMenuItemInner.opacity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transitions: [
|
||||||
|
Transition {
|
||||||
|
to: "visible"
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
PauseAnimation {
|
||||||
|
duration: (fabRep.count - 1 - fabMenuItem.index) * 50
|
||||||
|
}
|
||||||
|
ParallelAnimation {
|
||||||
|
Anim {
|
||||||
|
property: "implicitWidth"
|
||||||
|
type: Anim.FastSpatial
|
||||||
|
}
|
||||||
|
Anim {
|
||||||
|
property: "opacity"
|
||||||
|
duration: Tokens.anim.durations.small
|
||||||
|
easing: Tokens.anim.standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Transition {
|
||||||
|
from: "visible"
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
PauseAnimation {
|
||||||
|
duration: fabMenuItem.index * Tokens.anim.durations.small / 8
|
||||||
|
}
|
||||||
|
ParallelAnimation {
|
||||||
|
Anim {
|
||||||
|
property: "implicitWidth"
|
||||||
|
type: Anim.FastSpatial
|
||||||
|
}
|
||||||
|
Anim {
|
||||||
|
property: "opacity"
|
||||||
|
duration: Tokens.anim.durations.small
|
||||||
|
easing: Tokens.anim.standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: fabMenuItemInner
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Tokens.spacing.medium
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
text: fabMenuItem.modelData.icon
|
||||||
|
color: Colors.palette.m3onPrimaryContainer
|
||||||
|
font.pointSize: Tokens.font.size.large
|
||||||
|
fill: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
animate: true
|
||||||
|
text: fabMenuItem.modelData.name
|
||||||
|
font.capitalization: Font.Capitalize
|
||||||
|
color: Colors.palette.m3onPrimaryContainer
|
||||||
|
Layout.preferredWidth: implicitWidth
|
||||||
|
|
||||||
|
Behavior on Layout.preferredWidth {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
id: sLayer
|
||||||
|
onClicked: {
|
||||||
|
const name = fabMenuItem.modelData.name;
|
||||||
|
|
||||||
|
if (name === "new chat")
|
||||||
|
root.newChatRequest();
|
||||||
|
else if (name === "pop out in window")
|
||||||
|
Detach.create();
|
||||||
|
|
||||||
|
ChatState.fabExpanded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,22 +31,6 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
icon: "open_in_new"
|
|
||||||
anchors.margins: Tokens.padding.small
|
|
||||||
isRound: true
|
|
||||||
padding: 8
|
|
||||||
font.pointSize: Math.round(18 * 1.2)
|
|
||||||
visible: !ChatState.isWindow
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
Detach.create();
|
|
||||||
Visibilities.getForActive().sidebar = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: chatList
|
id: chatList
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pragma Singleton
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import ZShell.Llm
|
import ZShell.Llm
|
||||||
|
import qs.Helpers
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -11,6 +12,11 @@ Singleton {
|
|||||||
readonly property string title: chatSession ? chatSession.title : "Chat"
|
readonly property string title: chatSession ? chatSession.title : "Chat"
|
||||||
property int currentIdx: -1
|
property int currentIdx: -1
|
||||||
property bool inChat: false
|
property bool inChat: false
|
||||||
|
property bool fabExpanded: false
|
||||||
property bool isWindow: false
|
property bool isWindow: false
|
||||||
property ShellScreen screen
|
property ShellScreen screen
|
||||||
|
property bool sidebarVisible: Visibilities.getForActive().sidebar
|
||||||
|
|
||||||
|
onSidebarVisibleChanged: if (!sidebarVisible)
|
||||||
|
fabExpanded = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pragma Singleton
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
|
import qs.Helpers
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
@@ -10,6 +11,7 @@ Singleton {
|
|||||||
|
|
||||||
function create(parent: Item, props: var): void {
|
function create(parent: Item, props: var): void {
|
||||||
chatComp.createObject(parent ?? dummy, props);
|
chatComp.createObject(parent ?? dummy, props);
|
||||||
|
Visibilities.getForActive().sidebar = false;
|
||||||
ChatState.isWindow = true;
|
ChatState.isWindow = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user