Merge branch 'main' of github.com:noctalia-dev/noctalia-shell
This commit is contained in:
commit
b908dc0ed2
7 changed files with 105 additions and 73 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
|
|
@ -130,4 +131,14 @@ Item {
|
|||
sidePanel.toggle(Quickshell.screens[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Wallpaper IPC: trigger a new random wallpaper
|
||||
IpcHandler {
|
||||
target: "wallpaper"
|
||||
function random() {
|
||||
if (Settings.data.wallpaper.enabled) {
|
||||
WallpaperService.setRandomWallpaper()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,12 +23,7 @@ QtObject {
|
|||
const normalized = (preview || "").replace(/\s+/g, ' ').trim()
|
||||
const lines = normalized.split(/\n+/)
|
||||
const title = (lines[0] || "Text").slice(0, 60)
|
||||
let subtitle = ""
|
||||
if (lines.length > 1) {
|
||||
subtitle = lines[1].slice(0, 80)
|
||||
} else {
|
||||
subtitle = `${normalized.length} chars`
|
||||
}
|
||||
const subtitle = (lines.length > 1) ? lines[1].slice(0, 80) : ""
|
||||
return {
|
||||
"title": title,
|
||||
"subtitle": subtitle
|
||||
|
|
@ -39,7 +34,7 @@ QtObject {
|
|||
if (item.isImage) {
|
||||
const meta = parseImageMeta(item.preview)
|
||||
const title = meta ? `Image ${meta.w}×${meta.h}` : "Image"
|
||||
const subtitle = meta ? `${meta.size} · ${meta.fmt}` : (item.preview || "")
|
||||
const subtitle = ""
|
||||
return {
|
||||
"isClipboard": true,
|
||||
"name": title,
|
||||
|
|
@ -54,7 +49,7 @@ QtObject {
|
|||
return {
|
||||
"isClipboard": true,
|
||||
"name": parts.title,
|
||||
"content": parts.subtitle,
|
||||
"content": "",
|
||||
"icon": "content_paste",
|
||||
"type": 'text',
|
||||
"id": item.id
|
||||
|
|
|
|||
|
|
@ -52,6 +52,17 @@ NPanel {
|
|||
searchText = ""
|
||||
selectedIndex = 0
|
||||
}
|
||||
// Focus search input on open and place cursor at end
|
||||
Qt.callLater(() => {
|
||||
if (searchInputBox && searchInputBox.inputItem) {
|
||||
searchInputBox.inputItem.forceActiveFocus()
|
||||
if (searchText && searchText.length > 0) {
|
||||
searchInputBox.inputItem.cursorPosition = searchText.length
|
||||
} else {
|
||||
searchInputBox.inputItem.cursorPosition = 0
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
|
|
@ -244,74 +255,48 @@ NPanel {
|
|||
anchors.margins: Style.marginL * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Search bar
|
||||
Rectangle {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(Style.barHeight * scaling)
|
||||
Layout.bottomMargin: Style.marginM * scaling
|
||||
radius: Style.radiusM * scaling
|
||||
color: Color.mSurface
|
||||
border.color: searchInput.activeFocus ? Color.mSecondary : Color.mOutline
|
||||
border.width: Math.max(1, searchInput.activeFocus ? Style.borderM * scaling : Style.borderS * scaling)
|
||||
|
||||
// Wrapper ensures the input stretches to full width under RowLayout
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM * scaling
|
||||
id: searchInputWrap
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(Style.barHeight * scaling)
|
||||
|
||||
NIcon {
|
||||
id: searchIcon
|
||||
text: "search"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
color: searchInput.activeFocus ? Color.mPrimary : Color.mOnSurface
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: searchInput
|
||||
placeholderText: searchText === "" ? "Search applications... (use > to view commands)" : "Search applications..."
|
||||
color: Color.mOnSurface
|
||||
placeholderTextColor: Color.mOnSurfaceVariant
|
||||
background: null
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
anchors.left: searchIcon.right
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
NTextInput {
|
||||
id: searchInputBox
|
||||
anchors.fill: parent
|
||||
placeholderText: "Search applications... (use > to view commands)"
|
||||
text: searchText
|
||||
onTextChanged: {
|
||||
// Update the parent searchText property
|
||||
if (searchText !== text) {
|
||||
searchText = text
|
||||
}
|
||||
// Defer selectedIndex reset to avoid binding loops
|
||||
Qt.callLater(() => selectedIndex = 0)
|
||||
|
||||
// Reset cursor position if needed
|
||||
if (shouldResetCursor && text === "") {
|
||||
cursorPosition = 0
|
||||
shouldResetCursor = false
|
||||
}
|
||||
}
|
||||
selectedTextColor: Color.mOnSurface
|
||||
selectionColor: Color.mPrimary
|
||||
padding: 0
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
inputMaxWidth: 100000
|
||||
// Tune vertical centering on inner input
|
||||
Component.onCompleted: {
|
||||
// Focus the search bar by default and set cursor position
|
||||
searchInputBox.inputItem.font.pointSize = Style.fontSizeL * scaling
|
||||
searchInputBox.inputItem.verticalAlignment = TextInput.AlignVCenter
|
||||
// Ensure focus when launcher first appears
|
||||
Qt.callLater(() => {
|
||||
selectedIndex = 0
|
||||
searchInput.forceActiveFocus()
|
||||
// Set cursor to end if there's already text
|
||||
searchInputBox.inputItem.forceActiveFocus()
|
||||
if (searchText && searchText.length > 0) {
|
||||
searchInput.cursorPosition = searchText.length
|
||||
searchInputBox.inputItem.cursorPosition = searchText.length
|
||||
} else {
|
||||
searchInputBox.inputItem.cursorPosition = 0
|
||||
}
|
||||
})
|
||||
}
|
||||
onTextChanged: {
|
||||
if (searchText !== text) {
|
||||
searchText = text
|
||||
}
|
||||
Qt.callLater(() => selectedIndex = 0)
|
||||
if (shouldResetCursor && text === "") {
|
||||
searchInputBox.inputItem.cursorPosition = 0
|
||||
shouldResetCursor = false
|
||||
}
|
||||
}
|
||||
// Forward key navigation to behave like before
|
||||
Keys.onDownPressed: selectNext()
|
||||
Keys.onUpPressed: selectPrev()
|
||||
Keys.onEnterPressed: activateSelected()
|
||||
|
|
@ -355,16 +340,13 @@ NPanel {
|
|||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
// Clear-all action to the right of the input
|
||||
NIconButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: searchText.startsWith(">clip")
|
||||
icon: "delete_sweep"
|
||||
tooltipText: "Clear clipboard history"
|
||||
onClicked: CliphistService.wipeAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -390,6 +372,22 @@ NPanel {
|
|||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
|
||||
// Keep viewport anchored to the selected item when the clipboard model refreshes
|
||||
Connections {
|
||||
target: CliphistService
|
||||
function onRevisionChanged() {
|
||||
if (Settings.data.appLauncher.enableClipboardHistory && searchText.startsWith(">clip")) {
|
||||
// Clamp selection in case the list shrank
|
||||
if (selectedIndex >= filteredEntries.length) {
|
||||
selectedIndex = Math.max(0, filteredEntries.length - 1)
|
||||
}
|
||||
Qt.callLater(() => {
|
||||
appsList.positionViewAtIndex(selectedIndex, ListView.Contain)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
width: appsList.width - Style.marginS * scaling
|
||||
height: 65 * scaling
|
||||
|
|
@ -431,6 +429,19 @@ NPanel {
|
|||
&& iconImg.status !== Image.Error && iconImg.source !== "")
|
||||
visible: !searchText.startsWith(">calc")
|
||||
|
||||
// Decode image thumbnails on demand
|
||||
Component.onCompleted: {
|
||||
if (modelData && modelData.type === 'image' && !CliphistService.imageDataById[modelData.id]) {
|
||||
CliphistService.decodeToDataUrl(modelData.id, modelData.mime || "image/*", function () {})
|
||||
}
|
||||
}
|
||||
onVisibleChanged: {
|
||||
if (visible && modelData && modelData.type === 'image'
|
||||
&& !CliphistService.imageDataById[modelData.id]) {
|
||||
CliphistService.decodeToDataUrl(modelData.id, modelData.mime || "image/*", function () {})
|
||||
}
|
||||
}
|
||||
|
||||
// Clipboard image display (pull from cache)
|
||||
Image {
|
||||
id: clipboardImage
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ NBox {
|
|||
colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
ScreenRecorderService.toggleRecording()
|
||||
// If we were not recording and we just initiated a start, close the panel
|
||||
if (!ScreenRecorderService.isRecording) {
|
||||
var panel = PanelService.getPanel("sidePanel")
|
||||
panel && panel.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -213,6 +213,7 @@ The following commands apply to the Nix flake installation.
|
|||
| Toggle Settings Window | `noctalia-shell ipc call settings toggle` |
|
||||
| Toggle Lock Screen | `noctalia-shell ipc call lockScreen toggle` |
|
||||
| Toggle Notification History | `noctalia-shell ipc call notifications toggleHistory` |
|
||||
| Select new random wallpaper | `noctalia-shell ipc call wallpaper random` |
|
||||
|
||||
</details>
|
||||
|
||||
|
|
@ -240,6 +241,7 @@ The following commands apply to both AUR package and manual installation.
|
|||
| Toggle Settings Window | `qs -c noctalia-shell ipc call settings toggle` |
|
||||
| Toggle Lock Screen | `qs -c noctalia-shell ipc call lockScreen toggle` |
|
||||
| Toggle Notification History | `qs -c noctalia-shell ipc call notifications toggleHistory` |
|
||||
| Select new random wallpaper | `qs -c noctalia-shell ipc call wallpaper random` |
|
||||
|
||||
</details>
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ Singleton {
|
|||
property var imageDataById: ({})
|
||||
property int revision: 0
|
||||
|
||||
// Approximate first-seen timestamps for entries this session (seconds)
|
||||
property var firstSeenById: ({})
|
||||
|
||||
// Internal: store callback for decode
|
||||
property var _decodeCallback: null
|
||||
|
||||
|
|
@ -131,6 +134,10 @@ Singleton {
|
|||
else
|
||||
mime = "image/*"
|
||||
}
|
||||
// Record first seen time for new ids (approximate copy time)
|
||||
if (!root.firstSeenById[id]) {
|
||||
root.firstSeenById[id] = Time.timestamp
|
||||
}
|
||||
return {
|
||||
"id": id,
|
||||
"preview": preview,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ ColumnLayout {
|
|||
property alias text: input.text
|
||||
property alias placeholderText: input.placeholderText
|
||||
property alias inputMethodHints: input.inputMethodHints
|
||||
property alias inputItem: input
|
||||
|
||||
signal editingFinished
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue