mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-03 20:32:07 -04:00
refactor: mega refactoring of a bunch of things
This commit is contained in:
@@ -29,6 +29,7 @@ Rectangle {
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
@@ -40,5 +41,7 @@ Rectangle {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,12 @@ Rectangle {
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
// Global keyboard handler for escape key
|
||||
Keys.onEscapePressed: {
|
||||
if (dropdownMenu.visible)
|
||||
dropdownMenu.visible = false;
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
@@ -43,11 +49,12 @@ Rectangle {
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dropdown
|
||||
|
||||
|
||||
width: 180
|
||||
height: 36
|
||||
anchors.right: parent.right
|
||||
@@ -80,10 +87,12 @@ Rectangle {
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dropdownArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
@@ -97,34 +106,35 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Integrated dropdown menu with full-screen overlay
|
||||
PanelWindow {
|
||||
id: dropdownMenu
|
||||
|
||||
|
||||
property int targetX: 0
|
||||
property int targetY: 0
|
||||
|
||||
|
||||
function updatePosition() {
|
||||
var globalPos = dropdown.mapToGlobal(0, 0);
|
||||
targetX = globalPos.x;
|
||||
targetY = globalPos.y + dropdown.height + 4;
|
||||
}
|
||||
|
||||
visible: false
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
color: "transparent"
|
||||
|
||||
function updatePosition() {
|
||||
var globalPos = dropdown.mapToGlobal(0, 0);
|
||||
targetX = globalPos.x;
|
||||
targetY = globalPos.y + dropdown.height + 4;
|
||||
}
|
||||
|
||||
|
||||
// Background click interceptor (invisible)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -133,7 +143,7 @@ Rectangle {
|
||||
dropdownMenu.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Dropdown menu content
|
||||
Rectangle {
|
||||
x: dropdownMenu.targetX
|
||||
@@ -141,25 +151,25 @@ Rectangle {
|
||||
width: 180
|
||||
height: Math.min(200, root.options.length * 36 + 16)
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1.0)
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
border.width: 1
|
||||
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
clip: true
|
||||
|
||||
|
||||
ListView {
|
||||
model: root.options
|
||||
spacing: 2
|
||||
|
||||
|
||||
delegate: Rectangle {
|
||||
width: ListView.view.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: optionArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
@@ -169,9 +179,10 @@ Rectangle {
|
||||
color: root.currentValue === modelData ? Theme.primary : Theme.surfaceText
|
||||
font.weight: root.currentValue === modelData ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: optionArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
@@ -181,16 +192,15 @@ Rectangle {
|
||||
dropdownMenu.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Global keyboard handler for escape key
|
||||
Keys.onEscapePressed: {
|
||||
if (dropdownMenu.visible) {
|
||||
dropdownMenu.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,7 @@ ScrollView {
|
||||
GridView {
|
||||
id: grid
|
||||
|
||||
property int baseCellWidth: adaptiveColumns ?
|
||||
Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) :
|
||||
(width - Theme.spacingS * 2) / columns
|
||||
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
|
||||
property int baseCellHeight: baseCellWidth + 20
|
||||
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
|
||||
property int remainingSpace: width - (actualColumns * cellWidth)
|
||||
@@ -68,14 +66,8 @@ ScrollView {
|
||||
width: grid.cellWidth - cellPadding
|
||||
height: grid.cellHeight - cellPadding
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: currentIndex === index ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
mouseArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
|
||||
border.color: currentIndex === index ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) :
|
||||
Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
color: currentIndex === index ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
|
||||
border.color: currentIndex === index ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: currentIndex === index ? 2 : 1
|
||||
|
||||
Column {
|
||||
@@ -91,6 +83,7 @@ ScrollView {
|
||||
|
||||
IconImage {
|
||||
id: iconImg
|
||||
|
||||
anchors.fill: parent
|
||||
source: (model.icon) ? Quickshell.iconPath(model.icon, "") : ""
|
||||
smooth: true
|
||||
@@ -113,7 +106,9 @@ ScrollView {
|
||||
color: Theme.primary
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -128,24 +123,29 @@ ScrollView {
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (hoverUpdatesSelection) {
|
||||
if (hoverUpdatesSelection)
|
||||
currentIndex = index;
|
||||
}
|
||||
|
||||
itemHovered(index);
|
||||
}
|
||||
onClicked: {
|
||||
itemClicked(index, model);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import qs.Common
|
||||
|
||||
Text {
|
||||
id: icon
|
||||
|
||||
|
||||
property alias name: icon.text
|
||||
property alias size: icon.font.pixelSize
|
||||
property alias color: icon.color
|
||||
@@ -15,4 +15,4 @@ Text {
|
||||
color: Theme.surfaceText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,14 +55,8 @@ ScrollView {
|
||||
width: list.width
|
||||
height: itemHeight
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: ListView.isCurrentItem ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
mouseArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
|
||||
border.color: ListView.isCurrentItem ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) :
|
||||
Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
color: ListView.isCurrentItem ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
|
||||
border.color: ListView.isCurrentItem ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: ListView.isCurrentItem ? 2 : 1
|
||||
|
||||
Row {
|
||||
@@ -77,6 +71,7 @@ ScrollView {
|
||||
|
||||
IconImage {
|
||||
id: iconImg
|
||||
|
||||
anchors.fill: parent
|
||||
source: (model.icon) ? Quickshell.iconPath(model.icon, "") : ""
|
||||
smooth: true
|
||||
@@ -99,7 +94,9 @@ ScrollView {
|
||||
color: Theme.primary
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -124,25 +121,31 @@ ScrollView {
|
||||
elide: Text.ElideRight
|
||||
visible: showDescription && model.comment && model.comment.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (hoverUpdatesSelection) {
|
||||
if (hoverUpdatesSelection)
|
||||
listView.currentIndex = index;
|
||||
}
|
||||
|
||||
itemHovered(index);
|
||||
}
|
||||
onClicked: {
|
||||
itemClicked(index, model);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,139 +6,139 @@ import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
|
||||
property string currentLocation: ""
|
||||
property string placeholderText: "Search for a location..."
|
||||
|
||||
signal locationSelected(string displayName, string coordinates)
|
||||
|
||||
width: parent.width
|
||||
height: searchInputField.height + (searchDropdown.visible ? searchDropdown.height : 0)
|
||||
|
||||
property bool _internalChange: false
|
||||
property bool isLoading: false
|
||||
property string helperTextState: "default" // "default", "prompt", "searching", "found", "not_found"
|
||||
property string currentSearchText: ""
|
||||
|
||||
signal locationSelected(string displayName, string coordinates)
|
||||
|
||||
function resetSearchState() {
|
||||
locationSearchTimer.stop();
|
||||
dropdownHideTimer.stop();
|
||||
if (locationSearcher.running)
|
||||
locationSearcher.running = false;
|
||||
|
||||
isLoading = false;
|
||||
searchResultsModel.clear();
|
||||
helperTextState = "default";
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
height: searchInputField.height + (searchDropdown.visible ? searchDropdown.height : 0)
|
||||
|
||||
ListModel {
|
||||
id: searchResultsModel
|
||||
}
|
||||
|
||||
function resetSearchState() {
|
||||
locationSearchTimer.stop()
|
||||
dropdownHideTimer.stop()
|
||||
if (locationSearcher.running) {
|
||||
locationSearcher.running = false;
|
||||
}
|
||||
isLoading = false
|
||||
searchResultsModel.clear()
|
||||
helperTextState = "default"
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: locationSearchTimer
|
||||
|
||||
interval: 500
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (locationInput.text.length > 2) {
|
||||
if (locationSearcher.running) {
|
||||
locationSearcher.running = false
|
||||
}
|
||||
|
||||
searchResultsModel.clear()
|
||||
root.isLoading = true
|
||||
root.helperTextState = "searching"
|
||||
|
||||
const searchLocation = locationInput.text
|
||||
root.currentSearchText = searchLocation
|
||||
const encodedLocation = encodeURIComponent(searchLocation)
|
||||
const curlCommand = `curl -s --connect-timeout 5 --max-time 10 'https://nominatim.openstreetmap.org/search?q=${encodedLocation}&format=json&limit=5&addressdetails=1'`
|
||||
|
||||
locationSearcher.command = ["bash", "-c", curlCommand]
|
||||
locationSearcher.running = true
|
||||
if (locationSearcher.running)
|
||||
locationSearcher.running = false;
|
||||
|
||||
searchResultsModel.clear();
|
||||
root.isLoading = true;
|
||||
root.helperTextState = "searching";
|
||||
const searchLocation = locationInput.text;
|
||||
root.currentSearchText = searchLocation;
|
||||
const encodedLocation = encodeURIComponent(searchLocation);
|
||||
const curlCommand = `curl -s --connect-timeout 5 --max-time 10 'https://nominatim.openstreetmap.org/search?q=${encodedLocation}&format=json&limit=5&addressdetails=1'`;
|
||||
locationSearcher.command = ["bash", "-c", curlCommand];
|
||||
locationSearcher.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Timer {
|
||||
id: dropdownHideTimer
|
||||
|
||||
interval: 200
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (!locationInput.getActiveFocus() && !searchDropdown.hovered) {
|
||||
root.resetSearchState()
|
||||
}
|
||||
if (!locationInput.getActiveFocus() && !searchDropdown.hovered)
|
||||
root.resetSearchState();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: locationSearcher
|
||||
|
||||
command: ["bash", "-c", "echo"]
|
||||
running: false
|
||||
|
||||
onExited: (exitCode) => {
|
||||
root.isLoading = false;
|
||||
if (exitCode !== 0) {
|
||||
searchResultsModel.clear();
|
||||
root.helperTextState = "not_found";
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (root.currentSearchText !== locationInput.text) {
|
||||
return
|
||||
}
|
||||
|
||||
const raw = text.trim()
|
||||
root.isLoading = false
|
||||
searchResultsModel.clear()
|
||||
if (root.currentSearchText !== locationInput.text)
|
||||
return ;
|
||||
|
||||
const raw = text.trim();
|
||||
root.isLoading = false;
|
||||
searchResultsModel.clear();
|
||||
if (!raw || raw[0] !== "[") {
|
||||
root.helperTextState = "not_found"
|
||||
return
|
||||
root.helperTextState = "not_found";
|
||||
return ;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(raw)
|
||||
const data = JSON.parse(raw);
|
||||
if (data.length === 0) {
|
||||
root.helperTextState = "not_found"
|
||||
return
|
||||
root.helperTextState = "not_found";
|
||||
return ;
|
||||
}
|
||||
|
||||
for (let i = 0; i < Math.min(data.length, 5); i++) {
|
||||
const location = data[i]
|
||||
const location = data[i];
|
||||
if (location.display_name && location.lat && location.lon) {
|
||||
const parts = location.display_name.split(', ')
|
||||
let cleanName = parts[0]
|
||||
const parts = location.display_name.split(', ');
|
||||
let cleanName = parts[0];
|
||||
if (parts.length > 1) {
|
||||
const state = parts[parts.length - 2]
|
||||
if (state && state !== cleanName) {
|
||||
cleanName += `, ${state}`
|
||||
}
|
||||
const state = parts[parts.length - 2];
|
||||
if (state && state !== cleanName)
|
||||
cleanName += `, ${state}`;
|
||||
|
||||
}
|
||||
const query = `${location.lat},${location.lon}`
|
||||
searchResultsModel.append({ "name": cleanName, "query": query })
|
||||
const query = `${location.lat},${location.lon}`;
|
||||
searchResultsModel.append({
|
||||
"name": cleanName,
|
||||
"query": query
|
||||
});
|
||||
}
|
||||
}
|
||||
root.helperTextState = "found"
|
||||
root.helperTextState = "found";
|
||||
} catch (e) {
|
||||
root.helperTextState = "not_found"
|
||||
root.helperTextState = "not_found";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode) => {
|
||||
root.isLoading = false
|
||||
if (exitCode !== 0) {
|
||||
searchResultsModel.clear()
|
||||
root.helperTextState = "not_found"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Search input field
|
||||
Item {
|
||||
id: searchInputField
|
||||
|
||||
width: parent.width
|
||||
height: 48
|
||||
|
||||
|
||||
DankTextField {
|
||||
id: locationInput
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
leftIconName: "search"
|
||||
@@ -147,123 +147,138 @@ Item {
|
||||
backgroundColor: Theme.surfaceVariant
|
||||
normalBorderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
focusedBorderColor: Theme.primary
|
||||
|
||||
onTextEdited: {
|
||||
if (root._internalChange) return
|
||||
if (root._internalChange)
|
||||
return ;
|
||||
|
||||
if (getActiveFocus()) {
|
||||
if (text.length > 2) {
|
||||
root.isLoading = true
|
||||
root.helperTextState = "searching"
|
||||
locationSearchTimer.restart()
|
||||
root.isLoading = true;
|
||||
root.helperTextState = "searching";
|
||||
locationSearchTimer.restart();
|
||||
} else {
|
||||
root.resetSearchState()
|
||||
root.helperTextState = "prompt"
|
||||
root.resetSearchState();
|
||||
root.helperTextState = "prompt";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onFocusStateChanged: (hasFocus) => {
|
||||
if (hasFocus) {
|
||||
dropdownHideTimer.stop()
|
||||
if (text.length <= 2) {
|
||||
root.helperTextState = "prompt"
|
||||
}
|
||||
}
|
||||
else {
|
||||
dropdownHideTimer.start()
|
||||
dropdownHideTimer.stop();
|
||||
if (text.length <= 2)
|
||||
root.helperTextState = "prompt";
|
||||
|
||||
} else {
|
||||
dropdownHideTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Status icon overlay
|
||||
DankIcon {
|
||||
name: {
|
||||
if (root.isLoading) return "hourglass_empty"
|
||||
if (searchResultsModel.count > 0) return "check_circle"
|
||||
if (locationInput.getActiveFocus() && locationInput.text.length > 2 && !root.isLoading) return "error"
|
||||
return ""
|
||||
if (root.isLoading)
|
||||
return "hourglass_empty";
|
||||
|
||||
if (searchResultsModel.count > 0)
|
||||
return "check_circle";
|
||||
|
||||
if (locationInput.getActiveFocus() && locationInput.text.length > 2 && !root.isLoading)
|
||||
return "error";
|
||||
|
||||
return "";
|
||||
}
|
||||
size: Theme.iconSize - 4
|
||||
color: {
|
||||
if (root.isLoading) return Theme.surfaceVariantText
|
||||
if (searchResultsModel.count > 0) return Theme.success || Theme.primary
|
||||
if (locationInput.getActiveFocus() && locationInput.text.length > 2) return Theme.error
|
||||
return "transparent"
|
||||
if (root.isLoading)
|
||||
return Theme.surfaceVariantText;
|
||||
|
||||
if (searchResultsModel.count > 0)
|
||||
return Theme.success || Theme.primary;
|
||||
|
||||
if (locationInput.getActiveFocus() && locationInput.text.length > 2)
|
||||
return Theme.error;
|
||||
|
||||
return "transparent";
|
||||
}
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: (locationInput.getActiveFocus() && locationInput.text.length > 2) ? 1.0 : 0.0
|
||||
|
||||
opacity: (locationInput.getActiveFocus() && locationInput.text.length > 2) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Search results dropdown
|
||||
Rectangle {
|
||||
id: searchDropdown
|
||||
|
||||
property bool hovered: false
|
||||
|
||||
width: parent.width
|
||||
height: Math.min(Math.max(searchResultsModel.count * 38 + Theme.spacingS * 2, 50), 200)
|
||||
|
||||
y: searchInputField.height
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
border.width: 1
|
||||
visible: locationInput.getActiveFocus() && locationInput.text.length > 2 && (searchResultsModel.count > 0 || root.isLoading)
|
||||
|
||||
property bool hovered: false
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
parent.hovered = true
|
||||
dropdownHideTimer.stop()
|
||||
parent.hovered = true;
|
||||
dropdownHideTimer.stop();
|
||||
}
|
||||
onExited: {
|
||||
parent.hovered = false
|
||||
if (!locationInput.getActiveFocus()) {
|
||||
dropdownHideTimer.start()
|
||||
}
|
||||
parent.hovered = false;
|
||||
if (!locationInput.getActiveFocus())
|
||||
dropdownHideTimer.start();
|
||||
|
||||
}
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
|
||||
|
||||
ListView {
|
||||
id: searchResultsList
|
||||
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
model: searchResultsModel
|
||||
spacing: 2
|
||||
|
||||
|
||||
delegate: Rectangle {
|
||||
width: searchResultsList.width
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: resultMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
|
||||
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
|
||||
DankIcon {
|
||||
name: "place"
|
||||
size: Theme.iconSize - 6
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
Text {
|
||||
text: model.name || "Unknown"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
@@ -272,30 +287,31 @@ Item {
|
||||
elide: Text.ElideRight
|
||||
width: parent.width - 30
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: resultMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root._internalChange = true
|
||||
const selectedName = model.name
|
||||
const selectedQuery = model.query
|
||||
|
||||
locationInput.text = selectedName
|
||||
root.locationSelected(selectedName, selectedQuery)
|
||||
|
||||
root.resetSearchState()
|
||||
locationInput.setFocus(false)
|
||||
root._internalChange = false
|
||||
root._internalChange = true;
|
||||
const selectedName = model.name;
|
||||
const selectedQuery = model.query;
|
||||
locationInput.text = selectedName;
|
||||
root.locationSelected(selectedName, selectedQuery);
|
||||
root.resetSearchState();
|
||||
locationInput.setFocus(false);
|
||||
root._internalChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Show message when no results
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -304,6 +320,9 @@ Item {
|
||||
color: Theme.surfaceVariantText
|
||||
visible: searchResultsList.count === 0 && locationInput.text.length > 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,33 +10,29 @@ PanelWindow {
|
||||
|
||||
// Core properties
|
||||
property alias content: contentLoader.sourceComponent
|
||||
|
||||
// Sizing - can use fixed or relative to screen
|
||||
property real width: 400
|
||||
property real height: 300
|
||||
|
||||
// Screen-relative sizing helpers
|
||||
readonly property real screenWidth: screen ? screen.width : 1920
|
||||
readonly property real screenHeight: screen ? screen.height : 1080
|
||||
|
||||
// Background behavior
|
||||
property bool showBackground: true
|
||||
property real backgroundOpacity: 0.5
|
||||
|
||||
// Positioning
|
||||
property string positioning: "center" // "center", "top-right", "custom"
|
||||
property string positioning: "center"
|
||||
// "center", "top-right", "custom"
|
||||
property point customPosition: Qt.point(0, 0)
|
||||
|
||||
// Focus management
|
||||
property string keyboardFocus: "ondemand" // "ondemand", "exclusive", "none"
|
||||
property string keyboardFocus: "ondemand"
|
||||
// "ondemand", "exclusive", "none"
|
||||
property bool closeOnEscapeKey: true
|
||||
property bool closeOnBackgroundClick: true
|
||||
|
||||
// Animation
|
||||
property string animationType: "scale" // "scale", "slide", "fade"
|
||||
property string animationType: "scale"
|
||||
// "scale", "slide", "fade"
|
||||
property int animationDuration: Theme.mediumDuration
|
||||
property var animationEasing: Theme.emphasizedEasing
|
||||
|
||||
// Styling
|
||||
property color backgroundColor: Theme.surfaceContainer
|
||||
property color borderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
@@ -49,11 +45,47 @@ PanelWindow {
|
||||
signal dialogClosed()
|
||||
signal backgroundClicked()
|
||||
|
||||
// Convenience functions
|
||||
function open() {
|
||||
visible = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
visible = !visible;
|
||||
}
|
||||
|
||||
// PanelWindow configuration
|
||||
// visible property is inherited from PanelWindow
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
switch (root.keyboardFocus) {
|
||||
case "exclusive":
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
case "none":
|
||||
return WlrKeyboardFocus.None;
|
||||
default:
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
}
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (root.visible) {
|
||||
opened();
|
||||
} else {
|
||||
// Properly cleanup text input surfaces
|
||||
if (Qt.inputMethod) {
|
||||
Qt.inputMethod.hide();
|
||||
Qt.inputMethod.reset();
|
||||
}
|
||||
dialogClosed();
|
||||
}
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
@@ -61,103 +93,77 @@ PanelWindow {
|
||||
bottom: true
|
||||
}
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
switch (root.keyboardFocus) {
|
||||
case "exclusive": return WlrKeyboardFocus.Exclusive
|
||||
case "none": return WlrKeyboardFocus.None
|
||||
default: return WlrKeyboardFocus.OnDemand
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (root.visible) {
|
||||
opened()
|
||||
} else {
|
||||
// Properly cleanup text input surfaces
|
||||
if (Qt.inputMethod) {
|
||||
Qt.inputMethod.hide()
|
||||
Qt.inputMethod.reset()
|
||||
}
|
||||
dialogClosed()
|
||||
}
|
||||
}
|
||||
|
||||
// Background overlay
|
||||
Rectangle {
|
||||
id: background
|
||||
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
opacity: root.showBackground ? (root.visible ? root.backgroundOpacity : 0) : 0
|
||||
visible: root.showBackground
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.closeOnBackgroundClick
|
||||
onClicked: (mouse) => {
|
||||
var localPos = mapToItem(contentContainer, mouse.x, mouse.y);
|
||||
if (localPos.x < 0 || localPos.x > contentContainer.width || localPos.y < 0 || localPos.y > contentContainer.height)
|
||||
root.backgroundClicked();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: root.animationEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.closeOnBackgroundClick
|
||||
onClicked: (mouse) => {
|
||||
var localPos = mapToItem(contentContainer, mouse.x, mouse.y)
|
||||
if (localPos.x < 0 || localPos.x > contentContainer.width ||
|
||||
localPos.y < 0 || localPos.y > contentContainer.height) {
|
||||
root.backgroundClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main content container
|
||||
Rectangle {
|
||||
id: contentContainer
|
||||
|
||||
|
||||
width: root.width
|
||||
height: root.height
|
||||
|
||||
// Positioning
|
||||
anchors.centerIn: positioning === "center" ? parent : undefined
|
||||
x: {
|
||||
if (positioning === "top-right") {
|
||||
return Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL)
|
||||
} else if (positioning === "custom") {
|
||||
return root.customPosition.x
|
||||
}
|
||||
return 0 // Will be overridden by anchors.centerIn when positioning === "center"
|
||||
if (positioning === "top-right")
|
||||
return Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL);
|
||||
else if (positioning === "custom")
|
||||
return root.customPosition.x;
|
||||
return 0; // Will be overridden by anchors.centerIn when positioning === "center"
|
||||
}
|
||||
y: {
|
||||
if (positioning === "top-right") {
|
||||
return Theme.barHeight + Theme.spacingXS
|
||||
} else if (positioning === "custom") {
|
||||
return root.customPosition.y
|
||||
}
|
||||
return 0 // Will be overridden by anchors.centerIn when positioning === "center"
|
||||
if (positioning === "top-right")
|
||||
return Theme.barHeight + Theme.spacingXS;
|
||||
else if (positioning === "custom")
|
||||
return root.customPosition.y;
|
||||
return 0; // Will be overridden by anchors.centerIn when positioning === "center"
|
||||
}
|
||||
|
||||
color: root.backgroundColor
|
||||
radius: root.cornerRadius
|
||||
border.color: root.borderColor
|
||||
border.width: root.borderWidth
|
||||
layer.enabled: root.enableShadow
|
||||
|
||||
// Animation properties
|
||||
opacity: root.visible ? 1 : 0
|
||||
scale: {
|
||||
if (root.animationType === "scale") {
|
||||
return root.visible ? 1 : 0.9
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if (root.animationType === "scale")
|
||||
return root.visible ? 1 : 0.9;
|
||||
|
||||
return 1;
|
||||
}
|
||||
// Transform for slide animation
|
||||
transform: root.animationType === "slide" ? slideTransform : null
|
||||
|
||||
Translate {
|
||||
id: slideTransform
|
||||
|
||||
x: root.visible ? 0 : 15
|
||||
y: root.visible ? 0 : -30
|
||||
}
|
||||
@@ -165,6 +171,7 @@ PanelWindow {
|
||||
// Content area
|
||||
Loader {
|
||||
id: contentLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: true
|
||||
asynchronous: false
|
||||
@@ -176,14 +183,17 @@ PanelWindow {
|
||||
duration: root.animationDuration
|
||||
easing.type: root.animationEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
enabled: root.animationType === "scale"
|
||||
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: root.animationEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Shadow effect
|
||||
@@ -195,14 +205,15 @@ PanelWindow {
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.3)
|
||||
shadowOpacity: 0.3
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Keyboard handling
|
||||
FocusScope {
|
||||
id: focusScope
|
||||
|
||||
anchors.fill: parent
|
||||
visible: root.visible // Only active when the modal is visible
|
||||
|
||||
Keys.onEscapePressed: (event) => {
|
||||
if (root.closeOnEscapeKey) {
|
||||
root.visible = false;
|
||||
@@ -211,16 +222,4 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience functions
|
||||
function open() {
|
||||
visible = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible = false
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
visible = !visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,12 @@ Item {
|
||||
property bool enabled: true
|
||||
property string unit: "%"
|
||||
property bool showValue: true
|
||||
property bool isDragging: false
|
||||
|
||||
signal sliderValueChanged(int newValue)
|
||||
signal sliderDragFinished(int finalValue)
|
||||
|
||||
height: 80
|
||||
|
||||
property bool isDragging: false
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -18,6 +18,7 @@ Item {
|
||||
|
||||
Row {
|
||||
id: tabRow
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: tabBar.spacing
|
||||
|
||||
@@ -30,19 +31,14 @@ Item {
|
||||
property bool hasIcon: tabBar.showIcons && modelData.icon && modelData.icon.length > 0
|
||||
property bool hasText: modelData.text && modelData.text.length > 0
|
||||
|
||||
width: tabBar.equalWidthTabs ?
|
||||
(tabBar.width - tabBar.spacing * (tabCount - 1)) / tabCount :
|
||||
contentRow.implicitWidth + Theme.spacingM * 2
|
||||
width: tabBar.equalWidthTabs ? (tabBar.width - tabBar.spacing * (tabCount - 1)) / tabCount : contentRow.implicitWidth + Theme.spacingM * 2
|
||||
height: tabBar.tabHeight
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: isActive ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
tabArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
|
||||
"transparent"
|
||||
color: isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : tabArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
@@ -62,16 +58,18 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: parent.parent.hasText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: tabArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
tabBar.currentIndex = index
|
||||
tabBar.tabClicked(index)
|
||||
tabBar.currentIndex = index;
|
||||
tabBar.tabClicked(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +78,13 @@ Item {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ Item {
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
|
||||
anchors.fill: parent
|
||||
radius: toggle.text ? Theme.cornerRadius : 0
|
||||
color: toggle.text ? (toggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)) : "transparent"
|
||||
@@ -26,6 +27,7 @@ Item {
|
||||
|
||||
Row {
|
||||
id: textRow
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: toggleTrack.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -53,11 +55,14 @@ Item {
|
||||
width: Math.min(implicitWidth, toggle.width - 120)
|
||||
visible: toggle.description.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: toggleTrack
|
||||
|
||||
width: toggle.text ? 48 : parent.width
|
||||
height: toggle.text ? 24 : parent.height
|
||||
anchors.right: parent.right
|
||||
@@ -69,6 +74,7 @@ Item {
|
||||
|
||||
Rectangle {
|
||||
id: toggleHandle
|
||||
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
@@ -76,13 +82,6 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: toggle.checked ? parent.width - width - 2 : 2
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width + 2
|
||||
@@ -93,12 +92,22 @@ Item {
|
||||
border.width: 1
|
||||
z: -1
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: toggleArea
|
||||
|
||||
anchors.fill: toggle.text ? toggle : toggleTrack
|
||||
hoverEnabled: true
|
||||
cursorShape: toggle.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
@@ -110,4 +119,4 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,24 +10,29 @@ IconImage {
|
||||
|
||||
property string colorOverride: ""
|
||||
property real brightnessOverride: 0.5
|
||||
property real contrastOverride: 1.0
|
||||
property real contrastOverride: 1
|
||||
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
layer.enabled: colorOverride !== ""
|
||||
|
||||
Process {
|
||||
running: true
|
||||
command: ["sh", "-c", ". /etc/os-release && echo $LOGO"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: () => {
|
||||
root.source = Quickshell.iconPath(this.text.trim());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
colorization: 1
|
||||
colorizationColor: colorOverride
|
||||
brightness: brightnessOverride
|
||||
contrast: contrastOverride
|
||||
}
|
||||
Process {
|
||||
running: true
|
||||
command: ["sh", "-c", ". /etc/os-release && echo $LOGO"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: () => {
|
||||
root.source = Quickshell.iconPath(this.text.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user