mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
Compare commits
30 Commits
chroma
...
6bf1438ef1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bf1438ef1 | ||
|
|
b819306ab6 | ||
|
|
b140afca8e | ||
|
|
6735989455 | ||
|
|
db37ac24c7 | ||
|
|
0231270f9e | ||
|
|
b5194aa9e1 | ||
|
|
ea0ffaacb0 | ||
|
|
3b1f084a13 | ||
|
|
39a9e3a89f | ||
|
|
7a7af775c2 | ||
|
|
6ac2a305f7 | ||
|
|
3507c6cec3 | ||
|
|
3ff00768ac | ||
|
|
556d253ea8 | ||
|
|
3922070488 | ||
|
|
eebb4827c4 | ||
|
|
fd2c6a0784 | ||
|
|
417bf37515 | ||
|
|
132e799265 | ||
|
|
bdc864781b | ||
|
|
a343bc7562 | ||
|
|
1f2e231386 | ||
|
|
0e7f628c4a | ||
|
|
553f5257b3 | ||
|
|
80ce6aa19c | ||
|
|
2b2977de4a | ||
|
|
1d5d876e16 | ||
|
|
3c39162016 | ||
|
|
d38767fb5a |
10
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
10
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -42,12 +42,12 @@ body:
|
||||
placeholder: e.g., PikaOS, Void Linux, etc.
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: dms_version
|
||||
- type: textarea
|
||||
id: dms_doctor
|
||||
attributes:
|
||||
label: dms version
|
||||
description: Output of dms version command
|
||||
placeholder: e.g., 1.2.3
|
||||
label: dms doctor -v
|
||||
description: Output of `dms doctor -v` command
|
||||
placeholder: Paste the output of `dms doctor -v` here
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/support_request.yml
vendored
10
.github/ISSUE_TEMPLATE/support_request.yml
vendored
@@ -27,12 +27,12 @@ body:
|
||||
placeholder: Your Linux distribution
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: dms_version
|
||||
- type: textarea
|
||||
id: dms_doctor
|
||||
attributes:
|
||||
label: dms version
|
||||
description: Output of dms version command
|
||||
placeholder: e.g., 1.2.3
|
||||
label: dms doctor -v
|
||||
description: Output of `dms doctor -v` command
|
||||
placeholder: Paste the output of `dms doctor -v` here
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
|
||||
300
core/cmd/dms/commands_chroma.go
Normal file
300
core/cmd/dms/commands_chroma.go
Normal file
@@ -0,0 +1,300 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/yuin/goldmark"
|
||||
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
ghtml "github.com/yuin/goldmark/renderer/html"
|
||||
)
|
||||
|
||||
var (
|
||||
chromaLanguage string
|
||||
chromaStyle string
|
||||
chromaInline bool
|
||||
chromaMarkdown bool
|
||||
chromaLineNumbers bool
|
||||
|
||||
// Caching layer for performance
|
||||
lexerCache = make(map[string]chroma.Lexer)
|
||||
styleCache = make(map[string]*chroma.Style)
|
||||
formatterCache = make(map[string]*html.Formatter)
|
||||
cacheMutex sync.RWMutex
|
||||
maxFileSize = int64(5 * 1024 * 1024) // 5MB default
|
||||
)
|
||||
|
||||
var chromaCmd = &cobra.Command{
|
||||
Use: "chroma [file]",
|
||||
Short: "Syntax highlight source code",
|
||||
Long: `Generate syntax-highlighted HTML from source code.
|
||||
|
||||
Reads from file or stdin, outputs HTML with syntax highlighting.
|
||||
Language is auto-detected from filename or can be specified with --language.
|
||||
|
||||
Examples:
|
||||
dms chroma main.go
|
||||
dms chroma --language python script.py
|
||||
echo "def foo(): pass" | dms chroma -l python
|
||||
cat code.rs | dms chroma -l rust --style dracula
|
||||
dms chroma --markdown README.md
|
||||
dms chroma --markdown --style github-dark notes.md
|
||||
dms chroma list-languages
|
||||
dms chroma list-styles`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Run: runChroma,
|
||||
}
|
||||
|
||||
var chromaListLanguagesCmd = &cobra.Command{
|
||||
Use: "list-languages",
|
||||
Short: "List all supported languages",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
for _, name := range lexers.Names(true) {
|
||||
fmt.Println(name)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var chromaListStylesCmd = &cobra.Command{
|
||||
Use: "list-styles",
|
||||
Short: "List all available color styles",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
for _, name := range styles.Names() {
|
||||
fmt.Println(name)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
chromaCmd.Flags().StringVarP(&chromaLanguage, "language", "l", "", "Language for highlighting (auto-detect if not specified)")
|
||||
chromaCmd.Flags().StringVarP(&chromaStyle, "style", "s", "monokai", "Color style (monokai, dracula, github, etc.)")
|
||||
chromaCmd.Flags().BoolVar(&chromaInline, "inline", false, "Output inline styles instead of CSS classes")
|
||||
chromaCmd.Flags().BoolVar(&chromaLineNumbers, "line-numbers", false, "Show line numbers in output")
|
||||
chromaCmd.Flags().BoolVarP(&chromaMarkdown, "markdown", "m", false, "Render markdown with syntax-highlighted code blocks")
|
||||
chromaCmd.Flags().Int64Var(&maxFileSize, "max-size", 5*1024*1024, "Maximum file size to process without warning (bytes)")
|
||||
|
||||
chromaCmd.AddCommand(chromaListLanguagesCmd)
|
||||
chromaCmd.AddCommand(chromaListStylesCmd)
|
||||
}
|
||||
|
||||
func getCachedLexer(key string, fallbackFunc func() chroma.Lexer) chroma.Lexer {
|
||||
cacheMutex.RLock()
|
||||
if lexer, ok := lexerCache[key]; ok {
|
||||
cacheMutex.RUnlock()
|
||||
return lexer
|
||||
}
|
||||
cacheMutex.RUnlock()
|
||||
|
||||
lexer := fallbackFunc()
|
||||
if lexer != nil {
|
||||
cacheMutex.Lock()
|
||||
lexerCache[key] = lexer
|
||||
cacheMutex.Unlock()
|
||||
}
|
||||
return lexer
|
||||
}
|
||||
|
||||
func getCachedStyle(name string) *chroma.Style {
|
||||
cacheMutex.RLock()
|
||||
if style, ok := styleCache[name]; ok {
|
||||
cacheMutex.RUnlock()
|
||||
return style
|
||||
}
|
||||
cacheMutex.RUnlock()
|
||||
|
||||
style := styles.Get(name)
|
||||
if style == nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Style '%s' not found, using fallback\n", name)
|
||||
style = styles.Fallback
|
||||
}
|
||||
|
||||
cacheMutex.Lock()
|
||||
styleCache[name] = style
|
||||
cacheMutex.Unlock()
|
||||
return style
|
||||
}
|
||||
|
||||
func getCachedFormatter(inline bool, lineNumbers bool) *html.Formatter {
|
||||
key := fmt.Sprintf("inline=%t,lineNumbers=%t", inline, lineNumbers)
|
||||
|
||||
cacheMutex.RLock()
|
||||
if formatter, ok := formatterCache[key]; ok {
|
||||
cacheMutex.RUnlock()
|
||||
return formatter
|
||||
}
|
||||
cacheMutex.RUnlock()
|
||||
|
||||
var opts []html.Option
|
||||
if inline {
|
||||
opts = append(opts, html.WithClasses(false))
|
||||
} else {
|
||||
opts = append(opts, html.WithClasses(true))
|
||||
}
|
||||
opts = append(opts, html.TabWidth(4))
|
||||
|
||||
if lineNumbers {
|
||||
opts = append(opts, html.WithLineNumbers(true))
|
||||
opts = append(opts, html.LineNumbersInTable(false))
|
||||
opts = append(opts, html.WithLinkableLineNumbers(false, ""))
|
||||
}
|
||||
|
||||
formatter := html.New(opts...)
|
||||
|
||||
cacheMutex.Lock()
|
||||
formatterCache[key] = formatter
|
||||
cacheMutex.Unlock()
|
||||
return formatter
|
||||
}
|
||||
|
||||
func runChroma(cmd *cobra.Command, args []string) {
|
||||
var source string
|
||||
var filename string
|
||||
|
||||
// Read from file or stdin
|
||||
if len(args) > 0 {
|
||||
filename = args[0]
|
||||
|
||||
// Check file size before reading
|
||||
fileInfo, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading file info: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if fileInfo.Size() > maxFileSize {
|
||||
fmt.Fprintf(os.Stderr, "Warning: File size (%d bytes) exceeds recommended limit (%d bytes)\n",
|
||||
fileInfo.Size(), maxFileSize)
|
||||
fmt.Fprintf(os.Stderr, "Processing may be slow. Consider using smaller files.\n")
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
source = string(content)
|
||||
} else {
|
||||
stat, _ := os.Stdin.Stat()
|
||||
if (stat.Mode() & os.ModeCharDevice) != 0 {
|
||||
_ = cmd.Help()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
source = string(content)
|
||||
}
|
||||
|
||||
// Handle empty input
|
||||
if strings.TrimSpace(source) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle Markdown rendering
|
||||
if chromaMarkdown {
|
||||
md := goldmark.New(
|
||||
goldmark.WithExtensions(
|
||||
extension.GFM,
|
||||
highlighting.NewHighlighting(
|
||||
highlighting.WithStyle(chromaStyle),
|
||||
highlighting.WithFormatOptions(
|
||||
html.WithClasses(!chromaInline),
|
||||
),
|
||||
),
|
||||
),
|
||||
goldmark.WithParserOptions(
|
||||
parser.WithAutoHeadingID(),
|
||||
),
|
||||
goldmark.WithRendererOptions(
|
||||
ghtml.WithHardWraps(),
|
||||
ghtml.WithXHTML(),
|
||||
),
|
||||
)
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := md.Convert([]byte(source), &buf); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Markdown rendering error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Print(buf.String())
|
||||
return
|
||||
}
|
||||
|
||||
// Detect or use specified lexer
|
||||
var lexer chroma.Lexer
|
||||
if chromaLanguage != "" {
|
||||
lexer = getCachedLexer(chromaLanguage, func() chroma.Lexer {
|
||||
l := lexers.Get(chromaLanguage)
|
||||
if l == nil {
|
||||
fmt.Fprintf(os.Stderr, "Unknown language: %s\n", chromaLanguage)
|
||||
os.Exit(1)
|
||||
}
|
||||
return l
|
||||
})
|
||||
} else if filename != "" {
|
||||
lexer = getCachedLexer("file:"+filename, func() chroma.Lexer {
|
||||
return lexers.Match(filename)
|
||||
})
|
||||
}
|
||||
|
||||
// Try content analysis if no lexer found (limit to first 1KB for performance)
|
||||
if lexer == nil {
|
||||
analyzeContent := source
|
||||
if len(source) > 1024 {
|
||||
analyzeContent = source[:1024]
|
||||
}
|
||||
lexer = lexers.Analyse(analyzeContent)
|
||||
}
|
||||
|
||||
// Fallback to plaintext
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
|
||||
lexer = chroma.Coalesce(lexer)
|
||||
|
||||
// Get cached style
|
||||
style := getCachedStyle(chromaStyle)
|
||||
|
||||
// Get cached formatter
|
||||
formatter := getCachedFormatter(chromaInline, chromaLineNumbers)
|
||||
|
||||
// Tokenize
|
||||
iterator, err := lexer.Tokenise(nil, source)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Tokenization error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Format and output
|
||||
if chromaLineNumbers {
|
||||
var buf bytes.Buffer
|
||||
if err := formatter.Format(&buf, style, iterator); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Formatting error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Add spacing between line numbers
|
||||
output := buf.String()
|
||||
output = strings.ReplaceAll(output, "</span><span>", "</span>\u00A0\u00A0<span>")
|
||||
fmt.Print(output)
|
||||
} else {
|
||||
if err := formatter.Format(os.Stdout, style, iterator); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Formatting error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,6 +515,7 @@ func getCommonCommands() []*cobra.Command {
|
||||
genericNotifyActionCmd,
|
||||
matugenCmd,
|
||||
clipboardCmd,
|
||||
chromaCmd,
|
||||
doctorCmd,
|
||||
configCmd,
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ go 1.24.6
|
||||
|
||||
require (
|
||||
github.com/Wifx/gonetworkmanager/v2 v2.2.0
|
||||
github.com/alecthomas/chroma/v2 v2.17.2
|
||||
github.com/charmbracelet/bubbles v0.21.0
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
@@ -15,6 +16,8 @@ require (
|
||||
github.com/sblinch/kdl-go v0.0.0-20251203232544-981d4ecc17c3
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/yuin/goldmark v1.7.16
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
go.etcd.io/bbolt v1.4.3
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96
|
||||
golang.org/x/image v0.35.0
|
||||
@@ -28,6 +31,7 @@ require (
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.2 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc // indirect
|
||||
|
||||
44
core/go.sum
44
core/go.sum
@@ -4,6 +4,14 @@ github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBi
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/Wifx/gonetworkmanager/v2 v2.2.0 h1:kPstgsQtY8CmDOOFZd81ytM9Gi3f6ImzPCKF7nNhQ2U=
|
||||
github.com/Wifx/gonetworkmanager/v2 v2.2.0/go.mod h1:fMDb//SHsKWxyDUAwXvCqurV3npbIyyaQWenGpZ/uXg=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.17.2 h1:Rm81SCZ2mPoH+Q8ZCc/9YvzPUN/E7HgPiPJD8SLV6GI=
|
||||
github.com/alecthomas/chroma/v2 v2.17.2/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
@@ -24,16 +32,12 @@ github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoF
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||
github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI=
|
||||
github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI=
|
||||
github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI=
|
||||
github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
|
||||
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/displaywidth v0.7.0 h1:QNv1GYsnLX9QBrcWUtMlogpTXuM5FVnBwKWp1O5NwmE=
|
||||
github.com/clipperhouse/displaywidth v0.7.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
@@ -48,6 +52,10 @@ github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
@@ -58,14 +66,10 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
||||
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd h1:Gd/f9cGi/3h1JOPaa6er+CkKUGyGX2DBJdFbDKVO+R0=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd/go.mod h1:d3XQcsHu1idnquxt48kAv+h+1MUiYKLH/e7LAzjP+pI=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc h1:rhkjrnRkamkRC7woapp425E4CAH6RPcqsS9X8LA93IY=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc/go.mod h1:X1oe0Z2qMsa9hkar3AAPuL9hu4Mi3ztXEjdqRhr6fcc=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146 h1:xYfxAopYyL44ot6dMBIb1Z1njFM0ZBQ99HdIB99KxLs=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146/go.mod h1:QE/75B8tBSLNGyUUbA9tw3EGHoFtYOtypa2h8YJxsWI=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19 h1:0lz2eJScP8v5YZQsrEw+ggWC5jNySjg4bIZo5BIh6iI=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19/go.mod h1:L+Evfcs7EdTqxwv854354cb6+++7TFL3hJn3Wy4g+3w=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6 h1:Yo1MlE8LpvD0pr7mZ04b6hKZKQcPvLrQFgyY1jNMEyU=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6/go.mod h1:enMzPHv+9hL4B7tH7OJGQKNzCkMzXovUoaiXfsLF7Xs=
|
||||
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
||||
@@ -78,6 +82,8 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUv
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83 h1:B+A58zGFuDrvEZpPN+yS6swJA0nzqgZvDzgl/OPyefU=
|
||||
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83/go.mod h1:iHAf8OIncO2gcQ8XOjS7CMJ2aPbX2Bs0wl5pZyanEqk=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
@@ -133,42 +139,35 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
||||
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
|
||||
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
|
||||
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
|
||||
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -177,5 +176,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -215,8 +215,8 @@ func (cd *ConfigDeployer) deployNiriDmsConfigs(dmsDir, terminalCommand string) e
|
||||
|
||||
for _, cfg := range configs {
|
||||
path := filepath.Join(dmsDir, cfg.name)
|
||||
// Skip if file already exists to preserve user modifications
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
// Skip if file already exists and is not empty to preserve user modifications
|
||||
if info, err := os.Stat(path); err == nil && info.Size() > 0 {
|
||||
cd.log(fmt.Sprintf("Skipping %s (already exists)", cfg.name))
|
||||
continue
|
||||
}
|
||||
@@ -567,7 +567,8 @@ func (cd *ConfigDeployer) deployHyprlandDmsConfigs(dmsDir string, terminalComman
|
||||
|
||||
for _, cfg := range configs {
|
||||
path := filepath.Join(dmsDir, cfg.name)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
// Skip if file already exists and is not empty to preserve user modifications
|
||||
if info, err := os.Stat(path); err == nil && info.Size() > 0 {
|
||||
cd.log(fmt.Sprintf("Skipping %s (already exists)", cfg.name))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
inherit version;
|
||||
pname = "dms-shell";
|
||||
src = ./core;
|
||||
vendorHash = "sha256-lXqOJ0yNlOcXuR3vcuVjFI02Hskmavcasb1Ntf3UlPM=";
|
||||
vendorHash = "sha256-kWHB/FN6Z2Ydh+VvNrDnbg18RuJSDAle4DHDAP4NpNk=";
|
||||
|
||||
subPackages = [ "cmd/dms" ];
|
||||
|
||||
|
||||
@@ -79,6 +79,45 @@ Singleton {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
property var launcherPluginVisibility: ({})
|
||||
|
||||
function getPluginAllowWithoutTrigger(pluginId) {
|
||||
if (!launcherPluginVisibility[pluginId])
|
||||
return true;
|
||||
return launcherPluginVisibility[pluginId].allowWithoutTrigger !== false;
|
||||
}
|
||||
|
||||
function setPluginAllowWithoutTrigger(pluginId, allow) {
|
||||
const updated = JSON.parse(JSON.stringify(launcherPluginVisibility));
|
||||
if (!updated[pluginId])
|
||||
updated[pluginId] = {};
|
||||
updated[pluginId].allowWithoutTrigger = allow;
|
||||
launcherPluginVisibility = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
property var launcherPluginOrder: []
|
||||
onLauncherPluginOrderChanged: saveSettings()
|
||||
|
||||
function setLauncherPluginOrder(order) {
|
||||
launcherPluginOrder = order;
|
||||
}
|
||||
|
||||
function getOrderedLauncherPlugins(allPlugins) {
|
||||
if (!launcherPluginOrder || launcherPluginOrder.length === 0)
|
||||
return allPlugins;
|
||||
const orderMap = {};
|
||||
for (let i = 0; i < launcherPluginOrder.length; i++)
|
||||
orderMap[launcherPluginOrder[i]] = i;
|
||||
return allPlugins.slice().sort((a, b) => {
|
||||
const aOrder = orderMap[a.id] ?? 9999;
|
||||
const bOrder = orderMap[b.id] ?? 9999;
|
||||
if (aOrder !== bOrder)
|
||||
return aOrder - bOrder;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}
|
||||
|
||||
property alias dankBarLeftWidgetsModel: leftWidgetsModel
|
||||
property alias dankBarCenterWidgetsModel: centerWidgetsModel
|
||||
property alias dankBarRightWidgetsModel: rightWidgetsModel
|
||||
@@ -236,7 +275,16 @@ Singleton {
|
||||
property bool sortAppsAlphabetically: false
|
||||
property int appLauncherGridColumns: 4
|
||||
property bool spotlightCloseNiriOverview: true
|
||||
property var spotlightSectionViewModes: ({})
|
||||
onSpotlightSectionViewModesChanged: saveSettings()
|
||||
property var appDrawerSectionViewModes: ({})
|
||||
onAppDrawerSectionViewModesChanged: saveSettings()
|
||||
property bool niriOverviewOverlayEnabled: true
|
||||
property string dankLauncherV2Size: "compact"
|
||||
property bool dankLauncherV2BorderEnabled: false
|
||||
property int dankLauncherV2BorderThickness: 2
|
||||
property string dankLauncherV2BorderColor: "primary"
|
||||
property bool dankLauncherV2ShowFooter: true
|
||||
|
||||
property string _legacyWeatherLocation: "New York, NY"
|
||||
property string _legacyWeatherCoordinates: "40.7128,-74.0060"
|
||||
|
||||
@@ -752,9 +752,11 @@ Singleton {
|
||||
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5;
|
||||
}
|
||||
|
||||
function barIconSize(barThickness, offset) {
|
||||
function barIconSize(barThickness, offset, noBackground) {
|
||||
const defaultOffset = offset !== undefined ? offset : -6;
|
||||
return Math.round((barThickness / 48) * (iconSize + defaultOffset));
|
||||
const size = (noBackground ?? false) ? iconSizeLarge : iconSize;
|
||||
|
||||
return Math.round((barThickness / 48) * (size + defaultOffset));
|
||||
}
|
||||
|
||||
function barTextSize(barThickness, fontScale) {
|
||||
|
||||
@@ -134,7 +134,14 @@ var SPEC = {
|
||||
sortAppsAlphabetically: { def: false },
|
||||
appLauncherGridColumns: { def: 4 },
|
||||
spotlightCloseNiriOverview: { def: true },
|
||||
spotlightSectionViewModes: { def: {} },
|
||||
appDrawerSectionViewModes: { def: {} },
|
||||
niriOverviewOverlayEnabled: { def: true },
|
||||
dankLauncherV2Size: { def: "compact" },
|
||||
dankLauncherV2BorderEnabled: { def: false },
|
||||
dankLauncherV2BorderThickness: { def: 2 },
|
||||
dankLauncherV2BorderColor: { def: "primary" },
|
||||
dankLauncherV2ShowFooter: { def: true },
|
||||
|
||||
useAutoLocation: { def: false },
|
||||
weatherEnabled: { def: true },
|
||||
@@ -409,7 +416,9 @@ var SPEC = {
|
||||
|
||||
desktopWidgetGroups: { def: [] },
|
||||
|
||||
builtInPluginSettings: { def: {} }
|
||||
builtInPluginSettings: { def: {} },
|
||||
launcherPluginVisibility: { def: {} },
|
||||
launcherPluginOrder: { def: [] }
|
||||
};
|
||||
|
||||
function getValidKeys() {
|
||||
|
||||
@@ -6,7 +6,7 @@ import qs.Modals.Changelog
|
||||
import qs.Modals.Clipboard
|
||||
import qs.Modals.Greeter
|
||||
import qs.Modals.Settings
|
||||
import qs.Modals.Spotlight
|
||||
import qs.Modals.DankLauncherV2
|
||||
import qs.Modules
|
||||
import qs.Modules.AppDrawer
|
||||
import qs.Modules.DankDash
|
||||
@@ -473,15 +473,17 @@ Item {
|
||||
PopoutService.settingsModalLoader = settingsModalLoader;
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item) {
|
||||
PopoutService.settingsModal = item;
|
||||
PopoutService._onSettingsModalLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
SettingsModal {
|
||||
id: settingsModal
|
||||
property bool wasShown: false
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.settingsModal = settingsModal;
|
||||
PopoutService._onSettingsModalLoaded();
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
wasShown = true;
|
||||
@@ -506,11 +508,22 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SpotlightModal {
|
||||
id: spotlightModal
|
||||
LazyLoader {
|
||||
id: dankLauncherV2ModalLoader
|
||||
|
||||
active: false
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.spotlightModal = spotlightModal;
|
||||
PopoutService.dankLauncherV2ModalLoader = dankLauncherV2ModalLoader;
|
||||
}
|
||||
|
||||
DankLauncherV2Modal {
|
||||
id: dankLauncherV2Modal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.dankLauncherV2Modal = dankLauncherV2Modal;
|
||||
PopoutService._onDankLauncherV2ModalLoaded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1025,6 +1025,94 @@ Item {
|
||||
target: "clipboard"
|
||||
}
|
||||
|
||||
// ! spotlight and launcher should be synonymous for backwards compat
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
PopoutService.openDankLauncherV2();
|
||||
return "LAUNCHER_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
PopoutService.closeDankLauncherV2();
|
||||
return "LAUNCHER_CLOSE_SUCCESS";
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
PopoutService.toggleDankLauncherV2();
|
||||
return "LAUNCHER_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
function openWith(mode: string): string {
|
||||
if (!mode)
|
||||
return "LAUNCHER_OPEN_FAILED: No mode specified";
|
||||
PopoutService.openDankLauncherV2WithMode(mode);
|
||||
return `LAUNCHER_OPEN_SUCCESS: ${mode}`;
|
||||
}
|
||||
|
||||
function toggleWith(mode: string): string {
|
||||
if (!mode)
|
||||
return "LAUNCHER_TOGGLE_FAILED: No mode specified";
|
||||
PopoutService.toggleDankLauncherV2WithMode(mode);
|
||||
return `LAUNCHER_TOGGLE_SUCCESS: ${mode}`;
|
||||
}
|
||||
|
||||
function openQuery(query: string): string {
|
||||
PopoutService.openDankLauncherV2WithQuery(query);
|
||||
return "LAUNCHER_OPEN_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
function toggleQuery(query: string): string {
|
||||
PopoutService.toggleDankLauncherV2();
|
||||
return "LAUNCHER_TOGGLE_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
target: "launcher"
|
||||
}
|
||||
|
||||
// ! spotlight and launcher should be synonymous for backwards compat
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
PopoutService.openDankLauncherV2();
|
||||
return "SPOTLIGHT_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
PopoutService.closeDankLauncherV2();
|
||||
return "SPOTLIGHT_CLOSE_SUCCESS";
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
PopoutService.toggleDankLauncherV2();
|
||||
return "SPOTLIGHT_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
function openWith(mode: string): string {
|
||||
if (!mode)
|
||||
return "SPOTLIGHT_OPEN_FAILED: No mode specified";
|
||||
PopoutService.openDankLauncherV2WithMode(mode);
|
||||
return `SPOTLIGHT_OPEN_SUCCESS: ${mode}`;
|
||||
}
|
||||
|
||||
function toggleWith(mode: string): string {
|
||||
if (!mode)
|
||||
return "SPOTLIGHT_TOGGLE_FAILED: No mode specified";
|
||||
PopoutService.toggleDankLauncherV2WithMode(mode);
|
||||
return `SPOTLIGHT_TOGGLE_SUCCESS: ${mode}`;
|
||||
}
|
||||
|
||||
function openQuery(query: string): string {
|
||||
PopoutService.openDankLauncherV2WithQuery(query);
|
||||
return "SPOTLIGHT_OPEN_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
function toggleQuery(query: string): string {
|
||||
PopoutService.toggleDankLauncherV2();
|
||||
return "SPOTLIGHT_TOGGLE_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
target: "spotlight"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
FirstLaunchService.showWelcome();
|
||||
|
||||
@@ -15,5 +15,5 @@ Singleton {
|
||||
readonly property int viewportBuffer: 100
|
||||
readonly property int extendedBuffer: 200
|
||||
readonly property int keyboardHintsHeight: 80
|
||||
readonly property int headerHeight: 40
|
||||
readonly property int headerHeight: 32
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ Item {
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
focus: false
|
||||
|
||||
ClipboardHeader {
|
||||
@@ -195,7 +195,7 @@ Item {
|
||||
Item {
|
||||
id: keyboardHintsContainer
|
||||
width: parent.width
|
||||
height: modal.showKeyboardHints ? ClipboardConstants.keyboardHintsHeight + Theme.spacingL : 0
|
||||
height: modal.showKeyboardHints ? ClipboardConstants.keyboardHintsHeight + Theme.spacingM : 0
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
@@ -210,7 +210,7 @@ Item {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingL
|
||||
anchors.margins: Theme.spacingM
|
||||
visible: modal.showKeyboardHints
|
||||
wtypeAvailable: modal.wtypeAvailable
|
||||
}
|
||||
|
||||
@@ -44,26 +44,28 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankActionButton {
|
||||
iconName: "history"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: header.activeTab === "recents" ? Theme.primary : Theme.surfaceText
|
||||
onClicked: tabChanged("recents")
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "push_pin"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: header.activeTab === "saved" ? Theme.primary : Theme.surfaceText
|
||||
opacity: header.pinnedCount > 0 ? 1 : 0
|
||||
enabled: header.pinnedCount > 0
|
||||
visible: header.pinnedCount > 0
|
||||
tooltipText: I18n.tr("Saved")
|
||||
onClicked: tabChanged("saved")
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "history"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: header.activeTab === "recents" ? Theme.primary : Theme.surfaceText
|
||||
tooltipText: I18n.tr("History")
|
||||
onClicked: tabChanged("recents")
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "info"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: showKeyboardHints ? Theme.primary : Theme.surfaceText
|
||||
tooltipText: I18n.tr("Keyboard Shortcuts")
|
||||
onClicked: keyboardHintsToggled()
|
||||
}
|
||||
|
||||
@@ -71,6 +73,7 @@ Item {
|
||||
iconName: "delete_sweep"
|
||||
iconSize: Theme.iconSize
|
||||
iconColor: Theme.surfaceText
|
||||
tooltipText: I18n.tr("Clear All")
|
||||
onClicked: clearAllClicked()
|
||||
}
|
||||
|
||||
|
||||
@@ -82,14 +82,13 @@ DankModal {
|
||||
filtered = internalEntries;
|
||||
} else {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
filtered = internalEntries.filter(entry =>
|
||||
entry.preview.toLowerCase().includes(lowerQuery)
|
||||
);
|
||||
filtered = internalEntries.filter(entry => entry.preview.toLowerCase().includes(lowerQuery));
|
||||
}
|
||||
|
||||
// Sort: pinned first, then by ID descending
|
||||
filtered.sort((a, b) => {
|
||||
if (a.pinned !== b.pinned) return b.pinned ? 1 : -1;
|
||||
if (a.pinned !== b.pinned)
|
||||
return b.pinned ? 1 : -1;
|
||||
return b.id - a.id;
|
||||
});
|
||||
|
||||
@@ -193,24 +192,19 @@ DankModal {
|
||||
}
|
||||
|
||||
function deletePinnedEntry(entry) {
|
||||
clearConfirmDialog.show(
|
||||
I18n.tr("Delete Saved Item?"),
|
||||
I18n.tr("This will permanently remove this saved clipboard item. This action cannot be undone."),
|
||||
function () {
|
||||
DMSService.sendRequest("clipboard.deleteEntry", {
|
||||
"id": entry.id
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
console.warn("ClipboardHistoryModal: Failed to delete entry:", response.error);
|
||||
return;
|
||||
}
|
||||
internalEntries = internalEntries.filter(e => e.id !== entry.id);
|
||||
updateFilteredModel();
|
||||
ToastService.showInfo(I18n.tr("Saved item deleted"));
|
||||
});
|
||||
},
|
||||
function () {}
|
||||
);
|
||||
clearConfirmDialog.show(I18n.tr("Delete Saved Item?"), I18n.tr("This will permanently remove this saved clipboard item. This action cannot be undone."), function () {
|
||||
DMSService.sendRequest("clipboard.deleteEntry", {
|
||||
"id": entry.id
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
console.warn("ClipboardHistoryModal: Failed to delete entry:", response.error);
|
||||
return;
|
||||
}
|
||||
internalEntries = internalEntries.filter(e => e.id !== entry.id);
|
||||
updateFilteredModel();
|
||||
ToastService.showInfo(I18n.tr("Saved item deleted"));
|
||||
});
|
||||
}, function () {});
|
||||
}
|
||||
|
||||
function pinEntry(entry) {
|
||||
@@ -226,7 +220,9 @@ DankModal {
|
||||
return;
|
||||
}
|
||||
|
||||
DMSService.sendRequest("clipboard.pinEntry", { "id": entry.id }, function (response) {
|
||||
DMSService.sendRequest("clipboard.pinEntry", {
|
||||
"id": entry.id
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
ToastService.showError(I18n.tr("Failed to pin entry"));
|
||||
return;
|
||||
@@ -238,7 +234,9 @@ DankModal {
|
||||
}
|
||||
|
||||
function unpinEntry(entry) {
|
||||
DMSService.sendRequest("clipboard.unpinEntry", { "id": entry.id }, function (response) {
|
||||
DMSService.sendRequest("clipboard.unpinEntry", {
|
||||
"id": entry.id
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
ToastService.showError(I18n.tr("Failed to unpin entry"));
|
||||
return;
|
||||
@@ -250,27 +248,20 @@ DankModal {
|
||||
|
||||
function clearAll() {
|
||||
const hasPinned = pinnedCount > 0;
|
||||
const message = hasPinned
|
||||
? I18n.tr("This will delete all unpinned entries. %1 pinned entries will be kept.").arg(pinnedCount)
|
||||
: I18n.tr("This will permanently delete all clipboard history.");
|
||||
const message = hasPinned ? I18n.tr("This will delete all unpinned entries. %1 pinned entries will be kept.").arg(pinnedCount) : I18n.tr("This will permanently delete all clipboard history.");
|
||||
|
||||
clearConfirmDialog.show(
|
||||
I18n.tr("Clear History?"),
|
||||
message,
|
||||
function () {
|
||||
DMSService.sendRequest("clipboard.clearHistory", null, function (response) {
|
||||
if (response.error) {
|
||||
console.warn("ClipboardHistoryModal: Failed to clear history:", response.error);
|
||||
return;
|
||||
}
|
||||
refreshClipboard();
|
||||
if (hasPinned) {
|
||||
ToastService.showInfo(I18n.tr("History cleared. %1 pinned entries kept.").arg(pinnedCount));
|
||||
}
|
||||
});
|
||||
},
|
||||
function () {}
|
||||
);
|
||||
clearConfirmDialog.show(I18n.tr("Clear History?"), message, function () {
|
||||
DMSService.sendRequest("clipboard.clearHistory", null, function (response) {
|
||||
if (response.error) {
|
||||
console.warn("ClipboardHistoryModal: Failed to clear history:", response.error);
|
||||
return;
|
||||
}
|
||||
refreshClipboard();
|
||||
if (hasPinned) {
|
||||
ToastService.showInfo(I18n.tr("History cleared. %1 pinned entries kept.").arg(pinnedCount));
|
||||
}
|
||||
});
|
||||
}, function () {});
|
||||
}
|
||||
|
||||
function getEntryPreview(entry) {
|
||||
|
||||
231
quickshell/Modals/DankLauncherV2/ActionPanel.qml
Normal file
231
quickshell/Modals/DankLauncherV2/ActionPanel.qml
Normal file
@@ -0,0 +1,231 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var selectedItem: null
|
||||
property var controller: null
|
||||
property bool expanded: false
|
||||
property int selectedActionIndex: 0
|
||||
|
||||
function getPluginContextMenuActions() {
|
||||
if (selectedItem?.type !== "plugin" || !selectedItem?.pluginId)
|
||||
return [];
|
||||
var instance = PluginService.pluginInstances[selectedItem.pluginId];
|
||||
if (!instance)
|
||||
return [];
|
||||
if (typeof instance.getContextMenuActions !== "function")
|
||||
return [];
|
||||
var actions = instance.getContextMenuActions(selectedItem.data);
|
||||
if (!Array.isArray(actions))
|
||||
return [];
|
||||
return actions;
|
||||
}
|
||||
|
||||
readonly property var actions: {
|
||||
var result = [];
|
||||
if (selectedItem?.primaryAction) {
|
||||
result.push(selectedItem.primaryAction);
|
||||
}
|
||||
|
||||
if (selectedItem?.type === "plugin") {
|
||||
var pluginActions = getPluginContextMenuActions();
|
||||
for (var i = 0; i < pluginActions.length; i++) {
|
||||
var act = pluginActions[i];
|
||||
result.push({
|
||||
name: act.text || act.name || "",
|
||||
icon: act.icon || "play_arrow",
|
||||
action: "plugin_action",
|
||||
pluginAction: act.action
|
||||
});
|
||||
}
|
||||
} else if (selectedItem?.type === "app" && !selectedItem?.isCore) {
|
||||
if (selectedItem?.actions) {
|
||||
for (var i = 0; i < selectedItem.actions.length; i++) {
|
||||
result.push(selectedItem.actions[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
readonly property bool hasActions: {
|
||||
if (selectedItem?.type === "app" && !selectedItem?.isCore)
|
||||
return true;
|
||||
if (selectedItem?.type === "plugin") {
|
||||
var pluginActions = getPluginContextMenuActions();
|
||||
return pluginActions.length > 0;
|
||||
}
|
||||
return actions.length > 1;
|
||||
}
|
||||
|
||||
width: parent?.width ?? 200
|
||||
height: expanded && hasActions ? 52 : 0
|
||||
color: Theme.surfaceContainerHigh
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
clip: true
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outlineMedium
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
|
||||
Flickable {
|
||||
id: actionsFlickable
|
||||
anchors.left: parent.left
|
||||
anchors.right: tabHint.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: parent.height
|
||||
contentWidth: actionsRow.width
|
||||
contentHeight: height
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
flickableDirection: Flickable.HorizontalFlick
|
||||
|
||||
Row {
|
||||
id: actionsRow
|
||||
height: parent.height
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: root.actions
|
||||
|
||||
Rectangle {
|
||||
id: actionButton
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: actionContent.implicitWidth + Theme.spacingM * 2
|
||||
height: actionsRow.height
|
||||
radius: Theme.cornerRadius
|
||||
color: index === root.selectedActionIndex ? Theme.primaryHover : actionArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
Row {
|
||||
id: actionContent
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: actionButton.modelData?.icon ?? "play_arrow"
|
||||
size: 16
|
||||
color: actionButton.index === root.selectedActionIndex ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: actionButton.modelData?.name ?? ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: actionButton.index === root.selectedActionIndex ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: actionArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.controller && root.selectedItem) {
|
||||
root.controller.executeAction(root.selectedItem, actionButton.modelData);
|
||||
}
|
||||
}
|
||||
onEntered: root.selectedActionIndex = actionButton.index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: tabHint
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.hasActions
|
||||
text: "Tab"
|
||||
font.pixelSize: Theme.fontSizeSmall - 2
|
||||
color: Theme.outlineButton
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
expanded = !expanded;
|
||||
selectedActionIndex = 0;
|
||||
}
|
||||
|
||||
function show() {
|
||||
expanded = true;
|
||||
selectedActionIndex = actions.length > 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
function hide() {
|
||||
expanded = false;
|
||||
selectedActionIndex = 0;
|
||||
}
|
||||
|
||||
function cycleAction() {
|
||||
if (actions.length > 0) {
|
||||
selectedActionIndex = (selectedActionIndex + 1) % actions.length;
|
||||
ensureSelectedVisible();
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSelectedVisible() {
|
||||
if (selectedActionIndex < 0 || !actionsRow.children || selectedActionIndex >= actionsRow.children.length)
|
||||
return;
|
||||
var buttonX = 0;
|
||||
for (var i = 0; i < selectedActionIndex; i++) {
|
||||
var child = actionsRow.children[i];
|
||||
if (child)
|
||||
buttonX += child.width + actionsRow.spacing;
|
||||
}
|
||||
|
||||
var button = actionsRow.children[selectedActionIndex];
|
||||
if (!button)
|
||||
return;
|
||||
var buttonRight = buttonX + button.width;
|
||||
var viewLeft = actionsFlickable.contentX;
|
||||
var viewRight = viewLeft + actionsFlickable.width;
|
||||
|
||||
if (buttonX < viewLeft) {
|
||||
actionsFlickable.contentX = Math.max(0, buttonX - Theme.spacingS);
|
||||
} else if (buttonRight > viewRight) {
|
||||
actionsFlickable.contentX = Math.min(actionsFlickable.contentWidth - actionsFlickable.width, buttonRight - actionsFlickable.width + Theme.spacingS);
|
||||
}
|
||||
}
|
||||
|
||||
function executeSelectedAction() {
|
||||
if (!controller || !selectedItem || selectedActionIndex >= actions.length)
|
||||
return;
|
||||
var action = actions[selectedActionIndex];
|
||||
if (action.action === "plugin_action" && typeof action.pluginAction === "function") {
|
||||
action.pluginAction();
|
||||
controller.performSearch();
|
||||
controller.itemExecuted();
|
||||
} else {
|
||||
controller.executeAction(selectedItem, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
1769
quickshell/Modals/DankLauncherV2/Controller.qml
Normal file
1769
quickshell/Modals/DankLauncherV2/Controller.qml
Normal file
File diff suppressed because it is too large
Load Diff
361
quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml
Normal file
361
quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml
Normal file
@@ -0,0 +1,361 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
visible: false
|
||||
|
||||
property bool spotlightOpen: false
|
||||
property bool keyboardActive: false
|
||||
property bool contentVisible: false
|
||||
property alias spotlightContent: launcherContent
|
||||
property bool openedFromOverview: false
|
||||
property bool isClosing: false
|
||||
property bool _windowEnabled: true
|
||||
|
||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||
readonly property var effectiveScreen: launcherWindow.screen
|
||||
readonly property real screenWidth: effectiveScreen?.width ?? 1920
|
||||
readonly property real screenHeight: effectiveScreen?.height ?? 1080
|
||||
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
|
||||
|
||||
readonly property int baseWidth: SettingsData.dankLauncherV2Size === "medium" ? 720 : SettingsData.dankLauncherV2Size === "large" ? 860 : 620
|
||||
readonly property int baseHeight: SettingsData.dankLauncherV2Size === "medium" ? 720 : SettingsData.dankLauncherV2Size === "large" ? 860 : 600
|
||||
readonly property int modalWidth: Math.min(baseWidth, screenWidth - 100)
|
||||
readonly property int modalHeight: Math.min(baseHeight, screenHeight - 100)
|
||||
readonly property real modalX: (screenWidth - modalWidth) / 2
|
||||
readonly property real modalY: (screenHeight - modalHeight) / 2
|
||||
|
||||
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
readonly property real cornerRadius: Theme.cornerRadius
|
||||
readonly property color borderColor: {
|
||||
if (!SettingsData.dankLauncherV2BorderEnabled)
|
||||
return Theme.outlineMedium;
|
||||
switch (SettingsData.dankLauncherV2BorderColor) {
|
||||
case "primary":
|
||||
return Theme.primary;
|
||||
case "secondary":
|
||||
return Theme.secondary;
|
||||
case "outline":
|
||||
return Theme.outline;
|
||||
case "surfaceText":
|
||||
return Theme.surfaceText;
|
||||
default:
|
||||
return Theme.primary;
|
||||
}
|
||||
}
|
||||
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 1
|
||||
|
||||
signal dialogClosed
|
||||
|
||||
function _initializeAndShow(query, mode) {
|
||||
contentVisible = true;
|
||||
spotlightContent.searchField.forceActiveFocus();
|
||||
|
||||
if (spotlightContent.searchField) {
|
||||
spotlightContent.searchField.text = query;
|
||||
}
|
||||
if (spotlightContent.controller) {
|
||||
var targetMode = mode || "all";
|
||||
spotlightContent.controller.searchMode = targetMode;
|
||||
spotlightContent.controller.activePluginId = "";
|
||||
spotlightContent.controller.activePluginName = "";
|
||||
spotlightContent.controller.pluginFilter = "";
|
||||
spotlightContent.controller.collapsedSections = {};
|
||||
if (query) {
|
||||
spotlightContent.controller.setSearchQuery(query);
|
||||
} else {
|
||||
spotlightContent.controller.searchQuery = "";
|
||||
spotlightContent.controller.performSearch();
|
||||
}
|
||||
}
|
||||
if (spotlightContent.resetScroll) {
|
||||
spotlightContent.resetScroll();
|
||||
}
|
||||
if (spotlightContent.actionPanel) {
|
||||
spotlightContent.actionPanel.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function show() {
|
||||
closeCleanupTimer.stop();
|
||||
isClosing = false;
|
||||
openedFromOverview = false;
|
||||
|
||||
var focusedScreen = CompositorService.getFocusedScreen();
|
||||
if (focusedScreen)
|
||||
launcherWindow.screen = focusedScreen;
|
||||
|
||||
spotlightOpen = true;
|
||||
keyboardActive = true;
|
||||
ModalManager.openModal(root);
|
||||
if (useHyprlandFocusGrab)
|
||||
focusGrab.active = true;
|
||||
|
||||
_initializeAndShow("");
|
||||
}
|
||||
|
||||
function showWithQuery(query) {
|
||||
closeCleanupTimer.stop();
|
||||
isClosing = false;
|
||||
openedFromOverview = false;
|
||||
|
||||
var focusedScreen = CompositorService.getFocusedScreen();
|
||||
if (focusedScreen)
|
||||
launcherWindow.screen = focusedScreen;
|
||||
|
||||
spotlightOpen = true;
|
||||
keyboardActive = true;
|
||||
ModalManager.openModal(root);
|
||||
if (useHyprlandFocusGrab)
|
||||
focusGrab.active = true;
|
||||
|
||||
_initializeAndShow(query);
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (!spotlightOpen)
|
||||
return;
|
||||
openedFromOverview = false;
|
||||
isClosing = true;
|
||||
contentVisible = false;
|
||||
|
||||
keyboardActive = false;
|
||||
spotlightOpen = false;
|
||||
focusGrab.active = false;
|
||||
ModalManager.closeModal(root);
|
||||
|
||||
closeCleanupTimer.start();
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
spotlightOpen ? hide() : show();
|
||||
}
|
||||
|
||||
function showWithMode(mode) {
|
||||
closeCleanupTimer.stop();
|
||||
isClosing = false;
|
||||
openedFromOverview = false;
|
||||
|
||||
var focusedScreen = CompositorService.getFocusedScreen();
|
||||
if (focusedScreen)
|
||||
launcherWindow.screen = focusedScreen;
|
||||
|
||||
spotlightOpen = true;
|
||||
keyboardActive = true;
|
||||
ModalManager.openModal(root);
|
||||
if (useHyprlandFocusGrab)
|
||||
focusGrab.active = true;
|
||||
|
||||
_initializeAndShow("", mode);
|
||||
}
|
||||
|
||||
function toggleWithMode(mode) {
|
||||
if (spotlightOpen) {
|
||||
hide();
|
||||
} else {
|
||||
showWithMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeCleanupTimer
|
||||
interval: Theme.expressiveDurations.expressiveFastSpatial + 50
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
isClosing = false;
|
||||
dialogClosed();
|
||||
}
|
||||
}
|
||||
|
||||
HyprlandFocusGrab {
|
||||
id: focusGrab
|
||||
windows: [launcherWindow]
|
||||
active: false
|
||||
|
||||
onCleared: {
|
||||
if (spotlightOpen) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ModalManager
|
||||
function onCloseAllModalsExcept(excludedModal) {
|
||||
if (excludedModal !== root && spotlightOpen) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onScreensChanged() {
|
||||
if (Quickshell.screens.length === 0)
|
||||
return;
|
||||
|
||||
const screen = launcherWindow.screen;
|
||||
const screenName = screen?.name;
|
||||
|
||||
let needsReset = !screen || !screenName;
|
||||
if (!needsReset) {
|
||||
needsReset = true;
|
||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||
if (Quickshell.screens[i].name === screenName) {
|
||||
needsReset = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!needsReset)
|
||||
return;
|
||||
|
||||
const newScreen = CompositorService.getFocusedScreen() ?? Quickshell.screens[0];
|
||||
if (!newScreen)
|
||||
return;
|
||||
|
||||
root._windowEnabled = false;
|
||||
launcherWindow.screen = newScreen;
|
||||
Qt.callLater(() => {
|
||||
root._windowEnabled = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: launcherWindow
|
||||
visible: root._windowEnabled
|
||||
color: "transparent"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
WlrLayershell.namespace: "dms:launcher"
|
||||
WlrLayershell.layer: {
|
||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
||||
case "bottom":
|
||||
console.error("DankModal: 'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
console.error("DankModal: 'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: spotlightOpen ? fullScreenMask : null
|
||||
}
|
||||
|
||||
Item {
|
||||
id: fullScreenMask
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: backgroundDarken
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
opacity: contentVisible && SettingsData.modalDarkenBackground ? 0.5 : 0
|
||||
visible: contentVisible || opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveFastSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: spotlightOpen
|
||||
onClicked: mouse => {
|
||||
var contentX = modalContainer.x;
|
||||
var contentY = modalContainer.y;
|
||||
var contentW = modalContainer.width;
|
||||
var contentH = modalContainer.height;
|
||||
|
||||
if (mouse.x < contentX || mouse.x > contentX + contentW || mouse.y < contentY || mouse.y > contentY + contentH) {
|
||||
root.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: modalContainer
|
||||
x: root.modalX
|
||||
y: root.modalY
|
||||
width: root.modalWidth
|
||||
height: root.modalHeight
|
||||
visible: contentVisible || opacity > 0
|
||||
|
||||
opacity: contentVisible ? 1 : 0
|
||||
scale: contentVisible ? 1 : 0.96
|
||||
transformOrigin: Item.Center
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.fast
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.fast
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
||||
}
|
||||
}
|
||||
|
||||
DankRectangle {
|
||||
anchors.fill: parent
|
||||
color: root.backgroundColor
|
||||
borderColor: root.borderColor
|
||||
borderWidth: root.borderWidth
|
||||
radius: root.cornerRadius
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: mouse => mouse.accepted = true
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: keyboardActive
|
||||
|
||||
LauncherContent {
|
||||
id: launcherContent
|
||||
anchors.fill: parent
|
||||
parentModal: root
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
root.hide();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
quickshell/Modals/DankLauncherV2/GridItem.qml
Normal file
93
quickshell/Modals/DankLauncherV2/GridItem.qml
Normal file
@@ -0,0 +1,93 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var item: null
|
||||
property bool isSelected: false
|
||||
property bool isHovered: itemArea.containsMouse
|
||||
property var controller: null
|
||||
property int flatIndex: -1
|
||||
|
||||
signal clicked
|
||||
signal rightClicked(real mouseX, real mouseY)
|
||||
|
||||
readonly property string iconValue: {
|
||||
if (!item)
|
||||
return "";
|
||||
switch (item.iconType) {
|
||||
case "material":
|
||||
case "nerd":
|
||||
return "material:" + (item.icon || "apps");
|
||||
case "unicode":
|
||||
return "unicode:" + (item.icon || "");
|
||||
case "composite":
|
||||
return item.iconFull || "";
|
||||
case "image":
|
||||
default:
|
||||
return item.icon || "";
|
||||
}
|
||||
}
|
||||
|
||||
readonly property int computedIconSize: Math.min(48, Math.max(32, width * 0.45))
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
width: parent.width - Theme.spacingM
|
||||
|
||||
AppIconRenderer {
|
||||
width: root.computedIconSize
|
||||
height: root.computedIconSize
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
iconValue: root.iconValue
|
||||
iconSize: root.computedIconSize
|
||||
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
|
||||
iconColor: root.isSelected ? Theme.primary : Theme.surfaceText
|
||||
materialIconSizeAdjustment: root.computedIconSize * 0.3
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.item?.name ?? ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: root.isSelected ? Theme.primary : Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var scenePos = mapToItem(null, mouse.x, mouse.y);
|
||||
root.rightClicked(scenePos.x, scenePos.y);
|
||||
} else {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (root.controller) {
|
||||
root.controller.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
810
quickshell/Modals/DankLauncherV2/LauncherContent.qml
Normal file
810
quickshell/Modals/DankLauncherV2/LauncherContent.qml
Normal file
@@ -0,0 +1,810 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property var parentModal: null
|
||||
property string viewModeContext: "spotlight"
|
||||
property alias searchField: searchField
|
||||
property alias controller: controller
|
||||
property alias resultsList: resultsList
|
||||
property alias actionPanel: actionPanel
|
||||
|
||||
property bool editMode: false
|
||||
property var editingApp: null
|
||||
property string editAppId: ""
|
||||
|
||||
function resetScroll() {
|
||||
resultsList.resetScroll();
|
||||
}
|
||||
|
||||
function focusSearchField() {
|
||||
searchField.forceActiveFocus();
|
||||
}
|
||||
|
||||
function openEditMode(app) {
|
||||
if (!app)
|
||||
return;
|
||||
editingApp = app;
|
||||
editAppId = app.id || app.execString || app.exec || "";
|
||||
var existing = SessionData.getAppOverride(editAppId);
|
||||
editNameField.text = existing?.name || "";
|
||||
editIconField.text = existing?.icon || "";
|
||||
editCommentField.text = existing?.comment || "";
|
||||
editEnvVarsField.text = existing?.envVars || "";
|
||||
editExtraFlagsField.text = existing?.extraFlags || "";
|
||||
editMode = true;
|
||||
Qt.callLater(() => editNameField.forceActiveFocus());
|
||||
}
|
||||
|
||||
function closeEditMode() {
|
||||
editMode = false;
|
||||
editingApp = null;
|
||||
editAppId = "";
|
||||
Qt.callLater(() => searchField.forceActiveFocus());
|
||||
}
|
||||
|
||||
function saveAppOverride() {
|
||||
var override = {};
|
||||
if (editNameField.text.trim())
|
||||
override.name = editNameField.text.trim();
|
||||
if (editIconField.text.trim())
|
||||
override.icon = editIconField.text.trim();
|
||||
if (editCommentField.text.trim())
|
||||
override.comment = editCommentField.text.trim();
|
||||
if (editEnvVarsField.text.trim())
|
||||
override.envVars = editEnvVarsField.text.trim();
|
||||
if (editExtraFlagsField.text.trim())
|
||||
override.extraFlags = editExtraFlagsField.text.trim();
|
||||
SessionData.setAppOverride(editAppId, override);
|
||||
closeEditMode();
|
||||
}
|
||||
|
||||
function resetAppOverride() {
|
||||
SessionData.clearAppOverride(editAppId);
|
||||
closeEditMode();
|
||||
}
|
||||
|
||||
function showContextMenu(item, x, y, fromKeyboard) {
|
||||
if (!item)
|
||||
return;
|
||||
if (item.isCore)
|
||||
return;
|
||||
if (!contextMenu.hasContextMenuActions(item))
|
||||
return;
|
||||
contextMenu.show(x, y, item, fromKeyboard);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
Controller {
|
||||
id: controller
|
||||
viewModeContext: root.viewModeContext
|
||||
|
||||
onItemExecuted: {
|
||||
if (root.parentModal) {
|
||||
root.parentModal.hide();
|
||||
}
|
||||
if (SettingsData.spotlightCloseNiriOverview && NiriService.inOverview) {
|
||||
NiriService.toggleOverview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LauncherContextMenu {
|
||||
id: contextMenu
|
||||
parent: root
|
||||
controller: root.controller
|
||||
searchField: root.searchField
|
||||
parentHandler: root
|
||||
|
||||
onEditAppRequested: app => {
|
||||
root.openEditMode(app);
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (editMode) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
closeEditMode();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var hasCtrl = event.modifiers & Qt.ControlModifier;
|
||||
event.accepted = true;
|
||||
|
||||
switch (event.key) {
|
||||
case Qt.Key_Escape:
|
||||
if (actionPanel.expanded) {
|
||||
actionPanel.hide();
|
||||
return;
|
||||
}
|
||||
if (controller.clearPluginFilter())
|
||||
return;
|
||||
if (root.parentModal)
|
||||
root.parentModal.hide();
|
||||
return;
|
||||
case Qt.Key_Backspace:
|
||||
if (searchField.text.length === 0) {
|
||||
if (controller.clearPluginFilter())
|
||||
return;
|
||||
if (controller.autoSwitchedToFiles) {
|
||||
controller.restorePreviousMode();
|
||||
return;
|
||||
}
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_Down:
|
||||
controller.selectNext();
|
||||
return;
|
||||
case Qt.Key_Up:
|
||||
controller.selectPrevious();
|
||||
return;
|
||||
case Qt.Key_PageDown:
|
||||
controller.selectPageDown(8);
|
||||
return;
|
||||
case Qt.Key_PageUp:
|
||||
controller.selectPageUp(8);
|
||||
return;
|
||||
case Qt.Key_Right:
|
||||
if (controller.getCurrentSectionViewMode() !== "list") {
|
||||
controller.selectRight();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_Left:
|
||||
if (controller.getCurrentSectionViewMode() !== "list") {
|
||||
controller.selectLeft();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_J:
|
||||
if (hasCtrl) {
|
||||
controller.selectNext();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_K:
|
||||
if (hasCtrl) {
|
||||
controller.selectPrevious();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_N:
|
||||
if (hasCtrl) {
|
||||
controller.selectNextSection();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_P:
|
||||
if (hasCtrl) {
|
||||
controller.selectPreviousSection();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_Tab:
|
||||
if (actionPanel.hasActions) {
|
||||
actionPanel.expanded ? actionPanel.cycleAction() : actionPanel.show();
|
||||
}
|
||||
return;
|
||||
case Qt.Key_Backtab:
|
||||
if (actionPanel.expanded)
|
||||
actionPanel.hide();
|
||||
return;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
if (actionPanel.expanded && actionPanel.selectedActionIndex > 0) {
|
||||
actionPanel.executeSelectedAction();
|
||||
} else {
|
||||
controller.executeSelected();
|
||||
}
|
||||
return;
|
||||
case Qt.Key_Menu:
|
||||
case Qt.Key_F10:
|
||||
if (contextMenu.hasContextMenuActions(controller.selectedItem)) {
|
||||
var scenePos = resultsList.getSelectedItemPosition();
|
||||
var localPos = root.mapFromItem(null, scenePos.x, scenePos.y);
|
||||
showContextMenu(controller.selectedItem, localPos.x, localPos.y, true);
|
||||
}
|
||||
return;
|
||||
case Qt.Key_1:
|
||||
if (hasCtrl) {
|
||||
controller.setMode("all");
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_2:
|
||||
if (hasCtrl) {
|
||||
controller.setMode("apps");
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_3:
|
||||
if (hasCtrl) {
|
||||
controller.setMode("files");
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_4:
|
||||
if (hasCtrl) {
|
||||
controller.setMode("plugins");
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_Slash:
|
||||
if (event.modifiers === Qt.NoModifier && searchField.text.length === 0) {
|
||||
controller.setMode("files", true);
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
default:
|
||||
event.accepted = false;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: !editMode
|
||||
|
||||
Rectangle {
|
||||
id: footerBar
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: SettingsData.dankLauncherV2ShowFooter ? 32 : 0
|
||||
visible: SettingsData.dankLauncherV2ShowFooter
|
||||
color: Theme.surfaceContainerHigh
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
Row {
|
||||
id: modeButtonsRow
|
||||
x: I18n.isRtl ? parent.width - width - Theme.spacingS : Theme.spacingS
|
||||
y: (parent.height - height) / 2
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{
|
||||
id: "all",
|
||||
label: I18n.tr("All"),
|
||||
icon: "search"
|
||||
},
|
||||
{
|
||||
id: "apps",
|
||||
label: I18n.tr("Apps"),
|
||||
icon: "apps"
|
||||
},
|
||||
{
|
||||
id: "files",
|
||||
label: I18n.tr("Files"),
|
||||
icon: "folder"
|
||||
},
|
||||
{
|
||||
id: "plugins",
|
||||
label: I18n.tr("Plugins"),
|
||||
icon: "extension"
|
||||
}
|
||||
]
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: modeButtonMetrics.width + 14 + Theme.spacingXS + Theme.spacingM * 2 + Theme.spacingS
|
||||
height: footerBar.height - 4
|
||||
radius: Theme.cornerRadius - 2
|
||||
color: controller.searchMode === modelData.id || modeArea.containsMouse ? Theme.primaryContainer : "transparent"
|
||||
|
||||
TextMetrics {
|
||||
id: modeButtonMetrics
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
text: modelData.label
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: modelData.icon
|
||||
size: 14
|
||||
color: controller.searchMode === modelData.id ? Theme.primary : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.label
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: controller.searchMode === modelData.id ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: modeArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: controller.setMode(modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: hintsRow
|
||||
x: I18n.isRtl ? Theme.spacingS : parent.width - width - Theme.spacingS
|
||||
y: (parent.height - height) / 2
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: "↑↓ " + I18n.tr("nav")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "↵ " + I18n.tr("open")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Tab " + I18n.tr("actions")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
visible: actionPanel.hasActions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: footerBar.top
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
anchors.bottomMargin: Theme.spacingXS
|
||||
spacing: Theme.spacingXS
|
||||
clip: false
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
id: pluginBadge
|
||||
visible: controller.activePluginName.length > 0
|
||||
width: visible ? pluginBadgeContent.implicitWidth + Theme.spacingM : 0
|
||||
height: searchField.height
|
||||
radius: 16
|
||||
color: Theme.primary
|
||||
|
||||
Row {
|
||||
id: pluginBadgeContent
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: "extension"
|
||||
size: 14
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: controller.activePluginName
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primaryText
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: searchField
|
||||
width: parent.width - (pluginBadge.visible ? pluginBadge.width + Theme.spacingS : 0)
|
||||
cornerRadius: Theme.cornerRadius
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
focusedBorderColor: Theme.primary
|
||||
leftIconName: controller.activePluginId ? "extension" : controller.searchQuery.startsWith("/") ? "folder" : "search"
|
||||
leftIconSize: Theme.iconSize
|
||||
leftIconColor: Theme.surfaceVariantText
|
||||
leftIconFocusedColor: Theme.primary
|
||||
showClearButton: true
|
||||
textColor: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
enabled: root.parentModal ? root.parentModal.spotlightOpen : true
|
||||
placeholderText: ""
|
||||
ignoreUpDownKeys: true
|
||||
ignoreTabKeys: true
|
||||
keyForwardTargets: [root]
|
||||
|
||||
onTextChanged: {
|
||||
controller.setSearchQuery(text);
|
||||
if (text.length === 0) {
|
||||
controller.restorePreviousMode();
|
||||
}
|
||||
if (actionPanel.expanded) {
|
||||
actionPanel.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
if (root.parentModal) {
|
||||
root.parentModal.hide();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter)) {
|
||||
if (actionPanel.expanded && actionPanel.selectedActionIndex > 0) {
|
||||
actionPanel.executeSelectedAction();
|
||||
} else {
|
||||
controller.executeSelected();
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height - searchField.height - actionPanel.height - Theme.spacingXS * 2
|
||||
opacity: root.parentModal?.isClosing ? 0 : 1
|
||||
|
||||
ResultsList {
|
||||
id: resultsList
|
||||
anchors.fill: parent
|
||||
controller: root.controller
|
||||
|
||||
onItemRightClicked: (index, item, sceneX, sceneY) => {
|
||||
if (item && contextMenu.hasContextMenuActions(item)) {
|
||||
var localPos = root.mapFromItem(null, sceneX, sceneY);
|
||||
root.showContextMenu(item, localPos.x, localPos.y, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionPanel {
|
||||
id: actionPanel
|
||||
width: parent.width
|
||||
selectedItem: controller.selectedItem
|
||||
controller: controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: controller
|
||||
function onSelectedItemChanged() {
|
||||
if (actionPanel.expanded && !actionPanel.hasActions) {
|
||||
actionPanel.hide();
|
||||
}
|
||||
}
|
||||
function onSearchQueryRequested(query) {
|
||||
searchField.text = query;
|
||||
}
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: editView
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
visible: editMode
|
||||
focus: editMode
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
closeEditMode();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
saveAppOverride();
|
||||
event.accepted = true;
|
||||
}
|
||||
} else if (event.key === Qt.Key_S && event.modifiers & Qt.ControlModifier) {
|
||||
saveAppOverride();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: backButtonArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "arrow_back"
|
||||
size: 20
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: backButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: closeEditMode()
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
width: 40
|
||||
height: 40
|
||||
source: editingApp?.icon ? "image://icon/" + editingApp.icon : "image://icon/application-x-executable"
|
||||
sourceSize.width: 40
|
||||
sourceSize.height: 40
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Edit App")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: editingApp?.name || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outlineMedium
|
||||
}
|
||||
|
||||
Flickable {
|
||||
width: parent.width
|
||||
height: parent.height - y - buttonsRow.height - Theme.spacingM
|
||||
contentHeight: editFieldsColumn.height
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Column {
|
||||
id: editFieldsColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Name")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editNameField
|
||||
width: parent.width
|
||||
placeholderText: editingApp?.name || ""
|
||||
keyNavigationTab: editIconField
|
||||
keyNavigationBacktab: editExtraFlagsField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Icon")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editIconField
|
||||
width: parent.width
|
||||
placeholderText: editingApp?.icon || ""
|
||||
keyNavigationTab: editCommentField
|
||||
keyNavigationBacktab: editNameField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Description")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editCommentField
|
||||
width: parent.width
|
||||
placeholderText: editingApp?.comment || ""
|
||||
keyNavigationTab: editEnvVarsField
|
||||
keyNavigationBacktab: editIconField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Environment Variables")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "KEY=value KEY2=value2"
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editEnvVarsField
|
||||
width: parent.width
|
||||
placeholderText: "VAR=value"
|
||||
keyNavigationTab: editExtraFlagsField
|
||||
keyNavigationBacktab: editCommentField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Extra Arguments")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editExtraFlagsField
|
||||
width: parent.width
|
||||
placeholderText: "--flag --option=value"
|
||||
keyNavigationTab: editNameField
|
||||
keyNavigationBacktab: editEnvVarsField
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonsRow
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
id: resetButton
|
||||
width: 90
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: resetButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
||||
visible: SessionData.getAppOverride(editAppId) !== null
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Reset")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.error
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: resetButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: resetAppOverride()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: cancelButton
|
||||
width: 90
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: cancelButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Cancel")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cancelButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: closeEditMode()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: saveButton
|
||||
width: 90
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: saveButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.9) : Theme.primary
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Save")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primaryText
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: saveButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: saveAppOverride()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
484
quickshell/Modals/DankLauncherV2/LauncherContextMenu.qml
Normal file
484
quickshell/Modals/DankLauncherV2/LauncherContextMenu.qml
Normal file
@@ -0,0 +1,484 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property var item: null
|
||||
property var controller: null
|
||||
property var searchField: null
|
||||
property var parentHandler: null
|
||||
|
||||
signal hideRequested
|
||||
signal editAppRequested(var app)
|
||||
|
||||
function hasContextMenuActions(spotlightItem) {
|
||||
if (!spotlightItem)
|
||||
return false;
|
||||
if (spotlightItem.type === "app" && !spotlightItem.isCore)
|
||||
return true;
|
||||
if (spotlightItem.type === "plugin" && spotlightItem.pluginId) {
|
||||
var instance = PluginService.pluginInstances[spotlightItem.pluginId];
|
||||
if (!instance)
|
||||
return false;
|
||||
if (typeof instance.getContextMenuActions !== "function")
|
||||
return false;
|
||||
var actions = instance.getContextMenuActions(spotlightItem.data);
|
||||
return Array.isArray(actions) && actions.length > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property var desktopEntry: item?.data ?? null
|
||||
readonly property string appId: desktopEntry?.id || desktopEntry?.execString || ""
|
||||
readonly property bool isPinned: SessionData.isPinnedApp(appId)
|
||||
readonly property bool isRegularApp: item?.type === "app" && !item.isCore && desktopEntry
|
||||
readonly property bool isPluginItem: item?.type === "plugin"
|
||||
|
||||
function getPluginContextMenuActions() {
|
||||
if (!isPluginItem || !item?.pluginId)
|
||||
return [];
|
||||
|
||||
var instance = PluginService.pluginInstances[item.pluginId];
|
||||
if (!instance)
|
||||
return [];
|
||||
if (typeof instance.getContextMenuActions !== "function")
|
||||
return [];
|
||||
|
||||
var actions = instance.getContextMenuActions(item.data);
|
||||
if (!Array.isArray(actions))
|
||||
return [];
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
function executePluginAction(actionFunc) {
|
||||
if (typeof actionFunc === "function") {
|
||||
actionFunc();
|
||||
}
|
||||
controller?.performSearch();
|
||||
hide();
|
||||
}
|
||||
|
||||
readonly property var menuItems: {
|
||||
var items = [];
|
||||
|
||||
if (isPluginItem) {
|
||||
var pluginActions = getPluginContextMenuActions();
|
||||
for (var i = 0; i < pluginActions.length; i++) {
|
||||
var act = pluginActions[i];
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: act.icon || "play_arrow",
|
||||
text: act.text || act.name || "",
|
||||
pluginAction: act.action
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
if (!desktopEntry)
|
||||
return items;
|
||||
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: isPinned ? "keep_off" : "push_pin",
|
||||
text: isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock"),
|
||||
action: togglePin
|
||||
});
|
||||
|
||||
if (isRegularApp) {
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "visibility_off",
|
||||
text: I18n.tr("Hide App"),
|
||||
action: hideCurrentApp
|
||||
});
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "edit",
|
||||
text: I18n.tr("Edit App"),
|
||||
action: editCurrentApp
|
||||
});
|
||||
}
|
||||
|
||||
if (item?.actions && item.actions.length > 0) {
|
||||
items.push({
|
||||
type: "separator"
|
||||
});
|
||||
for (var i = 0; i < item.actions.length; i++) {
|
||||
var act = item.actions[i];
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: act.icon || "play_arrow",
|
||||
text: act.name || "",
|
||||
actionData: act
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
items.push({
|
||||
type: "separator"
|
||||
});
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "launch",
|
||||
text: I18n.tr("Launch"),
|
||||
action: launchApp
|
||||
});
|
||||
|
||||
if (SessionService.nvidiaCommand) {
|
||||
items.push({
|
||||
type: "separator"
|
||||
});
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "memory",
|
||||
text: I18n.tr("Launch on dGPU"),
|
||||
action: launchWithNvidia
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function show(x, y, spotlightItem, fromKeyboard) {
|
||||
if (!spotlightItem?.data)
|
||||
return;
|
||||
item = spotlightItem;
|
||||
selectedMenuIndex = fromKeyboard ? 0 : -1;
|
||||
keyboardNavigation = fromKeyboard;
|
||||
|
||||
if (parentHandler)
|
||||
parentHandler.enabled = false;
|
||||
|
||||
Qt.callLater(() => {
|
||||
var parentW = parent?.width ?? 500;
|
||||
var parentH = parent?.height ?? 600;
|
||||
var menuW = width > 0 ? width : 200;
|
||||
var menuH = height > 0 ? height : 200;
|
||||
var margin = 8;
|
||||
|
||||
var posX = x + 4;
|
||||
var posY = y + 4;
|
||||
|
||||
if (posX + menuW > parentW - margin) {
|
||||
posX = Math.max(margin, parentW - menuW - margin);
|
||||
}
|
||||
if (posY + menuH > parentH - margin) {
|
||||
posY = Math.max(margin, parentH - menuH - margin);
|
||||
}
|
||||
|
||||
root.x = posX;
|
||||
root.y = posY;
|
||||
open();
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (parentHandler)
|
||||
parentHandler.enabled = true;
|
||||
close();
|
||||
}
|
||||
|
||||
function togglePin() {
|
||||
if (!appId)
|
||||
return;
|
||||
if (isPinned)
|
||||
SessionData.removePinnedApp(appId);
|
||||
else
|
||||
SessionData.addPinnedApp(appId);
|
||||
hide();
|
||||
}
|
||||
|
||||
function hideCurrentApp() {
|
||||
if (!appId)
|
||||
return;
|
||||
SessionData.hideApp(appId);
|
||||
controller?.performSearch();
|
||||
hide();
|
||||
}
|
||||
|
||||
function editCurrentApp() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
editAppRequested(desktopEntry);
|
||||
hide();
|
||||
}
|
||||
|
||||
function launchApp() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
SessionService.launchDesktopEntry(desktopEntry);
|
||||
AppUsageHistoryData.addAppUsage(desktopEntry);
|
||||
controller?.itemExecuted();
|
||||
hide();
|
||||
}
|
||||
|
||||
function launchWithNvidia() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
SessionService.launchDesktopEntry(desktopEntry, true);
|
||||
AppUsageHistoryData.addAppUsage(desktopEntry);
|
||||
controller?.itemExecuted();
|
||||
hide();
|
||||
}
|
||||
|
||||
function executeDesktopAction(actionData) {
|
||||
if (!desktopEntry || !actionData)
|
||||
return;
|
||||
SessionService.launchDesktopAction(desktopEntry, actionData.actionData || actionData);
|
||||
AppUsageHistoryData.addAppUsage(desktopEntry);
|
||||
controller?.itemExecuted();
|
||||
hide();
|
||||
}
|
||||
|
||||
property int selectedMenuIndex: 0
|
||||
property bool keyboardNavigation: false
|
||||
|
||||
readonly property int visibleItemCount: {
|
||||
var count = 0;
|
||||
for (var i = 0; i < menuItems.length; i++) {
|
||||
if (menuItems[i].type === "item")
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (visibleItemCount > 0)
|
||||
selectedMenuIndex = (selectedMenuIndex + 1) % visibleItemCount;
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (visibleItemCount > 0)
|
||||
selectedMenuIndex = (selectedMenuIndex - 1 + visibleItemCount) % visibleItemCount;
|
||||
}
|
||||
|
||||
function activateSelected() {
|
||||
var itemIndex = 0;
|
||||
for (var i = 0; i < menuItems.length; i++) {
|
||||
if (menuItems[i].type !== "item")
|
||||
continue;
|
||||
if (itemIndex === selectedMenuIndex) {
|
||||
var menuItem = menuItems[i];
|
||||
if (menuItem.action)
|
||||
menuItem.action();
|
||||
else if (menuItem.pluginAction)
|
||||
executePluginAction(menuItem.pluginAction);
|
||||
else if (menuItem.actionData)
|
||||
executeDesktopAction(menuItem.actionData);
|
||||
return;
|
||||
}
|
||||
itemIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
width: menuContainer.implicitWidth
|
||||
height: menuContainer.implicitHeight
|
||||
padding: 0
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
modal: true
|
||||
dim: false
|
||||
background: Item {}
|
||||
|
||||
onOpened: {
|
||||
Qt.callLater(() => keyboardHandler.forceActiveFocus());
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (parentHandler)
|
||||
parentHandler.enabled = true;
|
||||
if (searchField?.visible) {
|
||||
Qt.callLater(() => searchField.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
id: keyboardHandler
|
||||
focus: true
|
||||
implicitWidth: menuContainer.implicitWidth
|
||||
implicitHeight: menuContainer.implicitHeight
|
||||
|
||||
Keys.onPressed: event => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Down:
|
||||
root.selectNext();
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Up:
|
||||
root.selectPrevious();
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
root.activateSelected();
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Escape:
|
||||
case Qt.Key_Left:
|
||||
root.hide();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: menuContainer
|
||||
anchors.fill: parent
|
||||
implicitWidth: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
|
||||
implicitHeight: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: -1
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Repeater {
|
||||
model: root.menuItems
|
||||
|
||||
Item {
|
||||
id: menuItemDelegate
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: menuColumn.width
|
||||
height: modelData.type === "separator" ? 5 : 32
|
||||
|
||||
readonly property int itemIndex: {
|
||||
var count = 0;
|
||||
for (var i = 0; i < index; i++) {
|
||||
if (root.menuItems[i].type === "item")
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: menuItemDelegate.modelData.type === "separator"
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: parent.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: menuItemDelegate.modelData.type === "item"
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (root.keyboardNavigation && root.selectedMenuIndex === menuItemDelegate.itemIndex) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
|
||||
}
|
||||
return itemMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: Theme.iconSize - 2
|
||||
height: Theme.iconSize - 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
visible: (menuItemDelegate.modelData?.icon ?? "").length > 0
|
||||
name: menuItemDelegate.modelData?.icon ?? ""
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: menuItemDelegate.modelData.text || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
width: parent.width - (Theme.iconSize - 2) - Theme.spacingS
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
root.keyboardNavigation = false;
|
||||
root.selectedMenuIndex = menuItemDelegate.itemIndex;
|
||||
}
|
||||
onClicked: {
|
||||
var menuItem = menuItemDelegate.modelData;
|
||||
if (menuItem.action)
|
||||
menuItem.action();
|
||||
else if (menuItem.pluginAction)
|
||||
root.executePluginAction(menuItem.pluginAction);
|
||||
else if (menuItem.actionData)
|
||||
root.executeDesktopAction(menuItem.actionData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
142
quickshell/Modals/DankLauncherV2/ResultItem.qml
Normal file
142
quickshell/Modals/DankLauncherV2/ResultItem.qml
Normal file
@@ -0,0 +1,142 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var item: null
|
||||
property bool isSelected: false
|
||||
property bool isHovered: itemArea.containsMouse
|
||||
property var controller: null
|
||||
property int flatIndex: -1
|
||||
|
||||
signal clicked
|
||||
signal rightClicked(real mouseX, real mouseY)
|
||||
|
||||
readonly property string iconValue: {
|
||||
if (!item)
|
||||
return "";
|
||||
switch (item.iconType) {
|
||||
case "material":
|
||||
case "nerd":
|
||||
return "material:" + (item.icon || "apps");
|
||||
case "unicode":
|
||||
return "unicode:" + (item.icon || "");
|
||||
case "composite":
|
||||
return item.iconFull || "";
|
||||
case "image":
|
||||
default:
|
||||
return item.icon || "";
|
||||
}
|
||||
}
|
||||
|
||||
width: parent?.width ?? 200
|
||||
height: 52
|
||||
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
AppIconRenderer {
|
||||
width: 36
|
||||
height: 36
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconValue: root.iconValue
|
||||
iconSize: 36
|
||||
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
|
||||
materialIconSizeAdjustment: 12
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 36 - Theme.spacingM * 3 - rightContent.width
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.item?.name ?? ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.item?.subtitle ?? ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
visible: text.length > 0
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rightContent
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
visible: root.item?.type && root.item.type !== "app"
|
||||
width: typeBadge.implicitWidth + Theme.spacingS * 2
|
||||
height: 20
|
||||
radius: 10
|
||||
color: Theme.surfaceVariantAlpha
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
id: typeBadge
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!root.item)
|
||||
return "";
|
||||
switch (root.item.type) {
|
||||
case "calculator":
|
||||
return I18n.tr("Calc");
|
||||
case "plugin":
|
||||
return I18n.tr("Plugin");
|
||||
case "file":
|
||||
return I18n.tr("File");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall - 2
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var scenePos = mapToItem(null, mouse.x, mouse.y);
|
||||
root.rightClicked(scenePos.x, scenePos.y);
|
||||
} else {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (root.controller) {
|
||||
root.controller.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
484
quickshell/Modals/DankLauncherV2/ResultsList.qml
Normal file
484
quickshell/Modals/DankLauncherV2/ResultsList.qml
Normal file
@@ -0,0 +1,484 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var controller: null
|
||||
property int gridColumns: controller?.gridColumns ?? 4
|
||||
|
||||
signal itemRightClicked(int index, var item, real mouseX, real mouseY)
|
||||
|
||||
function resetScroll() {
|
||||
mainFlickable.contentY = 0;
|
||||
}
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || !controller?.flatModel || index >= controller.flatModel.length)
|
||||
return;
|
||||
var entry = controller.flatModel[index];
|
||||
if (!entry || entry.isHeader)
|
||||
return;
|
||||
scrollItemIntoView(index, entry.sectionId);
|
||||
}
|
||||
|
||||
function scrollItemIntoView(flatIndex, sectionId) {
|
||||
var sections = controller?.sections ?? [];
|
||||
var sectionIndex = -1;
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].id === sectionId) {
|
||||
sectionIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sectionIndex < 0)
|
||||
return;
|
||||
var itemInSection = 0;
|
||||
var foundSection = false;
|
||||
for (var i = 0; i < controller.flatModel.length && i < flatIndex; i++) {
|
||||
var e = controller.flatModel[i];
|
||||
if (e.isHeader && e.section?.id === sectionId)
|
||||
foundSection = true;
|
||||
else if (foundSection && !e.isHeader && e.sectionId === sectionId)
|
||||
itemInSection++;
|
||||
}
|
||||
|
||||
var mode = controller.getSectionViewMode(sectionId);
|
||||
var sectionY = 0;
|
||||
for (var i = 0; i < sectionIndex; i++) {
|
||||
sectionY += getSectionHeight(sections[i]);
|
||||
}
|
||||
|
||||
var itemY, itemHeight;
|
||||
if (mode === "list") {
|
||||
itemY = itemInSection * 52;
|
||||
itemHeight = 52;
|
||||
} else {
|
||||
var cols = controller.getGridColumns(sectionId);
|
||||
var cellWidth = mode === "tile" ? Math.floor(mainFlickable.width / 3) : Math.floor(mainFlickable.width / root.gridColumns);
|
||||
var cellHeight = mode === "tile" ? cellWidth * 0.75 : cellWidth + 24;
|
||||
var row = Math.floor(itemInSection / cols);
|
||||
itemY = row * cellHeight;
|
||||
itemHeight = cellHeight;
|
||||
}
|
||||
|
||||
var targetY = sectionY + 32 + itemY;
|
||||
var targetBottom = targetY + itemHeight;
|
||||
var stickyHeight = mainFlickable.contentY > 0 ? 32 : 0;
|
||||
|
||||
var shadowPadding = 24;
|
||||
if (targetY < mainFlickable.contentY + stickyHeight) {
|
||||
mainFlickable.contentY = Math.max(0, targetY - 32);
|
||||
} else if (targetBottom > mainFlickable.contentY + mainFlickable.height - shadowPadding) {
|
||||
mainFlickable.contentY = Math.min(mainFlickable.contentHeight - mainFlickable.height, targetBottom - mainFlickable.height + shadowPadding);
|
||||
}
|
||||
}
|
||||
|
||||
function getSectionHeight(section) {
|
||||
var mode = controller?.getSectionViewMode(section.id) ?? "list";
|
||||
if (section.collapsed)
|
||||
return 32;
|
||||
|
||||
if (mode === "list") {
|
||||
return 32 + (section.items?.length ?? 0) * 52;
|
||||
} else {
|
||||
var cols = controller?.getGridColumns(section.id) ?? root.gridColumns;
|
||||
var rows = Math.ceil((section.items?.length ?? 0) / cols);
|
||||
var cellWidth = mode === "tile" ? Math.floor(root.width / 3) : Math.floor(root.width / cols);
|
||||
var cellHeight = mode === "tile" ? cellWidth * 0.75 : cellWidth + 24;
|
||||
return 32 + rows * cellHeight;
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedItemPosition() {
|
||||
var fallback = mapToItem(null, width / 2, height / 2);
|
||||
if (!controller?.flatModel || controller.selectedFlatIndex < 0)
|
||||
return fallback;
|
||||
|
||||
var entry = controller.flatModel[controller.selectedFlatIndex];
|
||||
if (!entry || entry.isHeader)
|
||||
return fallback;
|
||||
|
||||
var sections = controller.sections;
|
||||
var sectionIndex = -1;
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].id === entry.sectionId) {
|
||||
sectionIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sectionIndex < 0)
|
||||
return fallback;
|
||||
|
||||
var sectionY = 0;
|
||||
for (var i = 0; i < sectionIndex; i++) {
|
||||
sectionY += getSectionHeight(sections[i]);
|
||||
}
|
||||
|
||||
var mode = controller.getSectionViewMode(entry.sectionId);
|
||||
var itemInSection = entry.indexInSection || 0;
|
||||
|
||||
var itemY, itemX, itemH;
|
||||
if (mode === "list") {
|
||||
itemY = sectionY + 32 + itemInSection * 52;
|
||||
itemX = width / 2;
|
||||
itemH = 52;
|
||||
} else {
|
||||
var cols = controller.getGridColumns(entry.sectionId);
|
||||
var cellWidth = mode === "tile" ? Math.floor(width / 3) : Math.floor(width / cols);
|
||||
var cellHeight = mode === "tile" ? cellWidth * 0.75 : cellWidth + 24;
|
||||
var row = Math.floor(itemInSection / cols);
|
||||
var col = itemInSection % cols;
|
||||
itemY = sectionY + 32 + row * cellHeight;
|
||||
itemX = col * cellWidth + cellWidth / 2;
|
||||
itemH = cellHeight;
|
||||
}
|
||||
|
||||
var visualY = itemY - mainFlickable.contentY + itemH / 2;
|
||||
var clampedY = Math.max(40, Math.min(height - 40, visualY));
|
||||
return mapToItem(null, itemX, clampedY);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.controller
|
||||
function onSelectedFlatIndexChanged() {
|
||||
if (root.controller?.keyboardNavigationActive) {
|
||||
Qt.callLater(() => root.ensureVisible(root.controller.selectedFlatIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
id: mainFlickable
|
||||
anchors.fill: parent
|
||||
contentWidth: width
|
||||
contentHeight: sectionsColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: sectionsColumn
|
||||
width: parent.width
|
||||
|
||||
Repeater {
|
||||
model: root.controller?.sections ?? []
|
||||
|
||||
Column {
|
||||
id: sectionDelegate
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
readonly property int versionTrigger: root.controller?.viewModeVersion ?? 0
|
||||
readonly property string sectionId: modelData?.id ?? ""
|
||||
readonly property string currentViewMode: {
|
||||
void (versionTrigger);
|
||||
return root.controller?.getSectionViewMode(sectionId) ?? "list";
|
||||
}
|
||||
readonly property bool isGridMode: currentViewMode === "grid" || currentViewMode === "tile"
|
||||
readonly property bool isCollapsed: modelData?.collapsed ?? false
|
||||
|
||||
width: sectionsColumn.width
|
||||
|
||||
SectionHeader {
|
||||
width: parent.width
|
||||
height: 32
|
||||
section: sectionDelegate.modelData
|
||||
controller: root.controller
|
||||
viewMode: sectionDelegate.currentViewMode
|
||||
canChangeViewMode: root.controller?.canChangeSectionViewMode(sectionDelegate.sectionId) ?? false
|
||||
canCollapse: root.controller?.canCollapseSection(sectionDelegate.sectionId) ?? false
|
||||
}
|
||||
|
||||
Column {
|
||||
id: listContent
|
||||
width: parent.width
|
||||
visible: !sectionDelegate.isGridMode && !sectionDelegate.isCollapsed
|
||||
|
||||
Repeater {
|
||||
model: sectionDelegate.isGridMode || sectionDelegate.isCollapsed ? [] : (sectionDelegate.modelData?.items ?? [])
|
||||
|
||||
ResultItem {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: listContent.width
|
||||
height: 52
|
||||
item: modelData
|
||||
isSelected: getFlatIndex() === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: getFlatIndex()
|
||||
|
||||
function getFlatIndex() {
|
||||
if (!sectionDelegate?.sectionId)
|
||||
return -1;
|
||||
var flatIdx = 0;
|
||||
var sections = root.controller?.sections ?? [];
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
flatIdx++;
|
||||
if (sections[i].id === sectionDelegate.sectionId)
|
||||
return flatIdx + index;
|
||||
if (!sections[i].collapsed)
|
||||
flatIdx += sections[i].items?.length ?? 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (root.controller) {
|
||||
root.controller.executeItem(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(getFlatIndex(), modelData, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: gridContent
|
||||
width: parent.width
|
||||
visible: sectionDelegate.isGridMode && !sectionDelegate.isCollapsed
|
||||
columns: sectionDelegate.currentViewMode === "tile" ? 3 : root.gridColumns
|
||||
|
||||
readonly property real cellWidth: sectionDelegate.currentViewMode === "tile" ? Math.floor(width / 3) : Math.floor(width / root.gridColumns)
|
||||
readonly property real cellHeight: sectionDelegate.currentViewMode === "tile" ? cellWidth * 0.75 : cellWidth + 24
|
||||
|
||||
Repeater {
|
||||
model: sectionDelegate.isGridMode && !sectionDelegate.isCollapsed ? (sectionDelegate.modelData?.items ?? []) : []
|
||||
|
||||
Item {
|
||||
id: gridDelegateItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: gridContent.cellWidth
|
||||
height: gridContent.cellHeight
|
||||
|
||||
function getFlatIndex() {
|
||||
if (!sectionDelegate?.sectionId)
|
||||
return -1;
|
||||
var flatIdx = 0;
|
||||
var sections = root.controller?.sections ?? [];
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
flatIdx++;
|
||||
if (sections[i].id === sectionDelegate.sectionId)
|
||||
return flatIdx + index;
|
||||
if (!sections[i].collapsed)
|
||||
flatIdx += sections[i].items?.length ?? 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int cachedFlatIndex: getFlatIndex()
|
||||
|
||||
GridItem {
|
||||
width: parent.width - 4
|
||||
height: parent.height - 4
|
||||
anchors.centerIn: parent
|
||||
visible: sectionDelegate.currentViewMode === "grid"
|
||||
item: gridDelegateItem.modelData
|
||||
isSelected: gridDelegateItem.cachedFlatIndex === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: gridDelegateItem.cachedFlatIndex
|
||||
|
||||
onClicked: {
|
||||
if (root.controller) {
|
||||
root.controller.executeItem(gridDelegateItem.modelData);
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(gridDelegateItem.cachedFlatIndex, gridDelegateItem.modelData, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
TileItem {
|
||||
width: parent.width - 4
|
||||
height: parent.height - 4
|
||||
anchors.centerIn: parent
|
||||
visible: sectionDelegate.currentViewMode === "tile"
|
||||
item: gridDelegateItem.modelData
|
||||
isSelected: gridDelegateItem.cachedFlatIndex === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: gridDelegateItem.cachedFlatIndex
|
||||
|
||||
onClicked: {
|
||||
if (root.controller) {
|
||||
root.controller.executeItem(gridDelegateItem.modelData);
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(gridDelegateItem.cachedFlatIndex, gridDelegateItem.modelData, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bottomShadow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 24
|
||||
z: 100
|
||||
visible: {
|
||||
if (mainFlickable.contentHeight <= mainFlickable.height)
|
||||
return false;
|
||||
var atBottom = mainFlickable.contentY >= mainFlickable.contentHeight - mainFlickable.height - 5;
|
||||
if (atBottom)
|
||||
return false;
|
||||
|
||||
var flatModel = root.controller?.flatModel;
|
||||
if (!flatModel || flatModel.length === 0)
|
||||
return false;
|
||||
var lastItemIdx = -1;
|
||||
for (var i = flatModel.length - 1; i >= 0; i--) {
|
||||
if (!flatModel[i].isHeader) {
|
||||
lastItemIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastItemIdx >= 0 && root.controller?.selectedFlatIndex === lastItemIdx)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: stickyHeader
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
height: 32
|
||||
z: 101
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
visible: stickyHeaderSection !== null
|
||||
|
||||
readonly property int versionTrigger: root.controller?.viewModeVersion ?? 0
|
||||
|
||||
readonly property var stickyHeaderSection: {
|
||||
if (!root.controller?.sections || root.controller.sections.length === 0)
|
||||
return null;
|
||||
var sections = root.controller.sections;
|
||||
if (sections.length === 0)
|
||||
return null;
|
||||
|
||||
var scrollY = mainFlickable.contentY;
|
||||
if (scrollY <= 0)
|
||||
return null;
|
||||
|
||||
var y = 0;
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
var section = sections[i];
|
||||
var sectionHeight = root.getSectionHeight(section);
|
||||
if (scrollY < y + sectionHeight)
|
||||
return section;
|
||||
y += sectionHeight;
|
||||
}
|
||||
return sections[sections.length - 1];
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
width: parent.width
|
||||
section: stickyHeader.stickyHeaderSection
|
||||
controller: root.controller
|
||||
viewMode: {
|
||||
void (stickyHeader.versionTrigger);
|
||||
return root.controller?.getSectionViewMode(stickyHeader.stickyHeaderSection?.id) ?? "list";
|
||||
}
|
||||
canChangeViewMode: {
|
||||
void (stickyHeader.versionTrigger);
|
||||
return root.controller?.canChangeSectionViewMode(stickyHeader.stickyHeaderSection?.id) ?? false;
|
||||
}
|
||||
canCollapse: {
|
||||
void (stickyHeader.versionTrigger);
|
||||
return root.controller?.canCollapseSection(stickyHeader.stickyHeaderSection?.id) ?? false;
|
||||
}
|
||||
isSticky: true
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.centerIn: parent
|
||||
visible: (!root.controller?.sections || root.controller.sections.length === 0) && !root.controller?.isFileSearching
|
||||
width: emptyColumn.implicitWidth
|
||||
height: emptyColumn.implicitHeight
|
||||
|
||||
Column {
|
||||
id: emptyColumn
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: getEmptyIcon()
|
||||
size: 48
|
||||
color: Theme.outlineButton
|
||||
|
||||
function getEmptyIcon() {
|
||||
var mode = root.controller?.searchMode ?? "all";
|
||||
switch (mode) {
|
||||
case "files":
|
||||
return "folder_open";
|
||||
case "plugins":
|
||||
return "extension";
|
||||
case "apps":
|
||||
return "apps";
|
||||
default:
|
||||
return root.controller?.searchQuery?.length > 0 ? "search_off" : "search";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: getEmptyText()
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
function getEmptyText() {
|
||||
var mode = root.controller?.searchMode ?? "all";
|
||||
var hasQuery = root.controller?.searchQuery?.length > 0;
|
||||
|
||||
switch (mode) {
|
||||
case "files":
|
||||
if (!DSearchService.dsearchAvailable)
|
||||
return I18n.tr("File search requires dsearch\nInstall from github.com/morelazers/dsearch");
|
||||
if (!hasQuery)
|
||||
return I18n.tr("Type to search files");
|
||||
if (root.controller.searchQuery.length < 2)
|
||||
return I18n.tr("Type at least 2 characters");
|
||||
return I18n.tr("No files found");
|
||||
case "plugins":
|
||||
return hasQuery ? I18n.tr("No plugin results") : I18n.tr("Browse or search plugins");
|
||||
case "apps":
|
||||
return hasQuery ? I18n.tr("No apps found") : I18n.tr("Type to search apps");
|
||||
default:
|
||||
return hasQuery ? I18n.tr("No results found") : I18n.tr("Type to search");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
245
quickshell/Modals/DankLauncherV2/Scorer.js
Normal file
245
quickshell/Modals/DankLauncherV2/Scorer.js
Normal file
@@ -0,0 +1,245 @@
|
||||
.pragma library
|
||||
|
||||
const Weights = {
|
||||
exactMatch: 10000,
|
||||
prefixMatch: 5000,
|
||||
wordBoundary: 1000,
|
||||
substring: 500,
|
||||
fuzzy: 100,
|
||||
frecency: 2000,
|
||||
typeBonus: {
|
||||
app: 1000,
|
||||
plugin: 900,
|
||||
file: 800,
|
||||
action: 600
|
||||
}
|
||||
}
|
||||
|
||||
function tokenize(text) {
|
||||
return text.toLowerCase().trim().split(/[\s\-_]+/).filter(function(w) { return w.length > 0 })
|
||||
}
|
||||
|
||||
function hasWordBoundaryMatch(text, query) {
|
||||
var textWords = tokenize(text)
|
||||
var queryWords = tokenize(query)
|
||||
|
||||
if (queryWords.length === 0) return false
|
||||
if (queryWords.length > textWords.length) return false
|
||||
|
||||
for (var i = 0; i <= textWords.length - queryWords.length; i++) {
|
||||
var allMatch = true
|
||||
for (var j = 0; j < queryWords.length; j++) {
|
||||
if (!textWords[i + j].startsWith(queryWords[j])) {
|
||||
allMatch = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (allMatch) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function levenshteinDistance(s1, s2) {
|
||||
var len1 = s1.length
|
||||
var len2 = s2.length
|
||||
var matrix = []
|
||||
|
||||
for (var i = 0; i <= len1; i++) {
|
||||
matrix[i] = [i]
|
||||
}
|
||||
for (var j = 0; j <= len2; j++) {
|
||||
matrix[0][j] = j
|
||||
}
|
||||
|
||||
for (var i = 1; i <= len1; i++) {
|
||||
for (var j = 1; j <= len2; j++) {
|
||||
var cost = s1[i - 1] === s2[j - 1] ? 0 : 1
|
||||
matrix[i][j] = Math.min(
|
||||
matrix[i - 1][j] + 1,
|
||||
matrix[i][j - 1] + 1,
|
||||
matrix[i - 1][j - 1] + cost
|
||||
)
|
||||
}
|
||||
}
|
||||
return matrix[len1][len2]
|
||||
}
|
||||
|
||||
function fuzzyScore(text, query) {
|
||||
var maxDistance = query.length === 3 ? 1 : query.length <= 6 ? 2 : 3
|
||||
var bestScore = 0
|
||||
|
||||
if (Math.abs(text.length - query.length) <= maxDistance) {
|
||||
var distance = levenshteinDistance(text, query)
|
||||
if (distance <= maxDistance) {
|
||||
var maxLen = Math.max(text.length, query.length)
|
||||
bestScore = 1 - (distance / maxLen)
|
||||
}
|
||||
}
|
||||
|
||||
var words = tokenize(text)
|
||||
for (var i = 0; i < words.length && bestScore < 0.8; i++) {
|
||||
if (Math.abs(words[i].length - query.length) > maxDistance) continue
|
||||
var wordDistance = levenshteinDistance(words[i], query)
|
||||
if (wordDistance <= maxDistance) {
|
||||
var wordMaxLen = Math.max(words[i].length, query.length)
|
||||
var score = 1 - (wordDistance / wordMaxLen)
|
||||
bestScore = Math.max(bestScore, score)
|
||||
}
|
||||
}
|
||||
|
||||
return bestScore
|
||||
}
|
||||
|
||||
function getTimeBucketWeight(daysSinceUsed) {
|
||||
for (var i = 0; i < TimeBuckets.length; i++) {
|
||||
if (daysSinceUsed <= TimeBuckets[i].maxDays) {
|
||||
return TimeBuckets[i].weight
|
||||
}
|
||||
}
|
||||
return 10
|
||||
}
|
||||
|
||||
function calculateTextScore(name, query) {
|
||||
if (name === query) return Weights.exactMatch
|
||||
if (name.startsWith(query)) return Weights.prefixMatch
|
||||
if (name.includes(query)) return Weights.substring
|
||||
if (hasWordBoundaryMatch(name, query)) return Weights.wordBoundary
|
||||
|
||||
if (query.length >= 3) {
|
||||
var fs = fuzzyScore(name, query)
|
||||
if (fs > 0) return fs * Weights.fuzzy
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function score(item, query, frecencyData) {
|
||||
var typeBonus = Weights.typeBonus[item.type] || 0
|
||||
|
||||
if (!query || query.length === 0) {
|
||||
var usageCount = frecencyData ? frecencyData.usageCount : 0
|
||||
return typeBonus + (usageCount * 100)
|
||||
}
|
||||
|
||||
var name = (item.name || "").toLowerCase()
|
||||
var q = query.toLowerCase()
|
||||
|
||||
var textScore = calculateTextScore(name, q)
|
||||
|
||||
if (textScore === 0 && item.subtitle) {
|
||||
var subtitleScore = calculateTextScore(item.subtitle.toLowerCase(), q)
|
||||
textScore = subtitleScore * 0.5
|
||||
}
|
||||
|
||||
if (textScore === 0 && item.keywords) {
|
||||
for (var i = 0; i < item.keywords.length; i++) {
|
||||
var keywordScore = calculateTextScore(item.keywords[i].toLowerCase(), q)
|
||||
if (keywordScore > 0) {
|
||||
textScore = keywordScore * 0.3
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (textScore === 0) return 0
|
||||
|
||||
var usageBonus = frecencyData ? Math.min(frecencyData.usageCount * 10, Weights.frecency) : 0
|
||||
|
||||
return textScore + usageBonus + typeBonus
|
||||
}
|
||||
|
||||
function scoreItems(items, query, getFrecencyFn) {
|
||||
var scored = []
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i]
|
||||
var frecencyData = getFrecencyFn ? getFrecencyFn(item) : null
|
||||
var itemScore = score(item, query, frecencyData)
|
||||
|
||||
if (itemScore > 0 || !query || query.length === 0) {
|
||||
scored.push({
|
||||
item: item,
|
||||
score: itemScore
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
scored.sort(function(a, b) {
|
||||
return b.score - a.score
|
||||
})
|
||||
|
||||
return scored
|
||||
}
|
||||
|
||||
function groupBySection(scoredItems, sectionOrder, sortAlphabetically, maxPerSection) {
|
||||
var sections = {}
|
||||
var result = []
|
||||
var limit = maxPerSection || 50
|
||||
|
||||
for (var i = 0; i < sectionOrder.length; i++) {
|
||||
var sectionId = sectionOrder[i].id
|
||||
sections[sectionId] = {
|
||||
id: sectionId,
|
||||
title: sectionOrder[i].title,
|
||||
icon: sectionOrder[i].icon,
|
||||
priority: sectionOrder[i].priority,
|
||||
items: [],
|
||||
collapsed: false
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < scoredItems.length; i++) {
|
||||
var scoredItem = scoredItems[i]
|
||||
var item = scoredItem.item
|
||||
var sectionId = item.section || "apps"
|
||||
|
||||
if (sections[sectionId] && sections[sectionId].items.length < limit) {
|
||||
sections[sectionId].items.push(item)
|
||||
} else if (sections["apps"] && sections["apps"].items.length < limit) {
|
||||
sections["apps"].items.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < sectionOrder.length; i++) {
|
||||
var section = sections[sectionOrder[i].id]
|
||||
if (section && section.items.length > 0) {
|
||||
if (sortAlphabetically && section.id === "apps") {
|
||||
section.items.sort(function(a, b) {
|
||||
return (a.name || "").localeCompare(b.name || "")
|
||||
})
|
||||
}
|
||||
result.push(section)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function flattenSections(sections) {
|
||||
var flat = []
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
var section = sections[i]
|
||||
|
||||
flat.push({
|
||||
isHeader: true,
|
||||
section: section,
|
||||
sectionId: section.id,
|
||||
sectionIndex: i
|
||||
})
|
||||
|
||||
if (!section.collapsed) {
|
||||
for (var j = 0; j < section.items.length; j++) {
|
||||
flat.push({
|
||||
isHeader: false,
|
||||
item: section.items[j],
|
||||
sectionId: section.id,
|
||||
sectionIndex: i,
|
||||
indexInSection: j
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flat
|
||||
}
|
||||
114
quickshell/Modals/DankLauncherV2/Section.qml
Normal file
114
quickshell/Modals/DankLauncherV2/Section.qml
Normal file
@@ -0,0 +1,114 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var section: null
|
||||
property var controller: null
|
||||
property string viewMode: "list"
|
||||
property int gridColumns: 4
|
||||
property int startIndex: 0
|
||||
|
||||
signal itemClicked(int flatIndex)
|
||||
signal itemRightClicked(int flatIndex, var item, real mouseX, real mouseY)
|
||||
|
||||
height: headerItem.height + (section?.collapsed ? 0 : contentLoader.height + Theme.spacingXS)
|
||||
width: parent?.width ?? 200
|
||||
|
||||
SectionHeader {
|
||||
id: headerItem
|
||||
width: parent.width
|
||||
section: root.section
|
||||
controller: root.controller
|
||||
viewMode: root.viewMode
|
||||
canChangeViewMode: root.controller?.canChangeSectionViewMode(root.section?.id) ?? true
|
||||
|
||||
onViewModeToggled: {
|
||||
if (root.controller && root.section) {
|
||||
var newMode = root.viewMode === "list" ? "grid" : "list";
|
||||
root.controller.setSectionViewMode(root.section.id, newMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.top: headerItem.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
active: !root.section?.collapsed
|
||||
visible: active
|
||||
|
||||
sourceComponent: root.viewMode === "grid" ? gridComponent : listComponent
|
||||
|
||||
Component {
|
||||
id: listComponent
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
width: contentLoader.width
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.section?.items ?? []
|
||||
objectProp: "id"
|
||||
}
|
||||
|
||||
ResultItem {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent?.width ?? 200
|
||||
item: modelData
|
||||
isSelected: (root.startIndex + index) === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: root.startIndex + index
|
||||
|
||||
onClicked: root.itemClicked(root.startIndex + index)
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(root.startIndex + index, modelData, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: gridComponent
|
||||
|
||||
Flow {
|
||||
width: contentLoader.width
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.section?.items ?? []
|
||||
objectProp: "id"
|
||||
}
|
||||
|
||||
GridItem {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: Math.floor(contentLoader.width / root.gridColumns)
|
||||
height: width + 24
|
||||
item: modelData
|
||||
isSelected: (root.startIndex + index) === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: root.startIndex + index
|
||||
|
||||
onClicked: root.itemClicked(root.startIndex + index)
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(root.startIndex + index, modelData, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
169
quickshell/Modals/DankLauncherV2/SectionHeader.qml
Normal file
169
quickshell/Modals/DankLauncherV2/SectionHeader.qml
Normal file
@@ -0,0 +1,169 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var section: null
|
||||
property var controller: null
|
||||
property string viewMode: "list"
|
||||
property bool canChangeViewMode: true
|
||||
property bool canCollapse: true
|
||||
property bool isSticky: false
|
||||
|
||||
signal viewModeToggled
|
||||
|
||||
width: parent?.width ?? 200
|
||||
height: 32
|
||||
color: isSticky ? "transparent" : (hoverArea.containsMouse ? Theme.surfaceHover : "transparent")
|
||||
radius: Theme.cornerRadius / 2
|
||||
|
||||
MouseArea {
|
||||
id: hoverArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
Row {
|
||||
id: leftContent
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: root.section?.icon ?? "folder"
|
||||
size: 16
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.section?.title ?? ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.section?.items?.length ?? 0
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.outlineButton
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rightContent
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
id: viewModeRow
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
visible: root.canChangeViewMode && !root.section?.collapsed
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{
|
||||
mode: "list",
|
||||
icon: "view_list"
|
||||
},
|
||||
{
|
||||
mode: "grid",
|
||||
icon: "grid_view"
|
||||
},
|
||||
{
|
||||
mode: "tile",
|
||||
icon: "view_module"
|
||||
}
|
||||
]
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 4
|
||||
color: root.viewMode === modelData.mode ? Theme.primaryHover : modeArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: parent.modelData.icon
|
||||
size: 14
|
||||
color: root.viewMode === parent.modelData.mode ? Theme.primary : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: modeArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.viewMode !== parent.modelData.mode && root.controller && root.section) {
|
||||
root.controller.setSectionViewMode(root.section.id, parent.modelData.mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: collapseButton
|
||||
width: root.canCollapse ? 24 : 0
|
||||
height: 24
|
||||
visible: root.canCollapse
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: root.section?.collapsed ? "expand_more" : "expand_less"
|
||||
size: 16
|
||||
color: collapseArea.containsMouse ? Theme.primary : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: collapseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.controller && root.section) {
|
||||
root.controller.toggleSection(root.section.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: rightContent.width + Theme.spacingS
|
||||
cursorShape: root.canCollapse ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: root.canCollapse
|
||||
onClicked: {
|
||||
if (root.canCollapse && root.controller && root.section) {
|
||||
root.controller.toggleSection(root.section.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 1
|
||||
color: Theme.outlineMedium
|
||||
visible: root.isSticky
|
||||
}
|
||||
}
|
||||
136
quickshell/Modals/DankLauncherV2/TileItem.qml
Normal file
136
quickshell/Modals/DankLauncherV2/TileItem.qml
Normal file
@@ -0,0 +1,136 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var item: null
|
||||
property bool isSelected: false
|
||||
property bool isHovered: itemArea.containsMouse
|
||||
property var controller: null
|
||||
property int flatIndex: -1
|
||||
|
||||
signal clicked
|
||||
signal rightClicked(real mouseX, real mouseY)
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
|
||||
border.width: isSelected ? 2 : 0
|
||||
border.color: Theme.primary
|
||||
|
||||
readonly property string iconValue: {
|
||||
if (!item)
|
||||
return "";
|
||||
var data = item.data;
|
||||
if (data?.imageUrl)
|
||||
return "image:" + data.imageUrl;
|
||||
if (data?.imagePath)
|
||||
return "image:" + data.imagePath;
|
||||
if (data?.path && isImageFile(data.path))
|
||||
return "image:" + data.path;
|
||||
switch (item.iconType) {
|
||||
case "material":
|
||||
case "nerd":
|
||||
return "material:" + (item.icon || "image");
|
||||
case "unicode":
|
||||
return "unicode:" + (item.icon || "");
|
||||
case "composite":
|
||||
return item.iconFull || "";
|
||||
case "image":
|
||||
default:
|
||||
return item.icon || "";
|
||||
}
|
||||
}
|
||||
|
||||
function isImageFile(path) {
|
||||
if (!path)
|
||||
return false;
|
||||
var ext = path.split('.').pop().toLowerCase();
|
||||
return ["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp"].indexOf(ext) >= 0;
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
|
||||
Rectangle {
|
||||
id: imageContainer
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius - 2
|
||||
color: Theme.surfaceContainerHigh
|
||||
clip: true
|
||||
|
||||
AppIconRenderer {
|
||||
anchors.fill: parent
|
||||
iconValue: root.iconValue
|
||||
iconSize: Math.min(parent.width, parent.height)
|
||||
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
|
||||
materialIconSizeAdjustment: iconSize * 0.3
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: labelText.implicitHeight + Theme.spacingS * 2
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, 0.85)
|
||||
visible: root.item?.name?.length > 0
|
||||
|
||||
StyledText {
|
||||
id: labelText
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXS
|
||||
text: root.item?.name ?? ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingXS
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
color: Theme.primary
|
||||
visible: root.isSelected
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "check"
|
||||
size: 14
|
||||
color: Theme.primaryText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var scenePos = mapToItem(null, mouse.x, mouse.y);
|
||||
root.rightClicked(scenePos.x, scenePos.y);
|
||||
return;
|
||||
}
|
||||
root.clicked();
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (root.controller)
|
||||
root.controller.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -539,7 +539,7 @@ Rectangle {
|
||||
|
||||
Item {
|
||||
width: parent.width - parent.leftPadding - parent.rightPadding
|
||||
height: Theme.spacingS
|
||||
height: Theme.spacingXS
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
@@ -717,7 +717,7 @@ Rectangle {
|
||||
|
||||
Item {
|
||||
width: parent.width - parent.leftPadding - parent.rightPadding
|
||||
height: Theme.spacingS
|
||||
height: Theme.spacingXS
|
||||
visible: !root.searchActive
|
||||
}
|
||||
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: controller
|
||||
|
||||
property string searchQuery: ""
|
||||
property alias model: fileModel
|
||||
property int selectedIndex: 0
|
||||
property bool keyboardNavigationActive: false
|
||||
property bool isSearching: false
|
||||
property int totalResults: 0
|
||||
property string searchField: "filename"
|
||||
|
||||
signal searchCompleted
|
||||
|
||||
ListModel {
|
||||
id: fileModel
|
||||
}
|
||||
|
||||
function performSearch() {
|
||||
if (!DSearchService.dsearchAvailable) {
|
||||
model.clear()
|
||||
totalResults = 0
|
||||
isSearching = false
|
||||
return
|
||||
}
|
||||
|
||||
if (searchQuery.length === 0) {
|
||||
model.clear()
|
||||
totalResults = 0
|
||||
isSearching = false
|
||||
return
|
||||
}
|
||||
|
||||
isSearching = true
|
||||
const params = {
|
||||
"limit": 50,
|
||||
"fuzzy": true,
|
||||
"sort": "score",
|
||||
"desc": true
|
||||
}
|
||||
|
||||
if (searchField && searchField !== "all") {
|
||||
params.field = searchField
|
||||
}
|
||||
|
||||
DSearchService.search(searchQuery, params, response => {
|
||||
if (response.error) {
|
||||
model.clear()
|
||||
totalResults = 0
|
||||
isSearching = false
|
||||
return
|
||||
}
|
||||
|
||||
if (response.result) {
|
||||
updateModel(response.result)
|
||||
}
|
||||
|
||||
isSearching = false
|
||||
searchCompleted()
|
||||
})
|
||||
}
|
||||
|
||||
function updateModel(result) {
|
||||
model.clear()
|
||||
totalResults = result.total_hits || 0
|
||||
selectedIndex = 0
|
||||
keyboardNavigationActive = true
|
||||
|
||||
if (!result.hits || result.hits.length === 0) {
|
||||
selectedIndex = -1
|
||||
keyboardNavigationActive = false
|
||||
return
|
||||
}
|
||||
|
||||
for (var i = 0; i < result.hits.length; i++) {
|
||||
const hit = result.hits[i]
|
||||
const filePath = hit.id || ""
|
||||
const fileName = getFileName(filePath)
|
||||
const fileExt = getFileExtension(fileName)
|
||||
const fileType = determineFileType(fileName, filePath)
|
||||
const dirPath = getDirPath(filePath)
|
||||
|
||||
model.append({
|
||||
"filePath": filePath,
|
||||
"fileName": fileName,
|
||||
"fileExtension": fileExt,
|
||||
"fileType": fileType,
|
||||
"dirPath": dirPath,
|
||||
"score": hit.score || 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getFileName(path) {
|
||||
const parts = path.split('/')
|
||||
return parts[parts.length - 1] || path
|
||||
}
|
||||
|
||||
function getFileExtension(fileName) {
|
||||
const parts = fileName.split('.')
|
||||
if (parts.length > 1) {
|
||||
return parts[parts.length - 1].toLowerCase()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function getDirPath(path) {
|
||||
const lastSlash = path.lastIndexOf('/')
|
||||
if (lastSlash > 0) {
|
||||
return path.substring(0, lastSlash)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function determineFileType(fileName, filePath) {
|
||||
const ext = getFileExtension(fileName)
|
||||
|
||||
const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"]
|
||||
if (imageExts.includes(ext)) {
|
||||
return "image"
|
||||
}
|
||||
|
||||
const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"]
|
||||
if (videoExts.includes(ext)) {
|
||||
return "video"
|
||||
}
|
||||
|
||||
const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"]
|
||||
if (audioExts.includes(ext)) {
|
||||
return "audio"
|
||||
}
|
||||
|
||||
const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"]
|
||||
if (codeExts.includes(ext)) {
|
||||
return "code"
|
||||
}
|
||||
|
||||
const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"]
|
||||
if (docExts.includes(ext)) {
|
||||
return "document"
|
||||
}
|
||||
|
||||
const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"]
|
||||
if (archiveExts.includes(ext)) {
|
||||
return "archive"
|
||||
}
|
||||
|
||||
if (!ext || fileName.indexOf('.') === -1) {
|
||||
return "binary"
|
||||
}
|
||||
|
||||
return "file"
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (model.count === 0) {
|
||||
return
|
||||
}
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = Math.min(selectedIndex + 1, model.count - 1)
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (model.count === 0) {
|
||||
return
|
||||
}
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0)
|
||||
}
|
||||
|
||||
signal fileOpened
|
||||
|
||||
function openFile(filePath) {
|
||||
if (!filePath || filePath.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let url = filePath
|
||||
if (!url.startsWith("file://")) {
|
||||
url = "file://" + filePath
|
||||
}
|
||||
|
||||
Qt.openUrlExternally(url)
|
||||
fileOpened()
|
||||
}
|
||||
|
||||
function openFolder(filePath) {
|
||||
if (!filePath || filePath.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const lastSlash = filePath.lastIndexOf('/')
|
||||
if (lastSlash <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const dirPath = filePath.substring(0, lastSlash)
|
||||
let url = dirPath
|
||||
if (!url.startsWith("file://")) {
|
||||
url = "file://" + dirPath
|
||||
}
|
||||
|
||||
Qt.openUrlExternally(url)
|
||||
fileOpened()
|
||||
}
|
||||
|
||||
function openSelected() {
|
||||
if (model.count === 0 || selectedIndex < 0 || selectedIndex >= model.count) {
|
||||
return
|
||||
}
|
||||
|
||||
const item = model.get(selectedIndex)
|
||||
if (item && item.filePath) {
|
||||
openFile(item.filePath)
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
searchQuery = ""
|
||||
model.clear()
|
||||
selectedIndex = -1
|
||||
keyboardNavigationActive = false
|
||||
isSearching = false
|
||||
totalResults = 0
|
||||
}
|
||||
|
||||
onSearchQueryChanged: {
|
||||
performSearch()
|
||||
}
|
||||
|
||||
onSearchFieldChanged: {
|
||||
performSearch()
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: entry
|
||||
|
||||
required property string filePath
|
||||
required property string fileName
|
||||
required property string fileExtension
|
||||
required property string fileType
|
||||
required property string dirPath
|
||||
required property bool isSelected
|
||||
required property int itemIndex
|
||||
|
||||
signal clicked()
|
||||
|
||||
readonly property int iconSize: 40
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: isSelected ? Theme.primaryPressed : mouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Item {
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Image {
|
||||
id: imagePreview
|
||||
anchors.fill: parent
|
||||
source: fileType === "image" ? `file://${filePath}` : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
cache: true
|
||||
asynchronous: true
|
||||
visible: fileType === "image" && status === Image.Ready
|
||||
sourceSize.width: 128
|
||||
sourceSize.height: 128
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
source: imagePreview
|
||||
maskEnabled: true
|
||||
maskSource: imageMask
|
||||
visible: fileType === "image" && imagePreview.status === Image.Ready
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
id: imageMask
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: getFileTypeColor()
|
||||
visible: fileType !== "image" || imagePreview.status !== Image.Ready
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: getFileIconText()
|
||||
font.pixelSize: fileExtension.length > 0 ? (fileExtension.length > 3 ? Theme.fontSizeSmall - 2 : Theme.fontSizeSmall) : Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - iconSize - Theme.spacingL
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: fileName
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideMiddle
|
||||
wrapMode: Text.NoWrap
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: dirPath
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideMiddle
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: entry.clicked()
|
||||
}
|
||||
|
||||
function getFileTypeColor() {
|
||||
switch (fileType) {
|
||||
case "code":
|
||||
return Theme.codeFileColor || Theme.primarySelected
|
||||
case "document":
|
||||
return Theme.docFileColor || Theme.secondarySelected
|
||||
case "video":
|
||||
return Theme.videoFileColor || Theme.tertiarySelected
|
||||
case "audio":
|
||||
return Theme.audioFileColor || Theme.errorSelected
|
||||
case "archive":
|
||||
return Theme.archiveFileColor || Theme.warningSelected
|
||||
case "binary":
|
||||
return Theme.binaryFileColor || Theme.surfaceDim
|
||||
default:
|
||||
return Theme.surfaceLight
|
||||
}
|
||||
}
|
||||
|
||||
function getFileIconText() {
|
||||
if (fileType === "binary") {
|
||||
return "bin"
|
||||
}
|
||||
|
||||
if (fileExtension.length > 0) {
|
||||
return fileExtension
|
||||
}
|
||||
|
||||
return fileName.charAt(0).toUpperCase()
|
||||
}
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: resultsContainer
|
||||
|
||||
property var fileSearchController: null
|
||||
|
||||
function resetScroll() {
|
||||
filesList.contentY = 0;
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 32
|
||||
z: 100
|
||||
visible: filesList.contentHeight > filesList.height && (filesList.currentIndex < filesList.count - 1 || filesList.contentY < filesList.contentHeight - filesList.height - 1)
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: filesList
|
||||
|
||||
property int itemHeight: 60
|
||||
property int itemSpacing: Theme.spacingS
|
||||
property bool hoverUpdatesSelection: false
|
||||
property bool keyboardNavigationActive: fileSearchController ? fileSearchController.keyboardNavigationActive : false
|
||||
|
||||
signal keyboardNavigationReset
|
||||
signal itemClicked(int index)
|
||||
signal itemRightClicked(int index)
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count)
|
||||
return;
|
||||
const itemY = index * (itemHeight + itemSpacing);
|
||||
const itemBottom = itemY + itemHeight;
|
||||
const fadeHeight = 32;
|
||||
const isLastItem = index === count - 1;
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
else if (itemBottom > contentY + height - (isLastItem ? 0 : fadeHeight))
|
||||
contentY = Math.min(itemBottom - height + (isLastItem ? 0 : fadeHeight), contentHeight - height);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.bottomMargin: 1
|
||||
model: fileSearchController ? fileSearchController.model : null
|
||||
currentIndex: fileSearchController ? fileSearchController.selectedIndex : -1
|
||||
clip: true
|
||||
spacing: itemSpacing
|
||||
focus: true
|
||||
interactive: true
|
||||
cacheBuffer: Math.max(0, Math.min(height * 2, 1000))
|
||||
reuseItems: true
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
if (keyboardNavigationActive)
|
||||
ensureVisible(currentIndex);
|
||||
}
|
||||
|
||||
onItemClicked: function (index) {
|
||||
if (fileSearchController) {
|
||||
const item = fileSearchController.model.get(index);
|
||||
fileSearchController.openFile(item.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
onItemRightClicked: function (index) {
|
||||
if (fileSearchController) {
|
||||
const item = fileSearchController.model.get(index);
|
||||
fileSearchController.openFolder(item.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
onKeyboardNavigationReset: {
|
||||
if (fileSearchController)
|
||||
fileSearchController.keyboardNavigationActive = false;
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property int index
|
||||
required property string filePath
|
||||
required property string fileName
|
||||
required property string fileExtension
|
||||
required property string fileType
|
||||
required property string dirPath
|
||||
|
||||
width: ListView.view.width
|
||||
height: filesList.itemHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: ListView.isCurrentItem ? Theme.widgetBaseHoverColor : fileMouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Item {
|
||||
width: 40
|
||||
height: 40
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: Theme.surfaceLight
|
||||
visible: fileType !== "image"
|
||||
|
||||
DankNFIcon {
|
||||
id: nerdIcon
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
const lowerName = fileName.toLowerCase();
|
||||
if (lowerName.startsWith("dockerfile"))
|
||||
return "docker";
|
||||
if (lowerName.startsWith("makefile"))
|
||||
return "makefile";
|
||||
if (lowerName.startsWith("license"))
|
||||
return "license";
|
||||
if (lowerName.startsWith("readme"))
|
||||
return "readme";
|
||||
return fileExtension.toLowerCase();
|
||||
}
|
||||
size: Theme.fontSizeXLarge
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: fileExtension ? (fileExtension.length > 4 ? fileExtension.substring(0, 4) : fileExtension) : "?"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
visible: !nerdIcon.visible
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: fileType === "image"
|
||||
sourceComponent: Image {
|
||||
anchors.fill: parent
|
||||
source: "file://" + filePath
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
cache: false
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1.0
|
||||
maskSource: ShaderEffectSource {
|
||||
sourceItem: Rectangle {
|
||||
width: 40
|
||||
height: 40
|
||||
radius: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 40 - Theme.spacingL
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: fileName || ""
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideMiddle
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: dirPath || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideMiddle
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: fileMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (filesList.hoverUpdatesSelection && !filesList.keyboardNavigationActive)
|
||||
filesList.currentIndex = index;
|
||||
}
|
||||
onPositionChanged: {
|
||||
filesList.keyboardNavigationReset();
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
filesList.itemClicked(index);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
filesList.itemRightClicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: !fileSearchController || !fileSearchController.model || fileSearchController.model.count === 0
|
||||
|
||||
StyledText {
|
||||
property string displayText: {
|
||||
if (!fileSearchController) {
|
||||
return "";
|
||||
}
|
||||
if (!DSearchService.dsearchAvailable) {
|
||||
return I18n.tr("DankSearch not available");
|
||||
}
|
||||
if (fileSearchController.isSearching) {
|
||||
return I18n.tr("Searching...");
|
||||
}
|
||||
if (fileSearchController.searchQuery.length === 0) {
|
||||
return I18n.tr("Enter a search query");
|
||||
}
|
||||
if (!fileSearchController.model || fileSearchController.model.count === 0) {
|
||||
return I18n.tr("No files found");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
text: displayText
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
visible: displayText.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,864 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modals.Spotlight
|
||||
import qs.Modules.AppDrawer
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: spotlightKeyHandler
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property alias appLauncher: appLauncher
|
||||
property alias searchField: searchField
|
||||
property alias fileSearchController: fileSearchController
|
||||
property alias resultsView: resultsView
|
||||
property var parentModal: null
|
||||
property string searchMode: "apps"
|
||||
property bool usePopupContextMenu: false
|
||||
|
||||
property bool editMode: false
|
||||
property var editingApp: null
|
||||
property string editAppId: ""
|
||||
|
||||
function resetScroll() {
|
||||
if (searchMode === "apps") {
|
||||
resultsView.resetScroll();
|
||||
} else {
|
||||
fileSearchResults.resetScroll();
|
||||
}
|
||||
}
|
||||
|
||||
function updateSearchMode() {
|
||||
if (searchField.text.startsWith("/")) {
|
||||
if (searchMode !== "files") {
|
||||
searchMode = "files";
|
||||
}
|
||||
const query = searchField.text.substring(1);
|
||||
fileSearchController.searchQuery = query;
|
||||
} else {
|
||||
if (searchMode !== "apps") {
|
||||
searchMode = "apps";
|
||||
fileSearchController.reset();
|
||||
appLauncher.searchQuery = searchField.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openEditMode(app) {
|
||||
if (!app)
|
||||
return;
|
||||
editingApp = app;
|
||||
editAppId = app.id || app.execString || app.exec || "";
|
||||
const existing = SessionData.getAppOverride(editAppId);
|
||||
editNameField.text = existing?.name || "";
|
||||
editIconField.text = existing?.icon || "";
|
||||
editCommentField.text = existing?.comment || "";
|
||||
editEnvVarsField.text = existing?.envVars || "";
|
||||
editExtraFlagsField.text = existing?.extraFlags || "";
|
||||
editMode = true;
|
||||
Qt.callLater(() => editNameField.forceActiveFocus());
|
||||
}
|
||||
|
||||
function closeEditMode() {
|
||||
editMode = false;
|
||||
editingApp = null;
|
||||
editAppId = "";
|
||||
Qt.callLater(() => searchField.forceActiveFocus());
|
||||
}
|
||||
|
||||
function saveAppOverride() {
|
||||
const override = {};
|
||||
if (editNameField.text.trim())
|
||||
override.name = editNameField.text.trim();
|
||||
if (editIconField.text.trim())
|
||||
override.icon = editIconField.text.trim();
|
||||
if (editCommentField.text.trim())
|
||||
override.comment = editCommentField.text.trim();
|
||||
if (editEnvVarsField.text.trim())
|
||||
override.envVars = editEnvVarsField.text.trim();
|
||||
if (editExtraFlagsField.text.trim())
|
||||
override.extraFlags = editExtraFlagsField.text.trim();
|
||||
SessionData.setAppOverride(editAppId, override);
|
||||
closeEditMode();
|
||||
}
|
||||
|
||||
function resetAppOverride() {
|
||||
SessionData.clearAppOverride(editAppId);
|
||||
closeEditMode();
|
||||
}
|
||||
|
||||
onSearchModeChanged: {
|
||||
if (searchMode === "files") {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
} else {
|
||||
fileSearchController.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
clip: false
|
||||
Keys.onPressed: event => {
|
||||
if (editMode) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
closeEditMode();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
if (parentModal)
|
||||
parentModal.hide();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down) {
|
||||
if (searchMode === "apps") {
|
||||
appLauncher.selectNext();
|
||||
} else {
|
||||
fileSearchController.selectNext();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
if (searchMode === "apps") {
|
||||
appLauncher.selectPrevious();
|
||||
} else {
|
||||
fileSearchController.selectPrevious();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Right && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Left && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
||||
if (searchMode === "apps") {
|
||||
appLauncher.selectNext();
|
||||
} else {
|
||||
fileSearchController.selectNext();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
||||
if (searchMode === "apps") {
|
||||
appLauncher.selectPrevious();
|
||||
} else {
|
||||
fileSearchController.selectPrevious();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_L && event.modifiers & Qt.ControlModifier && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_H && event.modifiers & Qt.ControlModifier && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
if (searchMode === "apps") {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow();
|
||||
} else {
|
||||
appLauncher.selectNext();
|
||||
}
|
||||
} else {
|
||||
fileSearchController.selectNext();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
if (searchMode === "apps") {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow();
|
||||
} else {
|
||||
appLauncher.selectPrevious();
|
||||
}
|
||||
} else {
|
||||
fileSearchController.selectPrevious();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
|
||||
if (searchMode === "apps") {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow();
|
||||
} else {
|
||||
appLauncher.selectNext();
|
||||
}
|
||||
} else {
|
||||
fileSearchController.selectNext();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
|
||||
if (searchMode === "apps") {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow();
|
||||
} else {
|
||||
appLauncher.selectPrevious();
|
||||
}
|
||||
} else {
|
||||
fileSearchController.selectPrevious();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
if (searchMode === "apps") {
|
||||
appLauncher.launchSelected();
|
||||
} else if (searchMode === "files") {
|
||||
fileSearchController.openSelected();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Menu || event.key == Qt.Key_F10) {
|
||||
if (searchMode === "apps" && appLauncher.model.count > 0) {
|
||||
const selectedApp = appLauncher.model.get(appLauncher.selectedIndex);
|
||||
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
||||
if (selectedApp && menu && resultsView) {
|
||||
const itemPos = resultsView.getSelectedItemPosition();
|
||||
const contentPos = resultsView.mapToItem(spotlightKeyHandler, itemPos.x, itemPos.y);
|
||||
menu.show(contentPos.x, contentPos.y, selectedApp, true);
|
||||
}
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
AppLauncher {
|
||||
id: appLauncher
|
||||
viewMode: SettingsData.spotlightModalViewMode
|
||||
gridColumns: SettingsData.appLauncherGridColumns
|
||||
onAppLaunched: () => {
|
||||
if (parentModal)
|
||||
parentModal.hide();
|
||||
if (SettingsData.spotlightCloseNiriOverview && NiriService.inOverview) {
|
||||
NiriService.toggleOverview();
|
||||
}
|
||||
}
|
||||
onViewModeSelected: mode => {
|
||||
SettingsData.set("spotlightModalViewMode", mode);
|
||||
}
|
||||
}
|
||||
|
||||
FileSearchController {
|
||||
id: fileSearchController
|
||||
onFileOpened: () => {
|
||||
if (parentModal)
|
||||
parentModal.hide();
|
||||
if (SettingsData.spotlightCloseNiriOverview && NiriService.inOverview) {
|
||||
NiriService.toggleOverview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SpotlightContextMenuPopup {
|
||||
id: popupContextMenu
|
||||
parent: spotlightKeyHandler
|
||||
appLauncher: spotlightKeyHandler.appLauncher
|
||||
parentHandler: spotlightKeyHandler
|
||||
searchField: spotlightKeyHandler.searchField
|
||||
visible: false
|
||||
z: 1000
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: usePopupContextMenu && popupContextMenu.visible
|
||||
hoverEnabled: true
|
||||
z: 999
|
||||
onClicked: popupContextMenu.hide()
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: layerContextMenuLoader
|
||||
active: !spotlightKeyHandler.usePopupContextMenu
|
||||
asynchronous: false
|
||||
sourceComponent: Component {
|
||||
SpotlightContextMenu {
|
||||
appLauncher: spotlightKeyHandler.appLauncher
|
||||
parentHandler: spotlightKeyHandler
|
||||
parentModal: spotlightKeyHandler.parentModal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: parentModal
|
||||
function onSpotlightOpenChanged() {
|
||||
if (parentModal && !parentModal.spotlightOpen) {
|
||||
if (layerContextMenuLoader.item)
|
||||
layerContextMenuLoader.item.hide();
|
||||
popupContextMenu.hide();
|
||||
if (editMode)
|
||||
closeEditMode();
|
||||
}
|
||||
}
|
||||
enabled: parentModal !== null
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: popupContextMenu
|
||||
function onEditAppRequested(app) {
|
||||
spotlightKeyHandler.openEditMode(app);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: layerContextMenuLoader.item
|
||||
function onEditAppRequested(app) {
|
||||
spotlightKeyHandler.openEditMode(app);
|
||||
}
|
||||
enabled: layerContextMenuLoader.item !== null
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
clip: false
|
||||
visible: !editMode
|
||||
|
||||
Item {
|
||||
id: searchRow
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 56
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
DankTextField {
|
||||
id: searchField
|
||||
anchors.left: parent.left
|
||||
anchors.right: buttonsContainer.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
height: 56
|
||||
cornerRadius: Theme.cornerRadius
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
focusedBorderColor: Theme.primary
|
||||
leftIconName: searchMode === "files" ? "folder" : "search"
|
||||
leftIconSize: Theme.iconSize
|
||||
leftIconColor: Theme.surfaceVariantText
|
||||
leftIconFocusedColor: Theme.primary
|
||||
showClearButton: true
|
||||
textColor: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
enabled: parentModal ? parentModal.spotlightOpen : true
|
||||
placeholderText: ""
|
||||
ignoreLeftRightKeys: appLauncher.viewMode !== "list"
|
||||
ignoreTabKeys: true
|
||||
keyForwardTargets: [spotlightKeyHandler]
|
||||
onTextChanged: {
|
||||
if (searchMode === "apps")
|
||||
appLauncher.searchQuery = text;
|
||||
}
|
||||
onTextEdited: updateSearchMode()
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
if (parentModal)
|
||||
parentModal.hide();
|
||||
event.accepted = true;
|
||||
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) {
|
||||
if (searchMode === "apps") {
|
||||
if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0)
|
||||
appLauncher.launchSelected();
|
||||
else if (appLauncher.model.count > 0)
|
||||
appLauncher.launchApp(appLauncher.model.get(0));
|
||||
} else if (searchMode === "files") {
|
||||
if (fileSearchController.model.count > 0)
|
||||
fileSearchController.openSelected();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
|
||||
event.accepted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: buttonsContainer
|
||||
width: viewModeButtons.visible ? viewModeButtons.width : (fileSearchButtons.visible ? fileSearchButtons.width : 0)
|
||||
height: 36
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Row {
|
||||
id: viewModeButtons
|
||||
spacing: Theme.spacingXS
|
||||
visible: searchMode === "apps"
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
width: 36
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: appLauncher.viewMode === "list" ? Theme.primaryHover : listViewArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "view_list"
|
||||
size: 18
|
||||
color: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: listViewArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: appLauncher.setViewMode("list")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 36
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: appLauncher.viewMode === "grid" ? Theme.primaryHover : gridViewArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "grid_view"
|
||||
size: 18
|
||||
color: appLauncher.viewMode === "grid" ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: gridViewArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: appLauncher.setViewMode("grid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: fileSearchButtons
|
||||
spacing: Theme.spacingXS
|
||||
visible: searchMode === "files"
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: filenameFilterButton
|
||||
width: 36
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: fileSearchController.searchField === "filename" ? Theme.primaryHover : filenameFilterArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "title"
|
||||
size: 18
|
||||
color: fileSearchController.searchField === "filename" ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: filenameFilterArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: fileSearchController.searchField = "filename"
|
||||
onEntered: {
|
||||
filenameTooltipLoader.active = true;
|
||||
Qt.callLater(() => {
|
||||
if (filenameTooltipLoader.item) {
|
||||
const p = mapToItem(null, width / 2, height + Theme.spacingXS);
|
||||
filenameTooltipLoader.item.show(I18n.tr("Search filenames"), p.x, p.y, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
onExited: {
|
||||
if (filenameTooltipLoader.item)
|
||||
filenameTooltipLoader.item.hide();
|
||||
filenameTooltipLoader.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: contentFilterButton
|
||||
width: 36
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: fileSearchController.searchField === "body" ? Theme.primaryHover : contentFilterArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "description"
|
||||
size: 18
|
||||
color: fileSearchController.searchField === "body" ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: contentFilterArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: fileSearchController.searchField = "body"
|
||||
onEntered: {
|
||||
contentTooltipLoader.active = true;
|
||||
Qt.callLater(() => {
|
||||
if (contentTooltipLoader.item) {
|
||||
const p = mapToItem(null, width / 2, height + Theme.spacingXS);
|
||||
contentTooltipLoader.item.show(I18n.tr("Search file contents"), p.x, p.y, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
onExited: {
|
||||
if (contentTooltipLoader.item)
|
||||
contentTooltipLoader.item.hide();
|
||||
contentTooltipLoader.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height - y
|
||||
opacity: parentModal?.isClosing ? 0 : 1
|
||||
|
||||
SpotlightResults {
|
||||
id: resultsView
|
||||
anchors.fill: parent
|
||||
appLauncher: spotlightKeyHandler.appLauncher
|
||||
visible: searchMode === "apps"
|
||||
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
||||
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
||||
if (menu?.show) {
|
||||
const isPopup = menu.contentItem !== undefined;
|
||||
if (isPopup) {
|
||||
const localPos = popupContextMenu.parent.mapFromItem(null, mouseX, mouseY);
|
||||
menu.show(localPos.x, localPos.y, modelData, false);
|
||||
} else {
|
||||
menu.show(mouseX, mouseY, modelData, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileSearchResults {
|
||||
id: fileSearchResults
|
||||
anchors.fill: parent
|
||||
fileSearchController: spotlightKeyHandler.fileSearchController
|
||||
visible: searchMode === "files"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: editView
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
visible: editMode
|
||||
focus: editMode
|
||||
|
||||
Keys.onPressed: event => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Escape:
|
||||
closeEditMode();
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
saveAppOverride();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
case Qt.Key_S:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
saveAppOverride();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
case Qt.Key_R:
|
||||
if ((event.modifiers & Qt.ControlModifier) && SessionData.getAppOverride(editAppId) !== null) {
|
||||
resetAppOverride();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: backButtonArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "arrow_back"
|
||||
size: 20
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: backButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: closeEditMode()
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
width: 40
|
||||
height: 40
|
||||
source: editingApp?.icon ? "image://icon/" + editingApp.icon : "image://icon/application-x-executable"
|
||||
sourceSize.width: 40
|
||||
sourceSize.height: 40
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Edit App")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: editingApp?.name || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outlineMedium
|
||||
}
|
||||
|
||||
Flickable {
|
||||
width: parent.width
|
||||
height: parent.height - y - buttonsRow.height - Theme.spacingM
|
||||
contentHeight: editFieldsColumn.height
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Column {
|
||||
id: editFieldsColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Name")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editNameField
|
||||
width: parent.width
|
||||
height: 44
|
||||
placeholderText: editingApp?.name || ""
|
||||
keyNavigationTab: editIconField
|
||||
keyNavigationBacktab: editExtraFlagsField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Icon")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editIconField
|
||||
width: parent.width
|
||||
height: 44
|
||||
placeholderText: editingApp?.icon || ""
|
||||
keyNavigationTab: editCommentField
|
||||
keyNavigationBacktab: editNameField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Description")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editCommentField
|
||||
width: parent.width
|
||||
height: 44
|
||||
placeholderText: editingApp?.comment || ""
|
||||
keyNavigationTab: editEnvVarsField
|
||||
keyNavigationBacktab: editIconField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Environment Variables")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "KEY=value KEY2=value2"
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editEnvVarsField
|
||||
width: parent.width
|
||||
height: 44
|
||||
placeholderText: "VAR=value"
|
||||
keyNavigationTab: editExtraFlagsField
|
||||
keyNavigationBacktab: editCommentField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Extra Arguments")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editExtraFlagsField
|
||||
width: parent.width
|
||||
height: 44
|
||||
placeholderText: "--flag --option=value"
|
||||
keyNavigationTab: editNameField
|
||||
keyNavigationBacktab: editEnvVarsField
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonsRow
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
id: resetButton
|
||||
width: 90
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: resetButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
||||
visible: SessionData.getAppOverride(editAppId) !== null
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Reset")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.error
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: resetButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: resetAppOverride()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: cancelButton
|
||||
width: 90
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: cancelButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Cancel")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cancelButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: closeEditMode()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: saveButton
|
||||
width: 90
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: saveButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.9) : Theme.primary
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Save")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primaryText
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: saveButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: saveAppOverride()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: filenameTooltipLoader
|
||||
active: false
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentTooltipLoader
|
||||
active: false
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Modals.Spotlight
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
WlrLayershell.namespace: "dms:spotlight-context-menu"
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
|
||||
property var appLauncher: null
|
||||
property var parentHandler: null
|
||||
property var parentModal: null
|
||||
property real menuPositionX: 0
|
||||
property real menuPositionY: 0
|
||||
|
||||
signal editAppRequested(var app)
|
||||
|
||||
readonly property real shadowBuffer: 5
|
||||
|
||||
screen: parentModal?.effectiveScreen
|
||||
|
||||
function show(x, y, app, fromKeyboard) {
|
||||
fromKeyboard = fromKeyboard || false;
|
||||
menuContent.currentApp = app;
|
||||
|
||||
let screenX = x;
|
||||
let screenY = y;
|
||||
|
||||
if (parentModal) {
|
||||
if (fromKeyboard) {
|
||||
screenX = x + parentModal.alignedX;
|
||||
screenY = y + parentModal.alignedY;
|
||||
} else {
|
||||
screenX = x + (parentModal.alignedX - shadowBuffer);
|
||||
screenY = y + (parentModal.alignedY - shadowBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
menuPositionX = screenX;
|
||||
menuPositionY = screenY;
|
||||
|
||||
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
|
||||
menuContent.keyboardNavigation = true;
|
||||
visible = true;
|
||||
|
||||
if (parentHandler) {
|
||||
parentHandler.enabled = false;
|
||||
}
|
||||
Qt.callLater(() => {
|
||||
menuContent.keyboardHandler.forceActiveFocus();
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (parentHandler) {
|
||||
parentHandler.enabled = true;
|
||||
}
|
||||
visible = false;
|
||||
}
|
||||
|
||||
visible: false
|
||||
color: "transparent"
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible && parentHandler) {
|
||||
parentHandler.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
SpotlightContextMenuContent {
|
||||
id: menuContent
|
||||
|
||||
x: {
|
||||
const left = 10;
|
||||
const right = root.width - width - 10;
|
||||
const want = menuPositionX;
|
||||
return Math.max(left, Math.min(right, want));
|
||||
}
|
||||
y: {
|
||||
const top = 10;
|
||||
const bottom = root.height - height - 10;
|
||||
const want = menuPositionY;
|
||||
return Math.max(top, Math.min(bottom, want));
|
||||
}
|
||||
|
||||
appLauncher: root.appLauncher
|
||||
|
||||
opacity: root.visible ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
onHideRequested: root.hide()
|
||||
onEditAppRequested: app => root.editAppRequested(app)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: root.hide()
|
||||
}
|
||||
}
|
||||
@@ -1,411 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var currentApp: null
|
||||
property var appLauncher: null
|
||||
property int selectedMenuIndex: 0
|
||||
property bool keyboardNavigation: false
|
||||
|
||||
signal hideRequested
|
||||
|
||||
readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
|
||||
|
||||
readonly property var actualItem: (currentApp && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
|
||||
|
||||
function getPluginContextMenuActions() {
|
||||
if (!currentApp || !currentApp.isPlugin || !actualItem)
|
||||
return [];
|
||||
|
||||
const pluginId = appLauncher.getPluginIdForItem(actualItem);
|
||||
if (!pluginId) {
|
||||
console.log("[ContextMenu] No pluginId found for item:", JSON.stringify(actualItem.categories));
|
||||
return [];
|
||||
}
|
||||
|
||||
const instance = PluginService.pluginInstances[pluginId];
|
||||
if (!instance) {
|
||||
console.log("[ContextMenu] No instance for pluginId:", pluginId);
|
||||
return [];
|
||||
}
|
||||
if (typeof instance.getContextMenuActions !== "function") {
|
||||
console.log("[ContextMenu] Instance has no getContextMenuActions:", pluginId);
|
||||
return [];
|
||||
}
|
||||
|
||||
const actions = instance.getContextMenuActions(actualItem);
|
||||
if (!Array.isArray(actions))
|
||||
return [];
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
function executePluginAction(actionData) {
|
||||
if (!currentApp || !actualItem)
|
||||
return;
|
||||
|
||||
const pluginId = appLauncher.getPluginIdForItem(actualItem);
|
||||
if (!pluginId)
|
||||
return;
|
||||
|
||||
const instance = PluginService.pluginInstances[pluginId];
|
||||
if (!instance)
|
||||
return;
|
||||
|
||||
if (typeof actionData === "function") {
|
||||
actionData();
|
||||
} else if (typeof instance.executeContextMenuAction === "function") {
|
||||
instance.executeContextMenuAction(actualItem, actionData);
|
||||
}
|
||||
|
||||
if (appLauncher)
|
||||
appLauncher.updateFilteredModel();
|
||||
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
readonly property bool isRegularApp: desktopEntry && !currentApp?.isPlugin && !currentApp?.isCore && !currentApp?.isAction && !currentApp?.isBuiltInLauncher
|
||||
|
||||
signal editAppRequested(var app)
|
||||
|
||||
function hideCurrentApp() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
const appId = desktopEntry.id || desktopEntry.execString || "";
|
||||
SessionData.hideApp(appId);
|
||||
if (appLauncher)
|
||||
appLauncher.updateFilteredModel();
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
function editCurrentApp() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
editAppRequested(desktopEntry);
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
readonly property var menuItems: {
|
||||
const items = [];
|
||||
|
||||
if (currentApp && currentApp.isPlugin) {
|
||||
const pluginActions = getPluginContextMenuActions();
|
||||
for (let i = 0; i < pluginActions.length; i++) {
|
||||
const act = pluginActions[i];
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: act.icon || "",
|
||||
text: act.text || act.name || "",
|
||||
action: () => executePluginAction(act.action)
|
||||
});
|
||||
}
|
||||
if (items.length === 0) {
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "content_copy",
|
||||
text: I18n.tr("Copy"),
|
||||
action: launchCurrentApp
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
const appId = desktopEntry ? (desktopEntry.id || desktopEntry.execString || "") : "";
|
||||
const isPinned = SessionData.isPinnedApp(appId);
|
||||
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: isPinned ? "keep_off" : "push_pin",
|
||||
text: isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock"),
|
||||
action: togglePin
|
||||
});
|
||||
|
||||
if (isRegularApp) {
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "visibility_off",
|
||||
text: I18n.tr("Hide App"),
|
||||
action: hideCurrentApp
|
||||
});
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "edit",
|
||||
text: I18n.tr("Edit App"),
|
||||
action: editCurrentApp
|
||||
});
|
||||
}
|
||||
|
||||
if (desktopEntry && desktopEntry.actions) {
|
||||
items.push({
|
||||
type: "separator"
|
||||
});
|
||||
for (let i = 0; i < desktopEntry.actions.length; i++) {
|
||||
const act = desktopEntry.actions[i];
|
||||
items.push({
|
||||
type: "item",
|
||||
text: act.name || "",
|
||||
action: () => launchAction(act)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
items.push({
|
||||
type: "separator",
|
||||
hidden: !desktopEntry || !desktopEntry.actions || desktopEntry.actions.length === 0
|
||||
});
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "launch",
|
||||
text: I18n.tr("Launch"),
|
||||
action: launchCurrentApp
|
||||
});
|
||||
|
||||
if (SessionService.nvidiaCommand) {
|
||||
items.push({
|
||||
type: "separator"
|
||||
});
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "memory",
|
||||
text: I18n.tr("Launch on dGPU"),
|
||||
action: launchWithNvidia
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
readonly property int visibleItemCount: {
|
||||
let count = 0;
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
if (menuItems[i].type === "item" && !menuItems[i].hidden) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (visibleItemCount > 0) {
|
||||
selectedMenuIndex = (selectedMenuIndex + 1) % visibleItemCount;
|
||||
}
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (visibleItemCount > 0) {
|
||||
selectedMenuIndex = (selectedMenuIndex - 1 + visibleItemCount) % visibleItemCount;
|
||||
}
|
||||
}
|
||||
|
||||
function togglePin() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
const appId = desktopEntry.id || desktopEntry.execString || "";
|
||||
if (SessionData.isPinnedApp(appId))
|
||||
SessionData.removePinnedApp(appId);
|
||||
else
|
||||
SessionData.addPinnedApp(appId);
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
function launchCurrentApp() {
|
||||
if (currentApp && appLauncher)
|
||||
appLauncher.launchApp(currentApp);
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
function launchWithNvidia() {
|
||||
if (desktopEntry) {
|
||||
SessionService.launchDesktopEntry(desktopEntry, true);
|
||||
if (appLauncher && currentApp) {
|
||||
appLauncher.appLaunched(currentApp);
|
||||
}
|
||||
}
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
function launchAction(action) {
|
||||
if (desktopEntry) {
|
||||
SessionService.launchDesktopAction(desktopEntry, action);
|
||||
if (appLauncher && currentApp) {
|
||||
appLauncher.appLaunched(currentApp);
|
||||
}
|
||||
}
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
function activateSelected() {
|
||||
let itemIndex = 0;
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
if (menuItems[i].type === "item" && !menuItems[i].hidden) {
|
||||
if (itemIndex === selectedMenuIndex) {
|
||||
menuItems[i].action();
|
||||
return;
|
||||
}
|
||||
itemIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property alias keyboardHandler: keyboardHandler
|
||||
|
||||
implicitWidth: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
|
||||
implicitHeight: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Rectangle {
|
||||
id: menuContainer
|
||||
anchors.fill: parent
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: -1
|
||||
}
|
||||
|
||||
Item {
|
||||
id: keyboardHandler
|
||||
anchors.fill: parent
|
||||
focus: keyboardNavigation
|
||||
|
||||
Keys.onPressed: event => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Down:
|
||||
selectNext();
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Up:
|
||||
selectPrevious();
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
activateSelected();
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Escape:
|
||||
case Qt.Key_Left:
|
||||
hideRequested();
|
||||
event.accepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Repeater {
|
||||
model: menuItems
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: modelData.type === "separator" ? 5 : 32
|
||||
visible: !modelData.hidden
|
||||
|
||||
property int itemIndex: {
|
||||
let count = 0;
|
||||
for (let i = 0; i < index; i++) {
|
||||
if (menuItems[i].type === "item" && !menuItems[i].hidden) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: modelData.type === "separator"
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: parent.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: modelData.type === "item"
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (keyboardNavigation && selectedMenuIndex === itemIndex) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
|
||||
}
|
||||
return mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: Theme.iconSize - 2
|
||||
height: Theme.iconSize - 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
visible: modelData.icon !== undefined && modelData.icon !== ""
|
||||
name: modelData.icon || ""
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.text || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
width: parent.width - (Theme.iconSize - 2) - Theme.spacingS
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
keyboardNavigation = false;
|
||||
selectedMenuIndex = itemIndex;
|
||||
}
|
||||
onClicked: modelData.action()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Modals.Spotlight
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property var appLauncher: null
|
||||
property var parentHandler: null
|
||||
property var searchField: null
|
||||
|
||||
signal editAppRequested(var app)
|
||||
|
||||
function show(x, y, app, fromKeyboard) {
|
||||
fromKeyboard = fromKeyboard || false;
|
||||
menuContent.currentApp = app;
|
||||
|
||||
root.x = x + 4;
|
||||
root.y = y + 4;
|
||||
|
||||
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
|
||||
menuContent.keyboardNavigation = true;
|
||||
|
||||
if (parentHandler) {
|
||||
parentHandler.enabled = false;
|
||||
}
|
||||
|
||||
open();
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
Qt.callLater(() => {
|
||||
menuContent.keyboardHandler.forceActiveFocus();
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (parentHandler) {
|
||||
parentHandler.enabled = true;
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
width: menuContent.implicitWidth
|
||||
height: menuContent.implicitHeight
|
||||
padding: 0
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
modal: true
|
||||
dim: false
|
||||
background: Item {}
|
||||
|
||||
onClosed: {
|
||||
if (parentHandler) {
|
||||
parentHandler.enabled = true;
|
||||
}
|
||||
if (searchField?.visible) {
|
||||
Qt.callLater(() => {
|
||||
searchField.forceActiveFocus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: SpotlightContextMenuContent {
|
||||
id: menuContent
|
||||
appLauncher: root.appLauncher
|
||||
onHideRequested: root.hide()
|
||||
onEditAppRequested: app => root.editAppRequested(app)
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
|
||||
DankModal {
|
||||
id: spotlightModal
|
||||
|
||||
layerNamespace: "dms:spotlight"
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [spotlightModal.contentWindow]
|
||||
active: spotlightModal.useHyprlandFocusGrab && spotlightModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
property bool spotlightOpen: false
|
||||
property alias spotlightContent: spotlightContentInstance
|
||||
property bool openedFromOverview: false
|
||||
property bool isClosing: false
|
||||
|
||||
function resetContent() {
|
||||
if (!spotlightContent)
|
||||
return;
|
||||
if (spotlightContent.appLauncher)
|
||||
spotlightContent.appLauncher.reset();
|
||||
if (spotlightContent.fileSearchController)
|
||||
spotlightContent.fileSearchController.reset();
|
||||
if (spotlightContent.resetScroll)
|
||||
spotlightContent.resetScroll();
|
||||
if (spotlightContent.searchField)
|
||||
spotlightContent.searchField.text = "";
|
||||
spotlightContent.searchMode = "apps";
|
||||
}
|
||||
|
||||
function show() {
|
||||
openedFromOverview = false;
|
||||
isClosing = false;
|
||||
resetContent();
|
||||
spotlightOpen = true;
|
||||
open();
|
||||
Qt.callLater(() => {
|
||||
if (spotlightContent?.appLauncher)
|
||||
spotlightContent.appLauncher.ensureInitialized();
|
||||
if (spotlightContent?.searchField)
|
||||
spotlightContent.searchField.forceActiveFocus();
|
||||
});
|
||||
}
|
||||
|
||||
function showWithQuery(query) {
|
||||
openedFromOverview = false;
|
||||
isClosing = false;
|
||||
resetContent();
|
||||
spotlightOpen = true;
|
||||
if (spotlightContent?.searchField)
|
||||
spotlightContent.searchField.text = query;
|
||||
open();
|
||||
Qt.callLater(() => {
|
||||
if (spotlightContent?.appLauncher) {
|
||||
spotlightContent.appLauncher.ensureInitialized();
|
||||
spotlightContent.appLauncher.searchQuery = query;
|
||||
}
|
||||
if (spotlightContent?.searchField)
|
||||
spotlightContent.searchField.forceActiveFocus();
|
||||
});
|
||||
}
|
||||
|
||||
function showWithEditApp(app) {
|
||||
openedFromOverview = false;
|
||||
isClosing = false;
|
||||
resetContent();
|
||||
spotlightOpen = true;
|
||||
open();
|
||||
Qt.callLater(() => {
|
||||
if (spotlightContent?.appLauncher)
|
||||
spotlightContent.appLauncher.ensureInitialized();
|
||||
if (spotlightContent?.openEditMode)
|
||||
spotlightContent.openEditMode(app);
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
openedFromOverview = false;
|
||||
isClosing = true;
|
||||
spotlightOpen = false;
|
||||
close();
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
isClosing = false;
|
||||
resetContent();
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (spotlightOpen) {
|
||||
hide();
|
||||
} else {
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
shouldBeVisible: spotlightOpen
|
||||
modalWidth: 500
|
||||
modalHeight: 600
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
cornerRadius: Theme.cornerRadius
|
||||
borderColor: Theme.outlineMedium
|
||||
borderWidth: 1
|
||||
enableShadow: true
|
||||
keepContentLoaded: true
|
||||
animationScaleCollapsed: 0.96
|
||||
animationDuration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
animationExitCurve: Theme.expressiveCurves.emphasized
|
||||
onVisibleChanged: () => {
|
||||
if (visible && !spotlightOpen) {
|
||||
show();
|
||||
}
|
||||
if (visible && spotlightContent) {
|
||||
Qt.callLater(() => {
|
||||
if (spotlightContent.searchField) {
|
||||
spotlightContent.searchField.forceActiveFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
onBackgroundClicked: () => {
|
||||
return hide();
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onCloseAllModalsExcept(excludedModal) {
|
||||
if (excludedModal !== spotlightModal && !allowStacking && spotlightOpen) {
|
||||
spotlightOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
target: ModalManager
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
spotlightModal.show();
|
||||
return "SPOTLIGHT_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
spotlightModal.hide();
|
||||
return "SPOTLIGHT_CLOSE_SUCCESS";
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
spotlightModal.toggle();
|
||||
return "SPOTLIGHT_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
function openQuery(query: string): string {
|
||||
spotlightModal.showWithQuery(query);
|
||||
return "SPOTLIGHT_OPEN_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
function toggleQuery(query: string): string {
|
||||
if (spotlightModal.spotlightOpen) {
|
||||
spotlightModal.hide();
|
||||
} else {
|
||||
spotlightModal.showWithQuery(query);
|
||||
}
|
||||
return "SPOTLIGHT_TOGGLE_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
target: "spotlight"
|
||||
}
|
||||
|
||||
SpotlightContent {
|
||||
id: spotlightContentInstance
|
||||
|
||||
parentModal: spotlightModal
|
||||
}
|
||||
|
||||
directContent: spotlightContentInstance
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: resultsContainer
|
||||
|
||||
property var appLauncher: null
|
||||
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
|
||||
function resetScroll() {
|
||||
resultsList.contentY = 0;
|
||||
if (gridLoader.item) {
|
||||
gridLoader.item.contentY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedItemPosition() {
|
||||
if (!appLauncher)
|
||||
return {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
|
||||
const selectedIndex = appLauncher.selectedIndex;
|
||||
if (appLauncher.viewMode === "list") {
|
||||
const itemY = selectedIndex * (resultsList.itemHeight + resultsList.itemSpacing) - resultsList.contentY;
|
||||
return {
|
||||
x: resultsList.width / 2,
|
||||
y: itemY + resultsList.itemHeight / 2
|
||||
};
|
||||
} else if (gridLoader.item) {
|
||||
const grid = gridLoader.item;
|
||||
const row = Math.floor(selectedIndex / grid.actualColumns);
|
||||
const col = selectedIndex % grid.actualColumns;
|
||||
const itemX = col * grid.cellWidth + grid.leftMargin + grid.cellWidth / 2;
|
||||
const itemY = row * grid.cellHeight - grid.contentY + grid.cellHeight / 2;
|
||||
return {
|
||||
x: itemX,
|
||||
y: itemY
|
||||
};
|
||||
}
|
||||
return {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
}
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 32
|
||||
z: 100
|
||||
visible: {
|
||||
if (!appLauncher)
|
||||
return false;
|
||||
const view = appLauncher.viewMode === "list" ? resultsList : (gridLoader.item || resultsList);
|
||||
const isLastItem = appLauncher.viewMode === "list" ? view.currentIndex >= view.count - 1 : (gridLoader.item ? Math.floor(view.currentIndex / view.actualColumns) >= Math.floor((view.count - 1) / view.actualColumns) : false);
|
||||
const hasOverflow = view.contentHeight > view.height;
|
||||
const atBottom = view.contentY >= view.contentHeight - view.height - 1;
|
||||
return hasOverflow && (!isLastItem || !atBottom);
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: resultsList
|
||||
|
||||
property int itemHeight: 60
|
||||
property int iconSize: 40
|
||||
property bool showDescription: true
|
||||
property int itemSpacing: Theme.spacingS
|
||||
property bool hoverUpdatesSelection: false
|
||||
property bool keyboardNavigationActive: appLauncher ? appLauncher.keyboardNavigationActive : false
|
||||
|
||||
signal keyboardNavigationReset
|
||||
signal itemClicked(int index, var modelData)
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count)
|
||||
return;
|
||||
const itemY = index * (itemHeight + itemSpacing);
|
||||
const itemBottom = itemY + itemHeight;
|
||||
const fadeHeight = 32;
|
||||
const isLastItem = index === count - 1;
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
else if (itemBottom > contentY + height - (isLastItem ? 0 : fadeHeight))
|
||||
contentY = Math.min(itemBottom - height + (isLastItem ? 0 : fadeHeight), contentHeight - height);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.bottomMargin: 1
|
||||
visible: appLauncher && appLauncher.viewMode === "list"
|
||||
model: appLauncher ? appLauncher.model : null
|
||||
currentIndex: appLauncher ? appLauncher.selectedIndex : -1
|
||||
clip: true
|
||||
spacing: itemSpacing
|
||||
focus: true
|
||||
interactive: true
|
||||
cacheBuffer: Math.max(0, Math.min(height * 2, 1000))
|
||||
reuseItems: true
|
||||
onCurrentIndexChanged: {
|
||||
if (keyboardNavigationActive)
|
||||
ensureVisible(currentIndex);
|
||||
}
|
||||
onItemClicked: (index, modelData) => {
|
||||
if (appLauncher)
|
||||
appLauncher.launchApp(modelData);
|
||||
}
|
||||
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
||||
resultsContainer.itemRightClicked(index, modelData, mouseX, mouseY);
|
||||
}
|
||||
onKeyboardNavigationReset: () => {
|
||||
if (appLauncher)
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
|
||||
delegate: AppLauncherListDelegate {
|
||||
listView: resultsList
|
||||
itemHeight: resultsList.itemHeight
|
||||
iconSize: resultsList.iconSize
|
||||
showDescription: resultsList.showDescription
|
||||
hoverUpdatesSelection: resultsList.hoverUpdatesSelection
|
||||
keyboardNavigationActive: resultsList.keyboardNavigationActive
|
||||
isCurrentItem: ListView.isCurrentItem
|
||||
iconMaterialSizeAdjustment: 0
|
||||
iconUnicodeScale: 0.8
|
||||
onItemClicked: (idx, modelData) => resultsList.itemClicked(idx, modelData)
|
||||
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
|
||||
resultsList.itemRightClicked(idx, modelData, mouseX, mouseY);
|
||||
}
|
||||
onKeyboardNavigationReset: resultsList.keyboardNavigationReset
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: gridLoader
|
||||
|
||||
property real _lastWidth: 0
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.bottomMargin: 1
|
||||
visible: appLauncher && appLauncher.viewMode === "grid"
|
||||
active: appLauncher && appLauncher.viewMode === "grid"
|
||||
asynchronous: false
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
item.appLauncher = Qt.binding(() => resultsContainer.appLauncher);
|
||||
}
|
||||
}
|
||||
|
||||
onWidthChanged: {
|
||||
if (visible && Math.abs(width - _lastWidth) > 1) {
|
||||
_lastWidth = width;
|
||||
active = false;
|
||||
Qt.callLater(() => {
|
||||
active = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
sourceComponent: Component {
|
||||
DankGridView {
|
||||
id: resultsGrid
|
||||
|
||||
property var appLauncher: null
|
||||
|
||||
property int currentIndex: appLauncher ? appLauncher.selectedIndex : -1
|
||||
property int columns: appLauncher ? appLauncher.gridColumns : 4
|
||||
property bool adaptiveColumns: false
|
||||
property int minCellWidth: 120
|
||||
property int maxCellWidth: 160
|
||||
property real iconSizeRatio: 0.55
|
||||
property int maxIconSize: 48
|
||||
property int minIconSize: 32
|
||||
property bool hoverUpdatesSelection: false
|
||||
property bool keyboardNavigationActive: appLauncher ? appLauncher.keyboardNavigationActive : false
|
||||
property real baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : width / columns
|
||||
property real baseCellHeight: baseCellWidth + 20
|
||||
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
|
||||
property int remainingSpace: width - (actualColumns * cellWidth)
|
||||
|
||||
signal keyboardNavigationReset
|
||||
signal itemClicked(int index, var modelData)
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count)
|
||||
return;
|
||||
const itemY = Math.floor(index / actualColumns) * cellHeight;
|
||||
const itemBottom = itemY + cellHeight;
|
||||
const fadeHeight = 32;
|
||||
const isLastRow = Math.floor(index / actualColumns) >= Math.floor((count - 1) / actualColumns);
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
else if (itemBottom > contentY + height - (isLastRow ? 0 : fadeHeight))
|
||||
contentY = Math.min(itemBottom - height + (isLastRow ? 0 : fadeHeight), contentHeight - height);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
model: appLauncher ? appLauncher.model : null
|
||||
clip: true
|
||||
cellWidth: baseCellWidth
|
||||
cellHeight: baseCellHeight
|
||||
focus: true
|
||||
interactive: true
|
||||
cacheBuffer: Math.max(0, Math.min(height * 2, 1000))
|
||||
reuseItems: true
|
||||
onCurrentIndexChanged: {
|
||||
if (keyboardNavigationActive)
|
||||
ensureVisible(currentIndex);
|
||||
}
|
||||
onItemClicked: (index, modelData) => {
|
||||
if (appLauncher)
|
||||
appLauncher.launchApp(modelData);
|
||||
}
|
||||
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
||||
resultsContainer.itemRightClicked(index, modelData, mouseX, mouseY);
|
||||
}
|
||||
onKeyboardNavigationReset: () => {
|
||||
if (appLauncher)
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
|
||||
delegate: AppLauncherGridDelegate {
|
||||
gridView: resultsGrid
|
||||
cellWidth: resultsGrid.cellWidth
|
||||
cellHeight: resultsGrid.cellHeight
|
||||
minIconSize: resultsGrid.minIconSize
|
||||
maxIconSize: resultsGrid.maxIconSize
|
||||
iconSizeRatio: resultsGrid.iconSizeRatio
|
||||
hoverUpdatesSelection: resultsGrid.hoverUpdatesSelection
|
||||
keyboardNavigationActive: resultsGrid.keyboardNavigationActive
|
||||
currentIndex: resultsGrid.currentIndex
|
||||
onItemClicked: (idx, modelData) => resultsGrid.itemClicked(idx, modelData)
|
||||
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
|
||||
resultsGrid.itemRightClicked(idx, modelData, mouseX, mouseY);
|
||||
}
|
||||
onKeyboardNavigationReset: resultsGrid.keyboardNavigationReset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,456 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// DEVELOPER NOTE: This component manages the AppDrawer launcher (accessed via DankBar icon).
|
||||
// Changes to launcher behavior, especially item rendering, filtering, or model structure,
|
||||
// likely require corresponding updates in Modals/Spotlight/SpotlightResults.qml and vice versa.
|
||||
|
||||
property string searchQuery: ""
|
||||
property string selectedCategory: I18n.tr("All")
|
||||
property string viewMode: "list" // "list" or "grid"
|
||||
property int selectedIndex: 0
|
||||
property int maxResults: 50
|
||||
property int gridColumns: 4
|
||||
property bool debounceSearch: true
|
||||
property int debounceInterval: 50
|
||||
property bool keyboardNavigationActive: false
|
||||
property bool suppressUpdatesWhileLaunching: false
|
||||
property var categories: []
|
||||
readonly property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category))
|
||||
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
|
||||
property alias model: filteredModel
|
||||
property var _uniqueApps: []
|
||||
property bool _initialized: false
|
||||
property bool _isTriggered: false
|
||||
property string _triggeredCategory: ""
|
||||
property bool _updatingFromTrigger: false
|
||||
|
||||
signal appLaunched(var app)
|
||||
signal categorySelected(string category)
|
||||
signal viewModeSelected(string mode)
|
||||
|
||||
function ensureInitialized() {
|
||||
if (_initialized)
|
||||
return;
|
||||
_initialized = true;
|
||||
updateCategories();
|
||||
updateFilteredModel();
|
||||
}
|
||||
|
||||
function updateCategories() {
|
||||
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science");
|
||||
const result = [I18n.tr("All")];
|
||||
categories = result.concat(allCategories.filter(cat => cat !== I18n.tr("All")));
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PluginService
|
||||
function onPluginLoaded() {
|
||||
updateCategories();
|
||||
}
|
||||
function onPluginUnloaded() {
|
||||
updateCategories();
|
||||
}
|
||||
function onPluginListUpdated() {
|
||||
updateCategories();
|
||||
}
|
||||
function onRequestLauncherUpdate(pluginId) {
|
||||
// Only update if we are actually looking at this plugin or in All category
|
||||
updateFilteredModel();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onSortAppsAlphabeticallyChanged() {
|
||||
updateFilteredModel();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onHiddenAppsChanged() {
|
||||
updateFilteredModel();
|
||||
}
|
||||
function onAppOverridesChanged() {
|
||||
updateFilteredModel();
|
||||
}
|
||||
}
|
||||
|
||||
function updateFilteredModel() {
|
||||
if (suppressUpdatesWhileLaunching) {
|
||||
suppressUpdatesWhileLaunching = false;
|
||||
return;
|
||||
}
|
||||
filteredModel.clear();
|
||||
selectedIndex = 0;
|
||||
keyboardNavigationActive = false;
|
||||
|
||||
const triggerResult = checkPluginTriggers(searchQuery);
|
||||
if (triggerResult.triggered) {
|
||||
console.log("AppLauncher: Plugin trigger detected:", triggerResult.trigger, "for plugin:", triggerResult.pluginId);
|
||||
}
|
||||
|
||||
let apps = [];
|
||||
const allCategory = I18n.tr("All");
|
||||
const emptyTriggerPlugins = typeof PluginService !== "undefined" ? PluginService.getPluginsWithEmptyTrigger() : [];
|
||||
|
||||
if (triggerResult.triggered) {
|
||||
_isTriggered = true;
|
||||
_triggeredCategory = triggerResult.pluginCategory;
|
||||
_updatingFromTrigger = true;
|
||||
selectedCategory = triggerResult.pluginCategory;
|
||||
_updatingFromTrigger = false;
|
||||
if (triggerResult.isBuiltIn) {
|
||||
apps = AppSearchService.getBuiltInLauncherItems(triggerResult.pluginId, triggerResult.query);
|
||||
} else {
|
||||
apps = AppSearchService.getPluginItems(triggerResult.pluginCategory, triggerResult.query);
|
||||
}
|
||||
} else {
|
||||
if (_isTriggered) {
|
||||
_updatingFromTrigger = true;
|
||||
selectedCategory = allCategory;
|
||||
_updatingFromTrigger = false;
|
||||
_isTriggered = false;
|
||||
_triggeredCategory = "";
|
||||
}
|
||||
if (searchQuery.length === 0) {
|
||||
if (selectedCategory === allCategory) {
|
||||
let emptyTriggerItems = [];
|
||||
emptyTriggerPlugins.forEach(pluginId => {
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId);
|
||||
const pluginCategory = plugin.name || pluginId;
|
||||
const items = AppSearchService.getPluginItems(pluginCategory, "");
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items);
|
||||
});
|
||||
const builtInEmptyTrigger = AppSearchService.getBuiltInLauncherPluginsWithEmptyTrigger();
|
||||
builtInEmptyTrigger.forEach(pluginId => {
|
||||
const items = AppSearchService.getBuiltInLauncherItems(pluginId, "");
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items);
|
||||
});
|
||||
const coreItems = AppSearchService.getCoreApps("");
|
||||
apps = AppSearchService.getVisibleApplications().concat(emptyTriggerItems).concat(coreItems);
|
||||
} else {
|
||||
apps = AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults);
|
||||
const coreItems = AppSearchService.getCoreApps("").filter(app => app.categories.includes(selectedCategory));
|
||||
apps = apps.concat(coreItems);
|
||||
}
|
||||
} else {
|
||||
if (selectedCategory === allCategory) {
|
||||
apps = AppSearchService.searchApplications(searchQuery);
|
||||
|
||||
let emptyTriggerItems = [];
|
||||
emptyTriggerPlugins.forEach(pluginId => {
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId);
|
||||
const pluginCategory = plugin.name || pluginId;
|
||||
const items = AppSearchService.getPluginItems(pluginCategory, searchQuery);
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items);
|
||||
});
|
||||
const builtInEmptyTrigger = AppSearchService.getBuiltInLauncherPluginsWithEmptyTrigger();
|
||||
builtInEmptyTrigger.forEach(pluginId => {
|
||||
const items = AppSearchService.getBuiltInLauncherItems(pluginId, searchQuery);
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items);
|
||||
});
|
||||
|
||||
const coreItems = AppSearchService.getCoreApps(searchQuery);
|
||||
apps = apps.concat(emptyTriggerItems).concat(coreItems);
|
||||
} else {
|
||||
const categoryApps = AppSearchService.getAppsInCategory(selectedCategory);
|
||||
if (categoryApps.length > 0) {
|
||||
const allSearchResults = AppSearchService.searchApplications(searchQuery);
|
||||
const categoryNames = new Set(categoryApps.map(app => app.name));
|
||||
apps = allSearchResults.filter(searchApp => categoryNames.has(searchApp.name)).slice(0, maxResults);
|
||||
} else {
|
||||
apps = [];
|
||||
}
|
||||
|
||||
const coreItems = AppSearchService.getCoreApps(searchQuery).filter(app => app.categories.includes(selectedCategory));
|
||||
apps = apps.concat(coreItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (searchQuery.length === 0) {
|
||||
if (SettingsData.sortAppsAlphabetically) {
|
||||
apps = apps.sort((a, b) => {
|
||||
return (a.name || "").localeCompare(b.name || "");
|
||||
});
|
||||
} else {
|
||||
apps = apps.sort((a, b) => {
|
||||
const aId = a.id || a.execString || a.exec || "";
|
||||
const bId = b.id || b.execString || b.exec || "";
|
||||
const aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0;
|
||||
const bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0;
|
||||
if (aUsage !== bUsage) {
|
||||
return bUsage - aUsage;
|
||||
}
|
||||
return (a.name || "").localeCompare(b.name || "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const seenNames = new Set();
|
||||
const uniqueApps = [];
|
||||
apps.forEach(app => {
|
||||
if (app) {
|
||||
const itemKey = app.name + "|" + (app.execString || app.exec || app.action || "");
|
||||
if (seenNames.has(itemKey)) {
|
||||
return;
|
||||
}
|
||||
seenNames.add(itemKey);
|
||||
uniqueApps.push(app);
|
||||
|
||||
const isPluginItem = app.isCore ? false : (app.action !== undefined);
|
||||
filteredModel.append({
|
||||
"name": app.name || "",
|
||||
"exec": app.execString || app.exec || app.action || "",
|
||||
"icon": app.icon !== undefined ? String(app.icon) : (isPluginItem ? "" : "application-x-executable"),
|
||||
"comment": app.comment || "",
|
||||
"categories": app.categories || [],
|
||||
"isPlugin": isPluginItem,
|
||||
"isCore": app.isCore === true,
|
||||
"isBuiltInLauncher": app.isBuiltInLauncher === true,
|
||||
"isAction": app.isAction === true,
|
||||
"appIndex": uniqueApps.length - 1,
|
||||
"pinned": app._pinned === true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
root._uniqueApps = uniqueApps;
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (filteredModel.count === 0) {
|
||||
return;
|
||||
}
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = viewMode === "grid" ? Math.min(selectedIndex + gridColumns, filteredModel.count - 1) : Math.min(selectedIndex + 1, filteredModel.count - 1);
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (filteredModel.count === 0) {
|
||||
return;
|
||||
}
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = viewMode === "grid" ? Math.max(selectedIndex - gridColumns, 0) : Math.max(selectedIndex - 1, 0);
|
||||
}
|
||||
|
||||
function selectNextInRow() {
|
||||
if (filteredModel.count === 0 || viewMode !== "grid") {
|
||||
return;
|
||||
}
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1);
|
||||
}
|
||||
|
||||
function selectPreviousInRow() {
|
||||
if (filteredModel.count === 0 || viewMode !== "grid") {
|
||||
return;
|
||||
}
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0);
|
||||
}
|
||||
|
||||
function launchSelected() {
|
||||
if (filteredModel.count === 0 || selectedIndex < 0 || selectedIndex >= filteredModel.count) {
|
||||
return;
|
||||
}
|
||||
const selectedApp = filteredModel.get(selectedIndex);
|
||||
launchApp(selectedApp);
|
||||
}
|
||||
|
||||
function launchApp(appData) {
|
||||
if (!appData || typeof appData.appIndex === "undefined" || appData.appIndex < 0 || appData.appIndex >= _uniqueApps.length)
|
||||
return;
|
||||
suppressUpdatesWhileLaunching = true;
|
||||
|
||||
const actualApp = _uniqueApps[appData.appIndex];
|
||||
|
||||
if (appData.isBuiltInLauncher) {
|
||||
AppSearchService.executeBuiltInLauncherItem(actualApp);
|
||||
appLaunched(appData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (appData.isCore) {
|
||||
AppSearchService.executeCoreApp(actualApp);
|
||||
appLaunched(appData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (appData.isPlugin) {
|
||||
const pluginId = getPluginIdForItem(actualApp);
|
||||
if (pluginId) {
|
||||
AppSearchService.executePluginItem(actualApp, pluginId);
|
||||
appLaunched(appData);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (appData.isAction && actualApp.parentApp && actualApp.actionData) {
|
||||
SessionService.launchDesktopAction(actualApp.parentApp, actualApp.actionData);
|
||||
appLaunched(appData);
|
||||
AppUsageHistoryData.addAppUsage(actualApp.parentApp);
|
||||
return;
|
||||
}
|
||||
|
||||
SessionService.launchDesktopEntry(actualApp);
|
||||
appLaunched(appData);
|
||||
AppUsageHistoryData.addAppUsage(actualApp);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
suppressUpdatesWhileLaunching = false;
|
||||
searchQuery = "";
|
||||
selectedIndex = 0;
|
||||
setCategory(I18n.tr("All"));
|
||||
updateFilteredModel();
|
||||
}
|
||||
|
||||
function setCategory(category) {
|
||||
selectedCategory = category;
|
||||
categorySelected(category);
|
||||
}
|
||||
|
||||
function setViewMode(mode) {
|
||||
viewMode = mode;
|
||||
viewModeSelected(mode);
|
||||
}
|
||||
|
||||
onSearchQueryChanged: {
|
||||
if (!_initialized)
|
||||
return;
|
||||
if (debounceSearch) {
|
||||
searchDebounceTimer.restart();
|
||||
} else {
|
||||
updateFilteredModel();
|
||||
}
|
||||
}
|
||||
|
||||
onSelectedCategoryChanged: {
|
||||
if (_updatingFromTrigger || !_initialized)
|
||||
return;
|
||||
updateFilteredModel();
|
||||
}
|
||||
|
||||
onAppUsageRankingChanged: {
|
||||
if (_initialized)
|
||||
updateFilteredModel();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DesktopEntries
|
||||
function onApplicationsChanged() {
|
||||
if (!root._initialized)
|
||||
return;
|
||||
root.updateCategories();
|
||||
root.updateFilteredModel();
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: filteredModel
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: searchDebounceTimer
|
||||
|
||||
interval: root.debounceInterval
|
||||
repeat: false
|
||||
onTriggered: updateFilteredModel()
|
||||
}
|
||||
|
||||
function checkPluginTriggers(query) {
|
||||
if (!query)
|
||||
return {
|
||||
triggered: false,
|
||||
pluginCategory: "",
|
||||
query: ""
|
||||
};
|
||||
|
||||
const builtInTriggers = AppSearchService.getBuiltInLauncherTriggers();
|
||||
for (const trigger in builtInTriggers) {
|
||||
if (!query.startsWith(trigger))
|
||||
continue;
|
||||
const pluginId = builtInTriggers[trigger];
|
||||
const plugin = AppSearchService.builtInPlugins[pluginId];
|
||||
if (!plugin)
|
||||
continue;
|
||||
return {
|
||||
triggered: true,
|
||||
pluginId: pluginId,
|
||||
pluginCategory: plugin.name,
|
||||
query: query.substring(trigger.length).trim(),
|
||||
trigger: trigger,
|
||||
isBuiltIn: true
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof PluginService === "undefined")
|
||||
return {
|
||||
triggered: false,
|
||||
pluginCategory: "",
|
||||
query: ""
|
||||
};
|
||||
|
||||
const triggers = PluginService.getAllPluginTriggers();
|
||||
for (const trigger in triggers) {
|
||||
if (!query.startsWith(trigger))
|
||||
continue;
|
||||
const pluginId = triggers[trigger];
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId);
|
||||
if (!plugin)
|
||||
continue;
|
||||
return {
|
||||
triggered: true,
|
||||
pluginId: pluginId,
|
||||
pluginCategory: plugin.name || pluginId,
|
||||
query: query.substring(trigger.length).trim(),
|
||||
trigger: trigger,
|
||||
isBuiltIn: false
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
triggered: false,
|
||||
pluginCategory: "",
|
||||
query: ""
|
||||
};
|
||||
}
|
||||
|
||||
function getPluginIdForItem(item) {
|
||||
if (!item || !item.categories || typeof PluginService === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const launchers = PluginService.getLauncherPlugins();
|
||||
for (const pluginId in launchers) {
|
||||
const plugin = launchers[pluginId];
|
||||
const pluginCategory = plugin.name || pluginId;
|
||||
|
||||
let hasCategory = false;
|
||||
if (Array.isArray(item.categories)) {
|
||||
hasCategory = item.categories.includes(pluginCategory);
|
||||
} else if (item.categories && typeof item.categories.count !== "undefined") {
|
||||
for (let i = 0; i < item.categories.count; i++) {
|
||||
if (item.categories.get(i) === pluginCategory) {
|
||||
hasCategory = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCategory) {
|
||||
return pluginId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var categories: []
|
||||
property string selectedCategory: I18n.tr("All")
|
||||
property bool compact: false
|
||||
|
||||
signal categorySelected(string category)
|
||||
|
||||
readonly property int maxCompactItems: 8
|
||||
readonly property int itemHeight: 36
|
||||
readonly property color selectedBorderColor: "transparent"
|
||||
readonly property color unselectedBorderColor: "transparent"
|
||||
|
||||
function handleCategoryClick(category) {
|
||||
categorySelected(category)
|
||||
}
|
||||
|
||||
function getButtonWidth(itemCount, containerWidth) {
|
||||
return itemCount > 0 ? (containerWidth - (itemCount - 1) * Theme.spacingS) / itemCount : 0
|
||||
}
|
||||
|
||||
height: compact ? itemHeight : (itemHeight * 2 + Theme.spacingS)
|
||||
|
||||
Row {
|
||||
visible: compact
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: categories ? categories.slice(0, Math.min(categories.length || 0, maxCompactItems)) : []
|
||||
|
||||
Rectangle {
|
||||
property int itemCount: Math.min(categories ? categories.length || 0 : 0, maxCompactItems)
|
||||
|
||||
height: root.itemHeight
|
||||
width: root.getButtonWidth(itemCount, parent.width)
|
||||
radius: Theme.cornerRadius
|
||||
color: selectedCategory === modelData ? Theme.primary : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.handleCategoryClick(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
visible: !compact
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: categories ? categories.slice(0, Math.min(4, categories.length || 0)) : []
|
||||
|
||||
Rectangle {
|
||||
property int itemCount: Math.min(4, categories ? categories.length || 0 : 0)
|
||||
|
||||
height: root.itemHeight
|
||||
width: root.getButtonWidth(itemCount, parent.width)
|
||||
radius: Theme.cornerRadius
|
||||
color: selectedCategory === modelData ? Theme.primary : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.handleCategoryClick(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: categories && categories.length > 4
|
||||
|
||||
Repeater {
|
||||
model: categories && categories.length > 4 ? categories.slice(4) : []
|
||||
|
||||
Rectangle {
|
||||
property int itemCount: categories && categories.length > 4 ? categories.length - 4 : 0
|
||||
|
||||
height: root.itemHeight
|
||||
width: root.getButtonWidth(itemCount, parent.width)
|
||||
radius: Theme.cornerRadius
|
||||
color: selectedCategory === modelData ? Theme.primary : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.handleCategoryClick(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,11 +46,24 @@ Item {
|
||||
|
||||
function getRealWorkspaces() {
|
||||
if (CompositorService.isNiri) {
|
||||
const fallbackWorkspaces = [
|
||||
{
|
||||
"id": 1,
|
||||
"idx": 0,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"idx": 1,
|
||||
"name": ""
|
||||
}
|
||||
];
|
||||
if (!barWindow.screenName || SettingsData.workspaceFollowFocus) {
|
||||
return NiriService.getCurrentOutputWorkspaceNumbers();
|
||||
const currentWorkspaces = NiriService.getCurrentOutputWorkspaces();
|
||||
return currentWorkspaces.length > 0 ? currentWorkspaces : fallbackWorkspaces;
|
||||
}
|
||||
const workspaces = NiriService.allWorkspaces.filter(ws => ws.output === barWindow.screenName).map(ws => ws.idx + 1);
|
||||
return workspaces.length > 0 ? workspaces : [1, 2];
|
||||
const workspaces = NiriService.allWorkspaces.filter(ws => ws.output === barWindow.screenName);
|
||||
return workspaces.length > 0 ? workspaces : fallbackWorkspaces;
|
||||
} else if (CompositorService.isHyprland) {
|
||||
const workspaces = Hyprland.workspaces?.values || [];
|
||||
|
||||
@@ -118,7 +131,7 @@ Item {
|
||||
return NiriService.getCurrentWorkspaceNumber();
|
||||
}
|
||||
const activeWs = NiriService.allWorkspaces.find(ws => ws.output === barWindow.screenName && ws.is_active);
|
||||
return activeWs ? activeWs.idx + 1 : 1;
|
||||
return activeWs ? activeWs.idx : 1;
|
||||
} else if (CompositorService.isHyprland) {
|
||||
const monitors = Hyprland.monitors?.values || [];
|
||||
const currentMonitor = monitors.find(monitor => monitor.name === barWindow.screenName);
|
||||
@@ -151,12 +164,16 @@ Item {
|
||||
|
||||
if (CompositorService.isNiri) {
|
||||
const currentWs = getCurrentWorkspace();
|
||||
const currentIndex = realWorkspaces.findIndex(ws => ws === currentWs);
|
||||
const currentIndex = realWorkspaces.findIndex(ws => ws && ws.idx === currentWs);
|
||||
const validIndex = currentIndex === -1 ? 0 : currentIndex;
|
||||
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
|
||||
|
||||
if (nextIndex !== validIndex) {
|
||||
NiriService.switchToWorkspace(realWorkspaces[nextIndex] - 1);
|
||||
const nextWorkspace = realWorkspaces[nextIndex];
|
||||
if (!nextWorkspace || nextWorkspace.idx === undefined) {
|
||||
return;
|
||||
}
|
||||
NiriService.switchToWorkspace(nextWorkspace.idx);
|
||||
}
|
||||
} else if (CompositorService.isHyprland) {
|
||||
const currentWs = getCurrentWorkspace();
|
||||
|
||||
@@ -42,7 +42,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: BatteryService.getBatteryIcon()
|
||||
size: Theme.barIconSize(battery.barThickness)
|
||||
size: Theme.barIconSize(battery.barThickness, undefined, battery.barConfig?.noBackground)
|
||||
color: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
return Theme.widgetIconColor;
|
||||
@@ -78,7 +78,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: BatteryService.getBatteryIcon()
|
||||
size: Theme.barIconSize(battery.barThickness, -4)
|
||||
size: Theme.barIconSize(battery.barThickness, -4, battery.barConfig?.noBackground)
|
||||
color: {
|
||||
if (!BatteryService.batteryAvailable) {
|
||||
return Theme.widgetIconColor;
|
||||
|
||||
@@ -45,7 +45,7 @@ BasePill {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "shift_lock"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ BasePill {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "content_paste"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: Theme.widgetIconColor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ BasePill {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "palette"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: root.isActive ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ BasePill {
|
||||
property real micAccumulator: 0
|
||||
property real volumeAccumulator: 0
|
||||
property real brightnessAccumulator: 0
|
||||
readonly property real vIconSize: Theme.barIconSize(root.barThickness, -4)
|
||||
readonly property real vIconSize: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
|
||||
Loader {
|
||||
active: root.showPrinterIcon
|
||||
@@ -459,7 +459,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: "screen_record"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showScreenSharingIcon && NiriService.hasCasts
|
||||
@@ -468,7 +468,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: networkIcon
|
||||
name: root.getNetworkIconName()
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: root.getNetworkIconColor()
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||
@@ -477,7 +477,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: vpnIcon
|
||||
name: "vpn_lock"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
||||
@@ -486,7 +486,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: bluetoothIcon
|
||||
name: "bluetooth"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: BluetoothService.connected ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||
@@ -502,7 +502,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: audioIcon
|
||||
name: root.getVolumeIconName()
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: Theme.widgetIconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
@@ -544,7 +544,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: micIcon
|
||||
name: root.getMicIconName()
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: root.getMicIconColor()
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
@@ -586,7 +586,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: brightnessIcon
|
||||
name: root.getBrightnessIconName()
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: Theme.widgetIconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
@@ -618,7 +618,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: batteryIcon
|
||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: root.getBatteryIconColor()
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
||||
@@ -627,7 +627,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: printerIcon
|
||||
name: "print"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
||||
@@ -635,7 +635,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: "settings"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.hasNoVisibleIcons()
|
||||
|
||||
@@ -36,7 +36,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (DgopService.cpuUsage > 80) {
|
||||
return Theme.tempDanger;
|
||||
@@ -74,7 +74,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: cpuIcon
|
||||
name: "memory"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (DgopService.cpuUsage > 80) {
|
||||
return Theme.tempDanger;
|
||||
|
||||
@@ -36,7 +36,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: "device_thermostat"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (DgopService.cpuTemperature > 85) {
|
||||
return Theme.tempDanger;
|
||||
@@ -74,7 +74,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: cpuTempIcon
|
||||
name: "device_thermostat"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (DgopService.cpuTemperature > 85) {
|
||||
return Theme.tempDanger;
|
||||
|
||||
@@ -57,7 +57,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: layout.getLayoutIcon(layout.currentLayoutSymbol)
|
||||
size: Theme.barIconSize(layout.barThickness)
|
||||
size: Theme.barIconSize(layout.barThickness, undefined, layout.barConfig?.noBackground)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
@@ -78,7 +78,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: layout.getLayoutIcon(layout.currentLayoutSymbol)
|
||||
size: Theme.barIconSize(layout.barThickness, -4)
|
||||
size: Theme.barIconSize(layout.barThickness, -4, layout.barConfig?.noBackground)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: "storage"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (root.diskUsagePercent > 90) {
|
||||
return Theme.tempDanger;
|
||||
@@ -146,7 +146,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: "storage"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (root.diskUsagePercent > 90) {
|
||||
return Theme.tempDanger;
|
||||
|
||||
@@ -104,7 +104,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: "auto_awesome_mosaic"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (root.displayTemp > 80) {
|
||||
return Theme.tempDanger;
|
||||
@@ -142,7 +142,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: gpuTempIcon
|
||||
name: "auto_awesome_mosaic"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (root.displayTemp > 80) {
|
||||
return Theme.tempDanger;
|
||||
|
||||
@@ -17,7 +17,7 @@ BasePill {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: Theme.widgetTextColor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: "keyboard"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
@@ -21,15 +21,15 @@ BasePill {
|
||||
visible: SettingsData.launcherLogoMode === "apps"
|
||||
anchors.centerIn: parent
|
||||
name: "apps"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: Theme.widgetIconColor
|
||||
}
|
||||
|
||||
SystemLogo {
|
||||
visible: SettingsData.launcherLogoMode === "os"
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.noBackground)
|
||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.noBackground)
|
||||
colorOverride: Theme.effectiveLogoColor
|
||||
brightnessOverride: SettingsData.launcherLogoBrightness
|
||||
contrastOverride: SettingsData.launcherLogoContrast
|
||||
@@ -38,8 +38,8 @@ BasePill {
|
||||
IconImage {
|
||||
visible: SettingsData.launcherLogoMode === "dank"
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.noBackground)
|
||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.noBackground)
|
||||
smooth: true
|
||||
mipmap: true
|
||||
asynchronous: true
|
||||
@@ -57,8 +57,8 @@ BasePill {
|
||||
IconImage {
|
||||
visible: SettingsData.launcherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isLabwc)
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.noBackground)
|
||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.noBackground)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
source: {
|
||||
@@ -94,8 +94,8 @@ BasePill {
|
||||
IconImage {
|
||||
visible: SettingsData.launcherLogoMode === "custom" && SettingsData.launcherLogoCustomPath !== ""
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset)
|
||||
width: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.noBackground)
|
||||
height: Theme.barIconSize(root.barThickness, SettingsData.launcherLogoSizeOffset, root.barConfig?.noBackground)
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
source: SettingsData.launcherLogoCustomPath ? "file://" + SettingsData.launcherLogoCustomPath.replace("file://", "") : ""
|
||||
|
||||
@@ -41,7 +41,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: "network_check"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
@@ -79,7 +79,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: "network_check"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ BasePill {
|
||||
|
||||
anchors.centerIn: parent
|
||||
name: "assignment"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: root.isActive ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ BasePill {
|
||||
id: notifIcon
|
||||
anchors.centerIn: parent
|
||||
name: SessionData.doNotDisturb ? "notifications_off" : "notifications"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: SessionData.doNotDisturb ? Theme.primary : (root.isActive ? Theme.primary : Theme.widgetIconColor)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ BasePill {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "power_settings_new"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: Theme.widgetIconColor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: "developer_board"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (DgopService.memoryUsage > 90) {
|
||||
return Theme.tempDanger;
|
||||
@@ -84,7 +84,7 @@ BasePill {
|
||||
DankIcon {
|
||||
id: ramIcon
|
||||
name: "developer_board"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (DgopService.memoryUsage > 90) {
|
||||
return Theme.tempDanger;
|
||||
|
||||
@@ -366,10 +366,10 @@ Item {
|
||||
IconImage {
|
||||
id: iconImg
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
|
||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)) / 2) : Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Theme.barIconSize(root.barThickness)
|
||||
height: Theme.barIconSize(root.barThickness)
|
||||
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
source: {
|
||||
root._desktopEntriesUpdateTrigger;
|
||||
root._appIdSubstitutionsTrigger;
|
||||
@@ -395,9 +395,9 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
|
||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)) / 2) : Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
name: "sports_esports"
|
||||
color: Theme.widgetTextColor
|
||||
visible: !iconImg.visible && Paths.isSteamApp(appId)
|
||||
@@ -611,10 +611,10 @@ Item {
|
||||
IconImage {
|
||||
id: iconImg
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
|
||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)) / 2) : Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Theme.barIconSize(root.barThickness)
|
||||
height: Theme.barIconSize(root.barThickness)
|
||||
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
source: {
|
||||
root._desktopEntriesUpdateTrigger;
|
||||
root._appIdSubstitutionsTrigger;
|
||||
@@ -640,9 +640,9 @@ Item {
|
||||
|
||||
DankIcon {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
|
||||
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)) / 2) : Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
name: "sports_esports"
|
||||
color: Theme.widgetTextColor
|
||||
visible: !iconImg.visible && Paths.isSteamApp(appId)
|
||||
|
||||
@@ -198,8 +198,8 @@ Item {
|
||||
IconImage {
|
||||
id: iconImg
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness)
|
||||
height: Theme.barIconSize(root.barThickness)
|
||||
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
source: delegateRoot.iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
@@ -262,7 +262,7 @@ Item {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: root.menuOpen ? "expand_less" : "expand_more"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: Theme.widgetTextColor
|
||||
}
|
||||
|
||||
@@ -331,8 +331,8 @@ Item {
|
||||
IconImage {
|
||||
id: iconImg
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness)
|
||||
height: Theme.barIconSize(root.barThickness)
|
||||
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
source: delegateRoot.iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
@@ -402,7 +402,7 @@ Item {
|
||||
return root.menuOpen ? "chevron_right" : "chevron_left";
|
||||
}
|
||||
}
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
color: Theme.widgetTextColor
|
||||
}
|
||||
|
||||
@@ -754,8 +754,8 @@ Item {
|
||||
IconImage {
|
||||
id: menuIconImg
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness)
|
||||
height: Theme.barIconSize(root.barThickness)
|
||||
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
||||
source: parent.iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
|
||||
@@ -11,17 +11,14 @@ BasePill {
|
||||
readonly property bool hasUpdates: SystemUpdateService.updateCount > 0
|
||||
readonly property bool isChecking: SystemUpdateService.isChecking
|
||||
|
||||
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
|
||||
width: (SettingsData.updaterHideWidget && !hasUpdates) ? 0 : (18 + horizontalPadding * 2)
|
||||
|
||||
Ref {
|
||||
service: SystemUpdateService
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : updaterIcon.implicitWidth
|
||||
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
|
||||
implicitWidth: root.isVerticalOrientation ? root.widgetThickness : updaterIcon.implicitWidth
|
||||
implicitHeight: root.widgetThickness
|
||||
|
||||
DankIcon {
|
||||
id: statusIcon
|
||||
@@ -36,7 +33,7 @@ BasePill {
|
||||
return "system_update_alt";
|
||||
return "check_circle";
|
||||
}
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (SystemUpdateService.hasError)
|
||||
return Theme.error;
|
||||
@@ -93,7 +90,7 @@ BasePill {
|
||||
return "system_update_alt";
|
||||
return "check_circle";
|
||||
}
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: {
|
||||
if (SystemUpdateService.hasError)
|
||||
return Theme.error;
|
||||
|
||||
@@ -41,7 +41,7 @@ BasePill {
|
||||
id: icon
|
||||
|
||||
name: DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off"
|
||||
size: Theme.barIconSize(root.barThickness, -4)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
||||
color: DMSNetworkService.connected ? Theme.primary : Theme.widgetIconColor
|
||||
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -30,7 +30,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
||||
size: Theme.barIconSize(root.barThickness, -6)
|
||||
size: Theme.barIconSize(root.barThickness, -6, root.barConfig?.noBackground)
|
||||
color: Theme.widgetIconColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
@@ -57,7 +57,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
||||
size: Theme.barIconSize(root.barThickness, -6)
|
||||
size: Theme.barIconSize(root.barThickness, -6, root.barConfig?.noBackground)
|
||||
color: Theme.widgetIconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
@@ -199,15 +199,22 @@ Item {
|
||||
|
||||
let targetWorkspaceId;
|
||||
if (CompositorService.isNiri) {
|
||||
const wsNumber = typeof ws === "number" ? ws : -1;
|
||||
if (wsNumber <= 0) {
|
||||
return [];
|
||||
if (!ws || typeof ws !== "object") {
|
||||
const wsNumber = typeof ws === "number" ? ws : -1;
|
||||
if (wsNumber <= 0) {
|
||||
return [];
|
||||
}
|
||||
const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNumber && w.output === root.effectiveScreenName);
|
||||
if (!workspace) {
|
||||
return [];
|
||||
}
|
||||
targetWorkspaceId = workspace.id;
|
||||
} else {
|
||||
if (ws.id === undefined || ws.id === -1 || ws.idx === -1) {
|
||||
return [];
|
||||
}
|
||||
targetWorkspaceId = ws.id;
|
||||
}
|
||||
const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNumber && w.output === root.effectiveScreenName);
|
||||
if (!workspace) {
|
||||
return [];
|
||||
}
|
||||
targetWorkspaceId = workspace.id;
|
||||
} else if (CompositorService.isHyprland) {
|
||||
targetWorkspaceId = ws.id !== undefined ? ws.id : ws;
|
||||
} else if (CompositorService.isDwl) {
|
||||
@@ -300,6 +307,12 @@ Item {
|
||||
"active": false,
|
||||
"hidden": true
|
||||
};
|
||||
} else if (CompositorService.isNiri) {
|
||||
placeholder = {
|
||||
"id": -1,
|
||||
"idx": -1,
|
||||
"name": ""
|
||||
};
|
||||
} else if (CompositorService.isHyprland) {
|
||||
placeholder = {
|
||||
"id": -1,
|
||||
@@ -324,28 +337,52 @@ Item {
|
||||
|
||||
function getNiriWorkspaces() {
|
||||
if (NiriService.allWorkspaces.length === 0) {
|
||||
return [1, 2];
|
||||
return [
|
||||
{
|
||||
"id": 1,
|
||||
"idx": 0,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"idx": 1,
|
||||
"name": ""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
const fallbackWorkspaces = [
|
||||
{
|
||||
"id": 1,
|
||||
"idx": 0,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"idx": 1,
|
||||
"name": ""
|
||||
}
|
||||
];
|
||||
|
||||
let workspaces;
|
||||
if (!root.screenName || SettingsData.workspaceFollowFocus) {
|
||||
workspaces = NiriService.getCurrentOutputWorkspaceNumbers();
|
||||
const currentWorkspaces = NiriService.getCurrentOutputWorkspaces();
|
||||
workspaces = currentWorkspaces.length > 0 ? currentWorkspaces : fallbackWorkspaces;
|
||||
} else {
|
||||
const displayWorkspaces = NiriService.allWorkspaces.filter(ws => ws.output === root.screenName).map(ws => ws.idx + 1);
|
||||
workspaces = displayWorkspaces.length > 0 ? displayWorkspaces : [1, 2];
|
||||
const displayWorkspaces = NiriService.allWorkspaces.filter(ws => ws.output === root.screenName);
|
||||
workspaces = displayWorkspaces.length > 0 ? displayWorkspaces : fallbackWorkspaces;
|
||||
}
|
||||
|
||||
workspaces = workspaces.slice().sort((a, b) => a.idx - b.idx);
|
||||
|
||||
if (!SettingsData.showOccupiedWorkspacesOnly) {
|
||||
return workspaces;
|
||||
}
|
||||
|
||||
return workspaces.filter(wsNum => {
|
||||
const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNum && w.output === root.effectiveScreenName);
|
||||
if (!workspace)
|
||||
return false;
|
||||
if (workspace.is_active)
|
||||
return workspaces.filter(ws => {
|
||||
if (ws.is_active)
|
||||
return true;
|
||||
return NiriService.windows?.some(win => win.workspace_id === workspace.id) ?? false;
|
||||
return NiriService.windows?.some(win => win.workspace_id === ws.id) ?? false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -359,7 +396,7 @@ Item {
|
||||
}
|
||||
|
||||
const activeWs = NiriService.allWorkspaces.find(ws => ws.output === root.screenName && ws.is_active);
|
||||
return activeWs ? activeWs.idx + 1 : 1;
|
||||
return activeWs ? activeWs.idx : 1;
|
||||
}
|
||||
|
||||
function getDwlTags() {
|
||||
@@ -467,19 +504,22 @@ Item {
|
||||
readonly property real padding: Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
|
||||
readonly property real visualWidth: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
|
||||
readonly property real visualHeight: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
|
||||
readonly property real appIconSize: Theme.barIconSize(barThickness, -6)
|
||||
readonly property real appIconSize: Theme.barIconSize(barThickness, -6, root.barConfig?.noBackground)
|
||||
|
||||
function getRealWorkspaces() {
|
||||
return root.workspaceList.filter(ws => {
|
||||
if (useExtWorkspace)
|
||||
return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden;
|
||||
if (CompositorService.isHyprland)
|
||||
return ws && ws.id !== -1;
|
||||
if (CompositorService.isDwl)
|
||||
return ws && ws.tag !== -1;
|
||||
if (CompositorService.isSway || CompositorService.isScroll)
|
||||
return ws && ws.num !== -1;
|
||||
return ws !== -1;
|
||||
if (useExtWorkspace)
|
||||
return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden;
|
||||
if (CompositorService.isNiri)
|
||||
return ws && ws.idx !== -1;
|
||||
if (CompositorService.isHyprland)
|
||||
return ws && ws.id !== -1;
|
||||
if (CompositorService.isDwl)
|
||||
return ws && ws.tag !== -1;
|
||||
if (CompositorService.isSway || CompositorService.isScroll)
|
||||
return ws && ws.num !== -1;
|
||||
return ws !== -1;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -506,7 +546,7 @@ Item {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentIndex = realWorkspaces.findIndex(ws => ws === root.currentWorkspace);
|
||||
const currentIndex = realWorkspaces.findIndex(ws => ws && ws.idx === root.currentWorkspace);
|
||||
const validIndex = currentIndex === -1 ? 0 : currentIndex;
|
||||
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
|
||||
|
||||
@@ -514,7 +554,11 @@ Item {
|
||||
return;
|
||||
}
|
||||
|
||||
NiriService.switchToWorkspace(realWorkspaces[nextIndex] - 1);
|
||||
const nextWorkspace = realWorkspaces[nextIndex];
|
||||
if (!nextWorkspace || nextWorkspace.idx === undefined) {
|
||||
return;
|
||||
}
|
||||
NiriService.switchToWorkspace(nextWorkspace.idx);
|
||||
} else if (CompositorService.isHyprland) {
|
||||
const realWorkspaces = getRealWorkspaces();
|
||||
if (realWorkspaces.length < 2) {
|
||||
@@ -565,10 +609,26 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
function getWorkspaceIndexFallback(modelData, index) {
|
||||
if (root.useExtWorkspace)
|
||||
return index + 1;
|
||||
if (CompositorService.isNiri)
|
||||
return (modelData?.idx !== undefined && modelData?.idx !== -1) ? modelData.idx : "";
|
||||
if (CompositorService.isHyprland)
|
||||
return modelData?.id || "";
|
||||
if (CompositorService.isDwl)
|
||||
return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
|
||||
if (CompositorService.isSway || CompositorService.isScroll)
|
||||
return modelData?.num || "";
|
||||
return modelData - 1;
|
||||
}
|
||||
|
||||
function getWorkspaceIndex(modelData, index) {
|
||||
let isPlaceholder;
|
||||
if (root.useExtWorkspace) {
|
||||
isPlaceholder = modelData?.hidden === true;
|
||||
} else if (CompositorService.isNiri) {
|
||||
isPlaceholder = modelData?.idx === -1;
|
||||
} else if (CompositorService.isHyprland) {
|
||||
isPlaceholder = modelData?.id === -1;
|
||||
} else if (CompositorService.isDwl) {
|
||||
@@ -582,26 +642,28 @@ Item {
|
||||
if (isPlaceholder)
|
||||
return index + 1;
|
||||
|
||||
let workspaceName = "";
|
||||
if (SettingsData.showWorkspaceName) {
|
||||
let workspaceName = modelData?.name;
|
||||
workspaceName = modelData?.name ?? "";
|
||||
|
||||
if (workspaceName && workspaceName !== "") {
|
||||
if (root.isVertical) {
|
||||
return workspaceName.charAt(0);
|
||||
workspaceName = workspaceName.charAt(0);
|
||||
}
|
||||
return workspaceName;
|
||||
} else {
|
||||
workspaceName = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (root.useExtWorkspace)
|
||||
return index + 1;
|
||||
if (CompositorService.isHyprland)
|
||||
return modelData?.id || "";
|
||||
if (CompositorService.isDwl)
|
||||
return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
|
||||
if (CompositorService.isSway || CompositorService.isScroll)
|
||||
return modelData?.num || "";
|
||||
return modelData - 1;
|
||||
if (workspaceName) {
|
||||
if (SettingsData.showWorkspaceIndex) {
|
||||
const indexLabel = getWorkspaceIndexFallback(modelData, index);
|
||||
return indexLabel ? `${indexLabel}: ${workspaceName}` : workspaceName;
|
||||
}
|
||||
return workspaceName;
|
||||
}
|
||||
|
||||
return getWorkspaceIndexFallback(modelData, index);
|
||||
}
|
||||
|
||||
readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll
|
||||
@@ -747,6 +809,8 @@ Item {
|
||||
property bool isActive: {
|
||||
if (root.useExtWorkspace)
|
||||
return (modelData?.id || modelData?.name) === root.currentWorkspace;
|
||||
if (CompositorService.isNiri)
|
||||
return !!(modelData && modelData.idx === root.currentWorkspace);
|
||||
if (CompositorService.isHyprland)
|
||||
return !!(modelData && modelData.id === root.currentWorkspace);
|
||||
if (CompositorService.isDwl)
|
||||
@@ -769,6 +833,8 @@ Item {
|
||||
property bool isPlaceholder: {
|
||||
if (root.useExtWorkspace)
|
||||
return !!(modelData && modelData.hidden);
|
||||
if (CompositorService.isNiri)
|
||||
return !!(modelData && modelData.idx === -1);
|
||||
if (CompositorService.isHyprland)
|
||||
return !!(modelData && modelData.id === -1);
|
||||
if (CompositorService.isDwl)
|
||||
@@ -800,6 +866,10 @@ Item {
|
||||
|
||||
readonly property real baseWidth: root.isVertical ? (SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5) : (isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7)
|
||||
readonly property real baseHeight: root.isVertical ? (isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7) : (SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5)
|
||||
readonly property bool hasWorkspaceName: SettingsData.showWorkspaceName && modelData?.name && modelData.name !== ""
|
||||
readonly property bool workspaceNamesEnabled: SettingsData.showWorkspaceName && CompositorService.isNiri
|
||||
readonly property real contentImplicitWidth: (hasWorkspaceName || loadedHasIcon) ? (appIconsLoader.item?.contentWidth ?? 0) : 0
|
||||
readonly property real contentImplicitHeight: (workspaceNamesEnabled || loadedHasIcon) ? (appIconsLoader.item?.contentHeight ?? 0) : 0
|
||||
|
||||
readonly property real iconsExtraWidth: {
|
||||
if (!root.isVertical && SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
|
||||
@@ -816,8 +886,16 @@ Item {
|
||||
return 0;
|
||||
}
|
||||
|
||||
readonly property real visualWidth: baseWidth + iconsExtraWidth
|
||||
readonly property real visualHeight: baseHeight + iconsExtraHeight
|
||||
readonly property real visualWidth: {
|
||||
if (contentImplicitWidth <= 0) return baseWidth + iconsExtraWidth;
|
||||
const padding = root.isVertical ? Theme.spacingXS : Theme.spacingS;
|
||||
return Math.max(baseWidth + iconsExtraWidth, contentImplicitWidth + padding);
|
||||
}
|
||||
readonly property real visualHeight: {
|
||||
if (contentImplicitHeight <= 0) return baseHeight + iconsExtraHeight;
|
||||
const padding = root.isVertical ? Theme.spacingS : Theme.spacingXS;
|
||||
return Math.max(baseHeight + iconsExtraHeight, contentImplicitHeight + padding);
|
||||
}
|
||||
|
||||
readonly property color unfocusedColor: {
|
||||
switch (SettingsData.workspaceUnfocusedColorMode) {
|
||||
@@ -915,8 +993,8 @@ Item {
|
||||
} else if (CompositorService.isNiri) {
|
||||
if (isRightClick) {
|
||||
NiriService.toggleOverview();
|
||||
} else {
|
||||
NiriService.switchToWorkspace(modelData - 1);
|
||||
} else if (modelData && modelData.idx !== undefined) {
|
||||
NiriService.switchToWorkspace(modelData.idx);
|
||||
}
|
||||
} else if (CompositorService.isHyprland && modelData?.id) {
|
||||
if (isRightClick && root.hyprlandOverviewLoader?.item) {
|
||||
@@ -958,7 +1036,7 @@ Item {
|
||||
if (root.useExtWorkspace) {
|
||||
wsData = modelData;
|
||||
} else if (CompositorService.isNiri) {
|
||||
wsData = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName) || null;
|
||||
wsData = modelData || null;
|
||||
} else if (CompositorService.isHyprland) {
|
||||
wsData = modelData;
|
||||
} else if (CompositorService.isDwl) {
|
||||
@@ -984,6 +1062,8 @@ Item {
|
||||
if (SettingsData.showWorkspaceApps) {
|
||||
if (CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll) {
|
||||
delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
|
||||
} else if (CompositorService.isNiri) {
|
||||
delegateRoot.loadedIcons = root.getWorkspaceIcons(isPlaceholder ? null : modelData);
|
||||
} else {
|
||||
delegateRoot.loadedIcons = root.getWorkspaceIcons(CompositorService.isHyprland ? modelData : (modelData === -1 ? null : modelData));
|
||||
}
|
||||
@@ -1096,8 +1176,12 @@ Item {
|
||||
Loader {
|
||||
id: appIconsLoader
|
||||
anchors.fill: parent
|
||||
active: SettingsData.showWorkspaceApps
|
||||
active: SettingsData.showWorkspaceApps || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon
|
||||
sourceComponent: Item {
|
||||
id: contentRoot
|
||||
readonly property real contentWidth: contentRow.item?.implicitWidth ?? 0
|
||||
readonly property real contentHeight: contentRow.item?.implicitHeight ?? 0
|
||||
|
||||
Loader {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -23,6 +23,7 @@ Item {
|
||||
property string pendingSaveContent: ""
|
||||
|
||||
signal hideRequested
|
||||
signal previewRequested(string content)
|
||||
|
||||
Ref {
|
||||
service: NotepadStorageService
|
||||
@@ -198,6 +199,10 @@ Item {
|
||||
createNewTab();
|
||||
}
|
||||
|
||||
onPreviewRequested: {
|
||||
textEditor.togglePreview();
|
||||
}
|
||||
|
||||
onEscapePressed: {
|
||||
root.hideRequested();
|
||||
}
|
||||
@@ -357,8 +362,8 @@ Item {
|
||||
DankModal {
|
||||
id: confirmationDialog
|
||||
|
||||
width: 400
|
||||
height: 180
|
||||
modalWidth: 400
|
||||
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 180
|
||||
shouldBeVisible: false
|
||||
allowStacking: true
|
||||
|
||||
@@ -371,6 +376,7 @@ Item {
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
implicitHeight: contentColumn.implicitHeight
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
confirmationDialog.close();
|
||||
@@ -379,47 +385,31 @@ Item {
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
id: contentColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
StyledText {
|
||||
text: I18n.tr("Unsaved Changes")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.pendingAction === "new" ? I18n.tr("You have unsaved changes. Save before creating a new file?") : root.pendingAction.startsWith("close_tab_") ? I18n.tr("You have unsaved changes. Save before closing this tab?") : root.pendingAction === "load_file" || root.pendingAction === "open" ? I18n.tr("You have unsaved changes. Save before opening a file?") : I18n.tr("You have unsaved changes. Save before continuing?")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
width: parent.width
|
||||
|
||||
Column {
|
||||
width: parent.width - 40
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Unsaved Changes")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.pendingAction === "new" ? I18n.tr("You have unsaved changes. Save before creating a new file?") : root.pendingAction.startsWith("close_tab_") ? I18n.tr("You have unsaved changes. Save before closing this tab?") : root.pendingAction === "load_file" || root.pendingAction === "open" ? I18n.tr("You have unsaved changes. Save before opening a file?") : I18n.tr("You have unsaved changes. Save before continuing?")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
confirmationDialog.close();
|
||||
root.confirmationDialogOpen = false;
|
||||
}
|
||||
}
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
height: 36
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
@@ -510,6 +500,20 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
confirmationDialog.close();
|
||||
root.confirmationDialogOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ pragma ComponentBehavior: Bound
|
||||
Column {
|
||||
id: root
|
||||
|
||||
Component.onCompleted: {
|
||||
if (PluginService.isPluginLoaded("dankNotepadModule")) {
|
||||
pluginHighlightedHtml = SettingsData.getBuiltInPluginSetting("dankNotepadModule", "highlightedHtml", "")
|
||||
}
|
||||
}
|
||||
|
||||
property alias text: textArea.text
|
||||
property alias textArea: textArea
|
||||
property bool contentLoaded: false
|
||||
@@ -21,10 +27,15 @@ Column {
|
||||
property var searchMatches: []
|
||||
property int currentMatchIndex: -1
|
||||
property int matchCount: 0
|
||||
property bool inlinePreviewVisible: false
|
||||
property string previewMode: "split" // split | full
|
||||
property string pluginHighlightedHtml: ""
|
||||
property string lastPluginContent: ""
|
||||
|
||||
signal saveRequested()
|
||||
signal openRequested()
|
||||
signal newRequested()
|
||||
signal previewRequested()
|
||||
signal escapePressed()
|
||||
signal contentChanged()
|
||||
signal settingsRequested()
|
||||
@@ -50,6 +61,7 @@ Column {
|
||||
lastSavedContent = content
|
||||
textArea.text = content
|
||||
contentLoaded = true
|
||||
syncContentToPlugin()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -164,6 +176,47 @@ Column {
|
||||
})
|
||||
}
|
||||
|
||||
function togglePreview() {
|
||||
if (!inlinePreviewVisible) {
|
||||
inlinePreviewVisible = true
|
||||
previewMode = "split"
|
||||
} else if (previewMode === "split") {
|
||||
previewMode = "full"
|
||||
} else {
|
||||
inlinePreviewVisible = false
|
||||
previewMode = "split"
|
||||
}
|
||||
syncContentToPlugin()
|
||||
}
|
||||
|
||||
function renderPreviewHtml() {
|
||||
if (!inlinePreviewVisible) return ""
|
||||
return pluginHighlightedHtml.length > 0 ? pluginHighlightedHtml : "<p><i>Rendering preview…</i></p>"
|
||||
}
|
||||
|
||||
function syncContentToPlugin() {
|
||||
if (!PluginService.isPluginLoaded("dankNotepadModule"))
|
||||
return
|
||||
|
||||
if (!currentTab)
|
||||
return
|
||||
|
||||
const filePath = currentTab?.filePath || ""
|
||||
const ext = filePath.split('.').pop().toLowerCase()
|
||||
const content = textArea.text
|
||||
|
||||
if (content === lastPluginContent && SettingsData.getBuiltInPluginSetting("dankNotepadModule", "previewActive", false) === inlinePreviewVisible) {
|
||||
return
|
||||
}
|
||||
|
||||
lastPluginContent = content
|
||||
SettingsData.setBuiltInPluginSetting("dankNotepadModule", "previewActive", inlinePreviewVisible)
|
||||
SettingsData.setBuiltInPluginSetting("dankNotepadModule", "currentFilePath", filePath)
|
||||
SettingsData.setBuiltInPluginSetting("dankNotepadModule", "currentFileExtension", ext)
|
||||
SettingsData.setBuiltInPluginSetting("dankNotepadModule", "sourceContent", content)
|
||||
SettingsData.setBuiltInPluginSetting("dankNotepadModule", "updatedAt", Date.now())
|
||||
}
|
||||
|
||||
function hideSearch() {
|
||||
searchVisible = false
|
||||
searchQuery = ""
|
||||
@@ -174,6 +227,57 @@ Column {
|
||||
textArea.forceActiveFocus()
|
||||
}
|
||||
|
||||
function copyPlainTextToClipboard() {
|
||||
if (!inlinePreviewVisible || !textArea.text) return
|
||||
|
||||
const content = textArea.text
|
||||
if (content.length > 0) {
|
||||
const proc = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
property string content: ""
|
||||
command: ["sh", "-c", "printf '%s' \\"$CONTENT\\" | dms clipboard copy"]
|
||||
environment: { "CONTENT": content }
|
||||
running: false
|
||||
}`,
|
||||
root,
|
||||
"copyProc"
|
||||
)
|
||||
proc.content = content
|
||||
proc.running = true
|
||||
proc.exited.connect(() => {
|
||||
ToastService.showInfo(I18n.tr("Copied to clipboard"))
|
||||
proc.destroy()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function copyHtmlToClipboard() {
|
||||
if (!inlinePreviewVisible || !pluginHighlightedHtml) return
|
||||
|
||||
if (pluginHighlightedHtml.length > 0) {
|
||||
const proc = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
property string content: ""
|
||||
command: ["sh", "-c", "printf '%s' \\"$CONTENT\\" | dms clipboard copy"]
|
||||
environment: { "CONTENT": content }
|
||||
running: false
|
||||
}`,
|
||||
root,
|
||||
"copyProcHtml"
|
||||
)
|
||||
proc.content = pluginHighlightedHtml
|
||||
proc.running = true
|
||||
proc.exited.connect(() => {
|
||||
ToastService.showInfo(I18n.tr("HTML copied to clipboard"))
|
||||
proc.destroy()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledRect {
|
||||
@@ -303,7 +407,6 @@ Column {
|
||||
onClicked: root.findNext()
|
||||
}
|
||||
|
||||
// Close button
|
||||
DankActionButton {
|
||||
id: closeSearchButton
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
@@ -323,192 +426,311 @@ Column {
|
||||
border.width: 1
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
DankFlickable {
|
||||
id: flickable
|
||||
RowLayout {
|
||||
id: editorPreviewRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
clip: true
|
||||
contentWidth: width - 11
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Item {
|
||||
id: editorPane
|
||||
visible: !inlinePreviewVisible || previewMode === "split"
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: !inlinePreviewVisible || previewMode === "split"
|
||||
Layout.preferredWidth: inlinePreviewVisible ? parent.width * 0.55 : parent.width
|
||||
clip: true
|
||||
|
||||
DankFlickable {
|
||||
id: flickable
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentWidth: width - 11
|
||||
|
||||
Rectangle {
|
||||
id: lineNumberArea
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: SettingsData.notepadShowLineNumbers ? Math.max(30, 32 + Theme.spacingXS) : 0
|
||||
height: textArea.contentHeight + textArea.topPadding + textArea.bottomPadding
|
||||
color: "transparent"
|
||||
visible: SettingsData.notepadShowLineNumbers
|
||||
|
||||
ListView {
|
||||
id: lineNumberList
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: textArea.topPadding
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2
|
||||
width: 32
|
||||
height: textArea.contentHeight
|
||||
model: SettingsData.notepadShowLineNumbers ? root.lineModel : []
|
||||
interactive: false
|
||||
spacing: 0
|
||||
|
||||
delegate: Item {
|
||||
id: lineDelegate
|
||||
required property int index
|
||||
required property string modelData
|
||||
width: 32
|
||||
height: measuringText.contentHeight
|
||||
|
||||
Text {
|
||||
id: measuringText
|
||||
width: textArea.width - textArea.leftPadding - textArea.rightPadding
|
||||
text: modelData || " "
|
||||
font: textArea.font
|
||||
wrapMode: Text.Wrap
|
||||
visible: false
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 4
|
||||
anchors.top: parent.top
|
||||
text: index + 1
|
||||
font.family: textArea.font.family
|
||||
font.pixelSize: textArea.font.pixelSize
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextArea.flickable: TextArea {
|
||||
id: textArea
|
||||
placeholderText: ""
|
||||
placeholderTextColor: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
font.family: SettingsData.notepadUseMonospace ? SettingsData.monoFontFamily : (SettingsData.notepadFontFamily || SettingsData.fontFamily)
|
||||
font.pixelSize: SettingsData.notepadFontSize * SettingsData.fontScale
|
||||
font.letterSpacing: 0
|
||||
color: Theme.surfaceText
|
||||
selectedTextColor: Theme.background
|
||||
selectionColor: Theme.primary
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
wrapMode: TextArea.Wrap
|
||||
focus: true
|
||||
activeFocusOnTab: true
|
||||
textFormat: TextEdit.PlainText
|
||||
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
|
||||
persistentSelection: true
|
||||
tabStopDistance: 40
|
||||
leftPadding: (SettingsData.notepadShowLineNumbers ? lineNumberArea.width + Theme.spacingXS : Theme.spacingM)
|
||||
topPadding: Theme.spacingM
|
||||
rightPadding: Theme.spacingM
|
||||
bottomPadding: Theme.spacingM
|
||||
cursorDelegate: Rectangle {
|
||||
width: 1.5
|
||||
radius: 1
|
||||
color: Theme.surfaceText
|
||||
x: textArea.cursorRectangle.x
|
||||
y: textArea.cursorRectangle.y
|
||||
height: textArea.cursorRectangle.height
|
||||
opacity: 1.0
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: textArea.activeFocus
|
||||
loops: Animation.Infinite
|
||||
PropertyAnimation { from: 1.0; to: 0.0; duration: 650; easing.type: Easing.InOutQuad }
|
||||
PropertyAnimation { from: 0.0; to: 1.0; duration: 650; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
loadCurrentTabContent()
|
||||
setTextDocumentLineHeight()
|
||||
root.updateLineModel()
|
||||
Qt.callLater(() => {
|
||||
textArea.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NotepadStorageService
|
||||
function onCurrentTabIndexChanged() {
|
||||
loadCurrentTabContent()
|
||||
Qt.callLater(() => {
|
||||
textArea.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
function onTabsChanged() {
|
||||
if (NotepadStorageService.tabs.length > 0 && !contentLoaded) {/* Lines 444-445 omitted */}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onNotepadShowLineNumbersChanged() {
|
||||
root.updateLineModel()
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
if (contentLoaded && text !== lastSavedContent) {
|
||||
autoSaveTimer.restart()
|
||||
}
|
||||
root.contentChanged()
|
||||
root.updateLineModel()
|
||||
pluginSyncTimer.restart()
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: (event) => {
|
||||
root.escapePressed()
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switch (event.key) {
|
||||
case Qt.Key_S:
|
||||
event.accepted = true
|
||||
root.saveRequested()
|
||||
break
|
||||
case Qt.Key_O:
|
||||
event.accepted = true
|
||||
root.openRequested()
|
||||
break
|
||||
case Qt.Key_N:
|
||||
event.accepted = true
|
||||
root.newRequested()
|
||||
break
|
||||
case Qt.Key_A:
|
||||
event.accepted = true
|
||||
textArea.selectAll()
|
||||
break
|
||||
case Qt.Key_F:
|
||||
event.accepted = true
|
||||
root.showSearch()
|
||||
break
|
||||
case Qt.Key_P:
|
||||
if (PluginService.isPluginLoaded("dankNotepadModule")) {
|
||||
event.accepted = true
|
||||
root.previewRequested()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: placeholderOverlay
|
||||
text: I18n.tr("Start typing your notes here...")
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
font.family: textArea.font.family
|
||||
font.pixelSize: textArea.font.pixelSize
|
||||
visible: textArea.text.length === 0
|
||||
anchors.left: textArea.left
|
||||
anchors.top: textArea.top
|
||||
anchors.leftMargin: textArea.leftPadding
|
||||
anchors.topMargin: textArea.topPadding
|
||||
z: textArea.z + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: lineNumberArea
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: SettingsData.notepadShowLineNumbers ? Math.max(30, 32 + Theme.spacingXS) : 0
|
||||
height: textArea.contentHeight + textArea.topPadding + textArea.bottomPadding
|
||||
color: "transparent"
|
||||
visible: SettingsData.notepadShowLineNumbers
|
||||
id: previewDivider
|
||||
visible: inlinePreviewVisible && previewMode === "split"
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 1
|
||||
color: Theme.outlineMedium
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: lineNumberList
|
||||
Item {
|
||||
id: previewPane
|
||||
visible: inlinePreviewVisible
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: previewMode === "full"
|
||||
Layout.preferredWidth: previewMode === "full" ? parent.width : parent.width * 0.45
|
||||
clip: true
|
||||
|
||||
// Preview header with copy buttons
|
||||
Rectangle {
|
||||
id: previewHeader
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: textArea.topPadding
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2
|
||||
width: 32
|
||||
height: textArea.contentHeight
|
||||
model: SettingsData.notepadShowLineNumbers ? root.lineModel : []
|
||||
interactive: false
|
||||
spacing: 0
|
||||
height: 36
|
||||
color: Qt.rgba(Theme.surface.r, Theme.surface.g, Theme.surface.b, Theme.notepadTransparency)
|
||||
z: 2
|
||||
|
||||
delegate: Item {
|
||||
id: lineDelegate
|
||||
required property int index
|
||||
required property string modelData
|
||||
width: 32
|
||||
height: measuringText.contentHeight
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
id: measuringText
|
||||
width: textArea.width - textArea.leftPadding - textArea.rightPadding
|
||||
text: modelData || " "
|
||||
font: textArea.font
|
||||
wrapMode: Text.Wrap
|
||||
visible: false
|
||||
// Copy plain text button
|
||||
DankActionButton {
|
||||
iconName: "content_copy"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceTextMedium
|
||||
onClicked: copyPlainTextToClipboard()
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 4
|
||||
anchors.top: parent.top
|
||||
text: index + 1
|
||||
font.family: textArea.font.family
|
||||
font.pixelSize: textArea.font.pixelSize
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||
horizontalAlignment: Text.AlignRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("Copy Text")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextArea.flickable: TextArea {
|
||||
id: textArea
|
||||
placeholderText: ""
|
||||
placeholderTextColor: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
font.family: SettingsData.notepadUseMonospace ? SettingsData.monoFontFamily : (SettingsData.notepadFontFamily || SettingsData.fontFamily)
|
||||
font.pixelSize: SettingsData.notepadFontSize * SettingsData.fontScale
|
||||
font.letterSpacing: 0
|
||||
color: Theme.surfaceText
|
||||
selectedTextColor: Theme.background
|
||||
selectionColor: Theme.primary
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
wrapMode: TextArea.Wrap
|
||||
focus: true
|
||||
activeFocusOnTab: true
|
||||
textFormat: TextEdit.PlainText
|
||||
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
|
||||
persistentSelection: true
|
||||
tabStopDistance: 40
|
||||
leftPadding: (SettingsData.notepadShowLineNumbers ? lineNumberArea.width + Theme.spacingXS : Theme.spacingM)
|
||||
topPadding: Theme.spacingM
|
||||
rightPadding: Theme.spacingM
|
||||
bottomPadding: Theme.spacingM
|
||||
cursorDelegate: Rectangle {
|
||||
width: 1.5
|
||||
radius: 1
|
||||
color: Theme.surfaceText
|
||||
x: textArea.cursorRectangle.x
|
||||
y: textArea.cursorRectangle.y
|
||||
height: textArea.cursorRectangle.height
|
||||
opacity: 1.0
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: 20
|
||||
color: Theme.outlineVariant
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: textArea.activeFocus
|
||||
loops: Animation.Infinite
|
||||
PropertyAnimation { from: 1.0; to: 0.0; duration: 650; easing.type: Easing.InOutQuad }
|
||||
PropertyAnimation { from: 0.0; to: 1.0; duration: 650; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
}
|
||||
// Copy HTML button
|
||||
DankActionButton {
|
||||
iconName: "code"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceTextMedium
|
||||
onClicked: copyHtmlToClipboard()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
loadCurrentTabContent()
|
||||
setTextDocumentLineHeight()
|
||||
root.updateLineModel()
|
||||
Qt.callLater(() => {
|
||||
textArea.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NotepadStorageService
|
||||
function onCurrentTabIndexChanged() {
|
||||
loadCurrentTabContent()
|
||||
Qt.callLater(() => {
|
||||
textArea.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
function onTabsChanged() {
|
||||
if (NotepadStorageService.tabs.length > 0 && !contentLoaded) {
|
||||
loadCurrentTabContent()
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("Copy HTML")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onNotepadShowLineNumbersChanged() {
|
||||
root.updateLineModel()
|
||||
DankFlickable {
|
||||
id: previewFlickable
|
||||
anchors.top: previewHeader.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: Theme.spacingS
|
||||
clip: true
|
||||
contentWidth: width - 11
|
||||
contentHeight: previewText.paintedHeight + Theme.spacingM * 2
|
||||
|
||||
Text {
|
||||
id: previewText
|
||||
width: parent.width - Theme.spacingM
|
||||
padding: Theme.spacingM
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.RichText
|
||||
text: inlinePreviewVisible ? renderPreviewHtml() : ""
|
||||
color: Theme.surfaceText
|
||||
font.family: SettingsData.notepadFontFamily || SettingsData.fontFamily
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
linkColor: Theme.primary
|
||||
|
||||
onLinkActivated: url => Qt.openUrlExternally(url)
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
if (contentLoaded && text !== lastSavedContent) {
|
||||
autoSaveTimer.restart()
|
||||
}
|
||||
root.contentChanged()
|
||||
root.updateLineModel()
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: (event) => {
|
||||
root.escapePressed()
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switch (event.key) {
|
||||
case Qt.Key_S:
|
||||
event.accepted = true
|
||||
root.saveRequested()
|
||||
break
|
||||
case Qt.Key_O:
|
||||
event.accepted = true
|
||||
root.openRequested()
|
||||
break
|
||||
case Qt.Key_N:
|
||||
event.accepted = true
|
||||
root.newRequested()
|
||||
break
|
||||
case Qt.Key_A:
|
||||
event.accepted = true
|
||||
selectAll()
|
||||
break
|
||||
case Qt.Key_F:
|
||||
event.accepted = true
|
||||
root.showSearch()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: placeholderOverlay
|
||||
text: I18n.tr("Start typing your notes here...")
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
font.family: textArea.font.family
|
||||
font.pixelSize: textArea.font.pixelSize
|
||||
visible: textArea.text.length === 0
|
||||
anchors.left: textArea.left
|
||||
anchors.top: textArea.top
|
||||
anchors.leftMargin: textArea.leftPadding
|
||||
anchors.topMargin: textArea.topPadding
|
||||
z: textArea.z + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -575,6 +797,24 @@ Column {
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
visible: PluginService.isPluginLoaded("dankNotepadModule")
|
||||
DankActionButton {
|
||||
iconName: inlinePreviewVisible ? "visibility" : "visibility_off"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.surfaceText
|
||||
enabled: textArea.text.length > 0
|
||||
onClicked: root.previewRequested()
|
||||
}
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("Preview")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
@@ -646,4 +886,20 @@ Column {
|
||||
autoSaveToSession()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: pluginSyncTimer
|
||||
interval: 350
|
||||
repeat: false
|
||||
onTriggered: syncContentToPlugin()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onBuiltInPluginSettingsChanged() {
|
||||
if (PluginService.isPluginLoaded("dankNotepadModule")) {
|
||||
pluginHighlightedHtml = SettingsData.getBuiltInPluginSetting("dankNotepadModule", "highlightedHtml", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,8 +558,8 @@ Rectangle {
|
||||
visible: !expanded
|
||||
anchors.right: clearButton.visible ? clearButton.left : parent.right
|
||||
anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: contentSpacing
|
||||
anchors.top: collapsedContent.bottom
|
||||
anchors.topMargin: contentSpacing
|
||||
spacing: contentSpacing
|
||||
|
||||
Repeater {
|
||||
@@ -614,8 +614,8 @@ Rectangle {
|
||||
visible: !expanded && actionCount < 3
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: contentSpacing
|
||||
anchors.top: collapsedContent.bottom
|
||||
anchors.topMargin: contentSpacing
|
||||
width: Math.max(clearText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
|
||||
height: actionButtonHeight
|
||||
radius: Theme.spacingXS
|
||||
|
||||
@@ -531,8 +531,8 @@ PanelWindow {
|
||||
Row {
|
||||
anchors.right: clearButton.visible ? clearButton.left : parent.right
|
||||
anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: contentSpacing
|
||||
anchors.top: notificationContent.bottom
|
||||
anchors.topMargin: contentSpacing
|
||||
spacing: contentSpacing
|
||||
z: 20
|
||||
|
||||
@@ -585,8 +585,8 @@ PanelWindow {
|
||||
visible: actionCount < 3
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: contentSpacing
|
||||
anchors.top: notificationContent.bottom
|
||||
anchors.topMargin: contentSpacing
|
||||
width: Math.max(clearTextLabel.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
|
||||
height: actionButtonHeight
|
||||
radius: Theme.spacingXS
|
||||
|
||||
@@ -59,8 +59,8 @@ Item {
|
||||
readonly property bool hasVerticalPill: verticalBarPill !== null
|
||||
readonly property bool hasPopout: popoutContent !== null
|
||||
|
||||
readonly property int iconSize: Theme.barIconSize(barThickness, -4)
|
||||
readonly property int iconSizeLarge: Theme.barIconSize(barThickness)
|
||||
readonly property int iconSize: Theme.barIconSize(barThickness, -4, root.barConfig?.noBackground)
|
||||
readonly property int iconSizeLarge: Theme.barIconSize(barThickness, undefined, root.barConfig?.noBackground)
|
||||
|
||||
Component.onCompleted: {
|
||||
loadPluginData();
|
||||
|
||||
@@ -73,6 +73,9 @@ DankPopout {
|
||||
root.close();
|
||||
};
|
||||
}
|
||||
if (item && "parentPopout" in item) {
|
||||
item.parentPopout = root;
|
||||
}
|
||||
if (item) {
|
||||
root.contentHeight = Qt.binding(() => item.implicitHeight + Theme.spacingS * 2);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ Column {
|
||||
property string detailsText: ""
|
||||
property bool showCloseButton: false
|
||||
property var closePopout: null
|
||||
property var parentPopout: null
|
||||
property alias headerActions: headerActionsLoader.sourceComponent
|
||||
|
||||
readonly property int headerHeight: popoutHeader.visible ? popoutHeader.height : 0
|
||||
|
||||
@@ -318,7 +318,7 @@ Popup {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: modelData.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: modelData.enabled
|
||||
enabled: modelData.enabled ?? false
|
||||
onEntered: {
|
||||
keyboardNavigation = false;
|
||||
selectedIndex = index;
|
||||
|
||||
@@ -7,6 +7,9 @@ import qs.Widgets
|
||||
Item {
|
||||
id: aboutTab
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property bool isHyprland: CompositorService.isHyprland
|
||||
property bool isNiri: CompositorService.isNiri
|
||||
property bool isSway: CompositorService.isSway
|
||||
@@ -255,7 +258,7 @@ Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
property bool compactMode: parent.width < 400
|
||||
property bool compactMode: parent.width < 450
|
||||
|
||||
DankButton {
|
||||
id: docsButton
|
||||
@@ -628,6 +631,7 @@ Item {
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Column {
|
||||
@@ -637,6 +641,7 @@ Item {
|
||||
text: I18n.tr("Version")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -644,6 +649,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,6 +666,7 @@ Item {
|
||||
text: I18n.tr("API")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -667,6 +674,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,6 +691,7 @@ Item {
|
||||
text: I18n.tr("Status")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -701,6 +710,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -715,6 +725,8 @@ Item {
|
||||
text: I18n.tr("Capabilities")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
Flow {
|
||||
@@ -780,6 +792,7 @@ Item {
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankButton {
|
||||
|
||||
@@ -9,6 +9,9 @@ import qs.Modules.Settings.Widgets
|
||||
Item {
|
||||
id: dankBarTab
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property var parentModal: null
|
||||
property string selectedBarId: "default"
|
||||
|
||||
@@ -366,9 +369,12 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
@@ -390,12 +396,14 @@ Item {
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -409,12 +417,14 @@ Item {
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -428,12 +438,14 @@ Item {
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
visible: {
|
||||
SettingsData.barConfigs;
|
||||
const cfg = SettingsData.getBarConfig(barCard.modelData.id);
|
||||
@@ -445,6 +457,7 @@ Item {
|
||||
text: I18n.tr("Disabled")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.error
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
visible: {
|
||||
SettingsData.barConfigs;
|
||||
const cfg = SettingsData.getBarConfig(barCard.modelData.id);
|
||||
@@ -520,6 +533,7 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -1126,7 +1140,9 @@ Item {
|
||||
text: I18n.tr("Color")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
x: Theme.spacingM
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
}
|
||||
|
||||
Item {
|
||||
|
||||
@@ -236,7 +236,6 @@ Item {
|
||||
DankTextField {
|
||||
id: searchField
|
||||
width: parent.width - addButton.width - Theme.spacingM
|
||||
height: Math.round(Theme.fontSizeMedium * 3)
|
||||
placeholderText: I18n.tr("Search keybinds...")
|
||||
leftIconName: "search"
|
||||
onTextChanged: {
|
||||
|
||||
@@ -353,6 +353,116 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "tune"
|
||||
title: I18n.tr("Appearance", "launcher appearance settings")
|
||||
settingKey: "dankLauncherV2Appearance"
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Size", "launcher size option")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: sizeGroup.implicitHeight
|
||||
clip: true
|
||||
|
||||
DankButtonGroup {
|
||||
id: sizeGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
buttonPadding: parent.width < 400 ? Theme.spacingS : Theme.spacingL
|
||||
minButtonWidth: parent.width < 400 ? 60 : 80
|
||||
textSize: parent.width < 400 ? Theme.fontSizeSmall : Theme.fontSizeMedium
|
||||
model: [I18n.tr("Compact", "compact launcher size"), I18n.tr("Medium", "medium launcher size"), I18n.tr("Large", "large launcher size")]
|
||||
currentIndex: SettingsData.dankLauncherV2Size === "compact" ? 0 : SettingsData.dankLauncherV2Size === "large" ? 2 : 1
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
SettingsData.set("dankLauncherV2Size", index === 0 ? "compact" : index === 2 ? "large" : "medium");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "dankLauncherV2ShowFooter"
|
||||
tags: ["launcher", "footer", "hints", "shortcuts"]
|
||||
text: I18n.tr("Show Footer", "launcher footer visibility")
|
||||
description: I18n.tr("Show mode tabs and keyboard hints at the bottom.", "launcher footer description")
|
||||
checked: SettingsData.dankLauncherV2ShowFooter
|
||||
onToggled: checked => SettingsData.set("dankLauncherV2ShowFooter", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "dankLauncherV2BorderEnabled"
|
||||
tags: ["launcher", "border", "outline"]
|
||||
text: I18n.tr("Border", "launcher border option")
|
||||
checked: SettingsData.dankLauncherV2BorderEnabled
|
||||
onToggled: checked => SettingsData.set("dankLauncherV2BorderEnabled", checked)
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: SettingsData.dankLauncherV2BorderEnabled
|
||||
|
||||
SettingsSliderRow {
|
||||
settingKey: "dankLauncherV2BorderThickness"
|
||||
tags: ["launcher", "border", "thickness"]
|
||||
text: I18n.tr("Thickness", "border thickness")
|
||||
minimum: 1
|
||||
maximum: 6
|
||||
value: SettingsData.dankLauncherV2BorderThickness
|
||||
defaultValue: 2
|
||||
unit: "px"
|
||||
onSliderValueChanged: newValue => SettingsData.set("dankLauncherV2BorderThickness", newValue)
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Color", "border color")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: borderColorGroup.implicitHeight
|
||||
clip: true
|
||||
|
||||
DankButtonGroup {
|
||||
id: borderColorGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
buttonPadding: parent.width < 400 ? Theme.spacingS : Theme.spacingL
|
||||
minButtonWidth: parent.width < 400 ? 50 : 70
|
||||
textSize: parent.width < 400 ? Theme.fontSizeSmall : Theme.fontSizeMedium
|
||||
model: [I18n.tr("Primary", "primary color"), I18n.tr("Secondary", "secondary color"), I18n.tr("Outline", "outline color"), I18n.tr("Text", "text color")]
|
||||
currentIndex: SettingsData.dankLauncherV2BorderColor === "secondary" ? 1 : SettingsData.dankLauncherV2BorderColor === "outline" ? 2 : SettingsData.dankLauncherV2BorderColor === "surfaceText" ? 3 : 0
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
SettingsData.set("dankLauncherV2BorderColor", index === 1 ? "secondary" : index === 2 ? "outline" : index === 3 ? "surfaceText" : "primary");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "open_in_new"
|
||||
@@ -462,6 +572,227 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
id: pluginVisibilityCard
|
||||
width: parent.width
|
||||
iconName: "filter_list"
|
||||
title: I18n.tr("Plugin Visibility")
|
||||
settingKey: "pluginVisibility"
|
||||
|
||||
property var allLauncherPlugins: {
|
||||
SettingsData.launcherPluginVisibility;
|
||||
SettingsData.launcherPluginOrder;
|
||||
var plugins = [];
|
||||
var builtIn = AppSearchService.getBuiltInLauncherPlugins() || {};
|
||||
for (var pluginId in builtIn) {
|
||||
var plugin = builtIn[pluginId];
|
||||
plugins.push({
|
||||
id: pluginId,
|
||||
name: plugin.name || pluginId,
|
||||
icon: plugin.cornerIcon || "extension",
|
||||
iconType: "material",
|
||||
isBuiltIn: true,
|
||||
trigger: AppSearchService.getBuiltInPluginTrigger(pluginId) || ""
|
||||
});
|
||||
}
|
||||
var thirdParty = PluginService.getLauncherPlugins() || {};
|
||||
for (var pluginId in thirdParty) {
|
||||
var plugin = thirdParty[pluginId];
|
||||
var rawIcon = plugin.icon || "extension";
|
||||
plugins.push({
|
||||
id: pluginId,
|
||||
name: plugin.name || pluginId,
|
||||
icon: rawIcon.startsWith("material:") ? rawIcon.substring(9) : rawIcon.startsWith("unicode:") ? rawIcon.substring(8) : rawIcon,
|
||||
iconType: rawIcon.startsWith("unicode:") ? "unicode" : "material",
|
||||
isBuiltIn: false,
|
||||
trigger: PluginService.getPluginTrigger(pluginId) || ""
|
||||
});
|
||||
}
|
||||
return SettingsData.getOrderedLauncherPlugins(plugins);
|
||||
}
|
||||
|
||||
function reorderPlugin(fromIndex, toIndex) {
|
||||
if (fromIndex === toIndex)
|
||||
return;
|
||||
var currentOrder = allLauncherPlugins.map(p => p.id);
|
||||
var item = currentOrder.splice(fromIndex, 1)[0];
|
||||
currentOrder.splice(toIndex, 0, item);
|
||||
SettingsData.setLauncherPluginOrder(currentOrder);
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("Control which plugins appear in 'All' mode without requiring a trigger prefix. Drag to reorder.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Column {
|
||||
id: pluginVisibilityColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: pluginVisibilityCard.allLauncherPlugins
|
||||
|
||||
delegate: Item {
|
||||
id: visibilityDelegateItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
property bool held: pluginDragArea.pressed
|
||||
property real originalY: y
|
||||
|
||||
width: pluginVisibilityColumn.width
|
||||
height: 52
|
||||
z: held ? 2 : 1
|
||||
|
||||
Rectangle {
|
||||
id: visibilityDelegate
|
||||
width: parent.width
|
||||
height: 52
|
||||
radius: Theme.cornerRadius
|
||||
color: visibilityDelegateItem.held ? Theme.surfaceHover : Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 28
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Item {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: visibilityDelegateItem.modelData.iconType !== "unicode"
|
||||
name: visibilityDelegateItem.modelData.icon
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
visible: visibilityDelegateItem.modelData.iconType === "unicode"
|
||||
text: visibilityDelegateItem.modelData.icon
|
||||
font.pixelSize: Theme.iconSize
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: visibilityDelegateItem.modelData.name
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: visibilityDelegateItem.modelData.isBuiltIn
|
||||
width: dmsBadgeLabel.implicitWidth + Theme.spacingS
|
||||
height: 16
|
||||
radius: 8
|
||||
color: Theme.primaryContainer
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
id: dmsBadgeLabel
|
||||
anchors.centerIn: parent
|
||||
text: "DMS"
|
||||
font.pixelSize: Theme.fontSizeSmall - 2
|
||||
color: Theme.primaryText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: visibilityDelegateItem.modelData.trigger ? I18n.tr("Trigger: %1").arg(visibilityDelegateItem.modelData.trigger) : I18n.tr("No trigger")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.getPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id)
|
||||
onToggled: function (isChecked) {
|
||||
SettingsData.setPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id, isChecked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pluginDragArea
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 28
|
||||
height: parent.height
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
drag.target: visibilityDelegateItem.held ? visibilityDelegateItem : undefined
|
||||
drag.axis: Drag.YAxis
|
||||
preventStealing: true
|
||||
|
||||
onPressed: {
|
||||
visibilityDelegateItem.originalY = visibilityDelegateItem.y;
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (!drag.active) {
|
||||
visibilityDelegateItem.y = visibilityDelegateItem.originalY;
|
||||
return;
|
||||
}
|
||||
const spacing = Theme.spacingS;
|
||||
const itemH = visibilityDelegateItem.height + spacing;
|
||||
var newIndex = Math.round(visibilityDelegateItem.y / itemH);
|
||||
newIndex = Math.max(0, Math.min(newIndex, pluginVisibilityCard.allLauncherPlugins.length - 1));
|
||||
pluginVisibilityCard.reorderPlugin(visibilityDelegateItem.index, newIndex);
|
||||
visibilityDelegateItem.y = visibilityDelegateItem.originalY;
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
x: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: "drag_indicator"
|
||||
size: 18
|
||||
color: Theme.outline
|
||||
opacity: pluginDragArea.containsMouse || pluginDragArea.pressed ? 1 : 0.5
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: !pluginDragArea.pressed && !pluginDragArea.drag.active
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("No launcher plugins installed.")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: pluginVisibilityCard.allLauncherPlugins.length === 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "search"
|
||||
|
||||
@@ -60,6 +60,11 @@ Item {
|
||||
currentIndex: 0
|
||||
selectionMode: "single"
|
||||
checkEnabled: false
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
currentIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +328,7 @@ Item {
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
currentIndex = index;
|
||||
if (powerCategory.currentIndex === 0) {
|
||||
SettingsData.set("acSuspendBehavior", index);
|
||||
} else {
|
||||
@@ -548,7 +554,6 @@ Item {
|
||||
|
||||
DankTextField {
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: modelData.placeholder
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
|
||||
@@ -69,7 +69,6 @@ Item {
|
||||
DankTextField {
|
||||
id: updaterCustomCommand
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "myPkgMngr --sysupdate"
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
@@ -114,7 +113,6 @@ Item {
|
||||
DankTextField {
|
||||
id: updaterTerminalCustomClass
|
||||
width: parent.width
|
||||
height: 48
|
||||
placeholderText: "-T udpClass"
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
|
||||
@@ -61,6 +61,8 @@ StyledRect {
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
visible: root.title !== ""
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Modals.Spotlight
|
||||
import qs.Modals.DankLauncherV2
|
||||
import qs.Services
|
||||
|
||||
Scope {
|
||||
@@ -13,7 +13,7 @@ Scope {
|
||||
property string searchActiveScreen: ""
|
||||
property bool isClosing: false
|
||||
property bool releaseKeyboard: false
|
||||
readonly property bool spotlightModalOpen: PopoutService.spotlightModal?.spotlightOpen ?? false
|
||||
readonly property bool spotlightModalOpen: PopoutService.dankLauncherV2Modal?.spotlightOpen ?? false
|
||||
property bool overlayActive: NiriService.inOverview || searchActive
|
||||
|
||||
function showSpotlight(screenName) {
|
||||
@@ -67,229 +67,220 @@ Scope {
|
||||
hideSpotlight();
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: niriOverlayLoader
|
||||
active: overlayActive || isClosing
|
||||
asynchronous: false
|
||||
Variants {
|
||||
id: overlayVariants
|
||||
model: Quickshell.screens
|
||||
|
||||
sourceComponent: Variants {
|
||||
id: overlayVariants
|
||||
model: Quickshell.screens
|
||||
PanelWindow {
|
||||
id: overlayWindow
|
||||
required property var modelData
|
||||
|
||||
PanelWindow {
|
||||
id: overlayWindow
|
||||
required property var modelData
|
||||
readonly property real dpr: CompositorService.getScreenScale(screen)
|
||||
readonly property bool isActiveScreen: screen.name === NiriService.currentOutput
|
||||
readonly property bool shouldShowSpotlight: niriOverviewScope.searchActive && screen.name === niriOverviewScope.searchActiveScreen && !niriOverviewScope.isClosing
|
||||
readonly property bool isSpotlightScreen: screen.name === niriOverviewScope.searchActiveScreen
|
||||
readonly property bool overlayVisible: NiriService.inOverview || niriOverviewScope.isClosing
|
||||
property bool hasActivePopout: !!PopoutManager.currentPopoutsByScreen[screen.name]
|
||||
property bool hasActiveModal: !!ModalManager.currentModalsByScreen[screen.name]
|
||||
|
||||
readonly property real dpr: CompositorService.getScreenScale(screen)
|
||||
readonly property bool isActiveScreen: screen.name === NiriService.currentOutput
|
||||
readonly property bool shouldShowSpotlight: niriOverviewScope.searchActive && screen.name === niriOverviewScope.searchActiveScreen && !niriOverviewScope.isClosing
|
||||
readonly property bool isSpotlightScreen: screen.name === niriOverviewScope.searchActiveScreen
|
||||
property bool hasActivePopout: !!PopoutManager.currentPopoutsByScreen[screen.name]
|
||||
property bool hasActiveModal: !!ModalManager.currentModalsByScreen[screen.name]
|
||||
Connections {
|
||||
target: PopoutManager
|
||||
function onPopoutChanged() {
|
||||
overlayWindow.hasActivePopout = !!PopoutManager.currentPopoutsByScreen[overlayWindow.screen.name];
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PopoutManager
|
||||
function onPopoutChanged() {
|
||||
overlayWindow.hasActivePopout = !!PopoutManager.currentPopoutsByScreen[overlayWindow.screen.name];
|
||||
Connections {
|
||||
target: ModalManager
|
||||
function onModalChanged() {
|
||||
overlayWindow.hasActiveModal = !!ModalManager.currentModalsByScreen[overlayWindow.screen.name];
|
||||
}
|
||||
}
|
||||
|
||||
screen: modelData
|
||||
visible: true
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.namespace: "dms:niri-overview-spotlight"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (!NiriService.inOverview)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (!isActiveScreen)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (niriOverviewScope.releaseKeyboard)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (hasActivePopout || hasActiveModal)
|
||||
return WlrKeyboardFocus.None;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: overlayVisible && spotlightContainer.visible ? spotlightContainer : null
|
||||
}
|
||||
|
||||
onShouldShowSpotlightChanged: {
|
||||
if (shouldShowSpotlight) {
|
||||
if (launcherContent?.controller) {
|
||||
launcherContent.controller.searchMode = "apps";
|
||||
launcherContent.controller.performSearch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!isActiveScreen)
|
||||
return;
|
||||
Qt.callLater(() => keyboardFocusScope.forceActiveFocus());
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ModalManager
|
||||
function onModalChanged() {
|
||||
overlayWindow.hasActiveModal = !!ModalManager.currentModalsByScreen[overlayWindow.screen.name];
|
||||
}
|
||||
}
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
screen: modelData
|
||||
visible: NiriService.inOverview || niriOverviewScope.isClosing
|
||||
color: "transparent"
|
||||
FocusScope {
|
||||
id: keyboardFocusScope
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
WlrLayershell.namespace: "dms:niri-overview-spotlight"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (!NiriService.inOverview)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (!isActiveScreen)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (niriOverviewScope.releaseKeyboard)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (hasActivePopout || hasActiveModal)
|
||||
return WlrKeyboardFocus.None;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: spotlightContainer.visible ? spotlightContainer : null
|
||||
}
|
||||
|
||||
onShouldShowSpotlightChanged: {
|
||||
if (shouldShowSpotlight) {
|
||||
if (spotlightContent?.appLauncher)
|
||||
spotlightContent.appLauncher.ensureInitialized();
|
||||
Keys.onPressed: event => {
|
||||
if (overlayWindow.shouldShowSpotlight || niriOverviewScope.isClosing)
|
||||
return;
|
||||
}
|
||||
if (!isActiveScreen)
|
||||
return;
|
||||
Qt.callLater(() => keyboardFocusScope.forceActiveFocus());
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: keyboardFocusScope
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (overlayWindow.shouldShowSpotlight || niriOverviewScope.isClosing)
|
||||
return;
|
||||
if ([Qt.Key_Escape, Qt.Key_Return].includes(event.key)) {
|
||||
NiriService.toggleOverview();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Left) {
|
||||
NiriService.moveColumnLeft();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Right) {
|
||||
NiriService.moveColumnRight();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Up) {
|
||||
NiriService.moveWorkspaceUp();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Down) {
|
||||
NiriService.moveWorkspaceDown();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.modifiers & (Qt.ControlModifier | Qt.MetaModifier) || [Qt.Key_Delete, Qt.Key_Backspace].includes(event.key)) {
|
||||
event.accepted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.isAutoRepeat || !event.text)
|
||||
return;
|
||||
if (!spotlightContent?.searchField)
|
||||
return;
|
||||
const trimmedText = event.text.trim();
|
||||
spotlightContent.searchField.text = trimmedText;
|
||||
if (spotlightContent.appLauncher) {
|
||||
spotlightContent.appLauncher.searchQuery = trimmedText;
|
||||
}
|
||||
niriOverviewScope.showSpotlight(overlayWindow.screen.name);
|
||||
Qt.callLater(() => spotlightContent.searchField.forceActiveFocus());
|
||||
if ([Qt.Key_Escape, Qt.Key_Return].includes(event.key)) {
|
||||
NiriService.toggleOverview();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Left) {
|
||||
NiriService.moveColumnLeft();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Right) {
|
||||
NiriService.moveColumnRight();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Up) {
|
||||
NiriService.moveWorkspaceUp();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Down) {
|
||||
NiriService.moveWorkspaceDown();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.modifiers & (Qt.ControlModifier | Qt.MetaModifier) || [Qt.Key_Delete, Qt.Key_Backspace].includes(event.key)) {
|
||||
event.accepted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.isAutoRepeat || !event.text)
|
||||
return;
|
||||
if (!launcherContent?.searchField)
|
||||
return;
|
||||
const trimmedText = event.text.trim();
|
||||
launcherContent.searchField.text = trimmedText;
|
||||
launcherContent.controller.setSearchQuery(trimmedText);
|
||||
niriOverviewScope.showSpotlight(overlayWindow.screen.name);
|
||||
Qt.callLater(() => launcherContent.searchField.forceActiveFocus());
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: spotlightContainer
|
||||
x: Theme.snap((parent.width - width) / 2, overlayWindow.dpr)
|
||||
y: Theme.snap((parent.height - height) / 2, overlayWindow.dpr)
|
||||
|
||||
readonly property int baseWidth: SettingsData.dankLauncherV2Size === "medium" ? 720 : SettingsData.dankLauncherV2Size === "large" ? 860 : 620
|
||||
readonly property int baseHeight: SettingsData.dankLauncherV2Size === "medium" ? 720 : SettingsData.dankLauncherV2Size === "large" ? 860 : 600
|
||||
width: Math.min(baseWidth, overlayWindow.screen.width - 100)
|
||||
height: Math.min(baseHeight, overlayWindow.screen.height - 100)
|
||||
|
||||
readonly property bool animatingOut: niriOverviewScope.isClosing && overlayWindow.isSpotlightScreen
|
||||
|
||||
scale: overlayWindow.shouldShowSpotlight ? 1.0 : 0.96
|
||||
opacity: overlayWindow.shouldShowSpotlight ? 1 : 0
|
||||
visible: overlayWindow.shouldShowSpotlight || animatingOut
|
||||
enabled: overlayWindow.shouldShowSpotlight
|
||||
|
||||
layer.enabled: true
|
||||
layer.smooth: false
|
||||
layer.textureSize: Qt.size(Math.round(width * overlayWindow.dpr), Math.round(height * overlayWindow.dpr))
|
||||
|
||||
Behavior on scale {
|
||||
id: scaleAnimation
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.fast
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
||||
onRunningChanged: {
|
||||
if (running || !spotlightContainer.animatingOut)
|
||||
return;
|
||||
niriOverviewScope.resetState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: spotlightContainer
|
||||
x: Theme.snap((parent.width - width) / 2, overlayWindow.dpr)
|
||||
y: Theme.snap((parent.height - height) / 2, overlayWindow.dpr)
|
||||
width: Theme.px(500, overlayWindow.dpr)
|
||||
height: Theme.px(600, overlayWindow.dpr)
|
||||
|
||||
readonly property bool animatingOut: niriOverviewScope.isClosing && overlayWindow.isSpotlightScreen
|
||||
|
||||
scale: overlayWindow.shouldShowSpotlight ? 1.0 : 0.96
|
||||
opacity: overlayWindow.shouldShowSpotlight ? 1 : 0
|
||||
visible: overlayWindow.shouldShowSpotlight || animatingOut
|
||||
enabled: overlayWindow.shouldShowSpotlight
|
||||
|
||||
layer.enabled: true
|
||||
layer.smooth: false
|
||||
layer.textureSize: Qt.size(Math.round(width * overlayWindow.dpr), Math.round(height * overlayWindow.dpr))
|
||||
|
||||
Behavior on scale {
|
||||
id: scaleAnimation
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
onRunningChanged: {
|
||||
if (running || !spotlightContainer.animatingOut)
|
||||
return;
|
||||
niriOverviewScope.resetState();
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.fast
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
}
|
||||
LauncherContent {
|
||||
id: launcherContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
|
||||
SpotlightContent {
|
||||
id: spotlightContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
usePopupContextMenu: true
|
||||
|
||||
property var fakeParentModal: QtObject {
|
||||
property bool spotlightOpen: spotlightContainer.visible
|
||||
function hide() {
|
||||
if (niriOverviewScope.searchActive) {
|
||||
niriOverviewScope.hideAndReleaseKeyboard();
|
||||
return;
|
||||
}
|
||||
NiriService.toggleOverview();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: spotlightContent.searchField
|
||||
function onTextChanged() {
|
||||
if (spotlightContent.searchField.text.length > 0 || !niriOverviewScope.searchActive)
|
||||
return;
|
||||
property var fakeParentModal: QtObject {
|
||||
property bool spotlightOpen: spotlightContainer.visible
|
||||
property bool isClosing: niriOverviewScope.isClosing
|
||||
function hide() {
|
||||
if (niriOverviewScope.searchActive) {
|
||||
niriOverviewScope.hideSpotlight();
|
||||
return;
|
||||
}
|
||||
NiriService.toggleOverview();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
parentModal = fakeParentModal;
|
||||
Connections {
|
||||
target: launcherContent.searchField
|
||||
function onTextChanged() {
|
||||
if (launcherContent.searchField.text.length > 0 || !niriOverviewScope.searchActive)
|
||||
return;
|
||||
niriOverviewScope.hideSpotlight();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: spotlightContent.appLauncher
|
||||
function onAppLaunched() {
|
||||
niriOverviewScope.releaseKeyboard = true;
|
||||
}
|
||||
}
|
||||
Component.onCompleted: {
|
||||
parentModal = fakeParentModal;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: spotlightContent.fileSearchController
|
||||
function onFileOpened() {
|
||||
niriOverviewScope.releaseKeyboard = true;
|
||||
}
|
||||
Connections {
|
||||
target: launcherContent.controller
|
||||
function onItemExecuted() {
|
||||
niriOverviewScope.releaseKeyboard = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
113
quickshell/PLUGINS/LauncherImageExample/LauncherImageExample.qml
Normal file
113
quickshell/PLUGINS/LauncherImageExample/LauncherImageExample.qml
Normal file
@@ -0,0 +1,113 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Services
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property var pluginService: null
|
||||
property string trigger: "img"
|
||||
|
||||
signal itemsChanged
|
||||
|
||||
readonly property var images: [
|
||||
{
|
||||
name: "DankDash",
|
||||
imageUrl: "https://danklinux.com/img/dankdash.png",
|
||||
comment: "DankMaterialShell Dashboard"
|
||||
},
|
||||
{
|
||||
name: "Control Center",
|
||||
imageUrl: "https://danklinux.com/img/cc.png",
|
||||
comment: "System Control Center"
|
||||
},
|
||||
{
|
||||
name: "Desktop",
|
||||
imageUrl: "https://danklinux.com/img/desktop.png",
|
||||
comment: "Desktop Environment"
|
||||
},
|
||||
{
|
||||
name: "Search",
|
||||
imageUrl: "https://danklinux.com/img/dsearch.png",
|
||||
comment: "Application Search"
|
||||
},
|
||||
{
|
||||
name: "Theme Registry",
|
||||
imageUrl: "https://danklinux.com/img/blog/v1.2/themeregistry.png",
|
||||
comment: "Theme Registry Browser"
|
||||
},
|
||||
{
|
||||
name: "Monitor Settings",
|
||||
imageUrl: "https://danklinux.com/img/blog/v1.2/monitordark.png",
|
||||
comment: "Display Configuration"
|
||||
}
|
||||
]
|
||||
|
||||
function getItems(query) {
|
||||
const lowerQuery = query ? query.toLowerCase().trim() : "";
|
||||
|
||||
if (lowerQuery.length === 0) {
|
||||
return images.map(img => ({
|
||||
name: img.name,
|
||||
icon: "material:image",
|
||||
comment: img.comment,
|
||||
action: "view:" + img.imageUrl,
|
||||
categories: ["Image Gallery"],
|
||||
imageUrl: img.imageUrl
|
||||
}));
|
||||
}
|
||||
|
||||
return images.filter(img => img.name.toLowerCase().includes(lowerQuery) || img.comment.toLowerCase().includes(lowerQuery)).map(img => ({
|
||||
name: img.name,
|
||||
icon: "material:image",
|
||||
comment: img.comment,
|
||||
action: "view:" + img.imageUrl,
|
||||
categories: ["Image Gallery"],
|
||||
imageUrl: img.imageUrl
|
||||
}));
|
||||
}
|
||||
|
||||
function executeItem(item) {
|
||||
if (!item?.action)
|
||||
return;
|
||||
const actionParts = item.action.split(":");
|
||||
const actionType = actionParts[0];
|
||||
const actionData = actionParts.slice(1).join(":");
|
||||
|
||||
if (actionType === "view") {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showInfo("Image Gallery", "Viewing: " + item.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getContextMenuActions(item) {
|
||||
if (!item)
|
||||
return [];
|
||||
return [
|
||||
{
|
||||
icon: "open_in_new",
|
||||
text: "Open in Browser",
|
||||
action: () => {
|
||||
const url = item.imageUrl || "";
|
||||
if (url) {
|
||||
Qt.openUrlExternally(url);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: "content_copy",
|
||||
text: "Copy URL",
|
||||
action: () => {
|
||||
const url = item.imageUrl || "";
|
||||
if (url) {
|
||||
Quickshell.execDetached(["dms", "cl", "copy", url]);
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showInfo("Copied", url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
43
quickshell/PLUGINS/LauncherImageExample/README.md
Normal file
43
quickshell/PLUGINS/LauncherImageExample/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# LauncherImageExample
|
||||
|
||||
Example launcher plugin demonstrating tile mode with URL-based images.
|
||||
|
||||
## Features
|
||||
|
||||
- **Tile Mode**: Uses `viewMode: "tile"` in plugin.json to display results as image tiles
|
||||
- **Enforced View Mode**: Uses `viewModeEnforced: true` to lock the view to tile mode (users cannot change it)
|
||||
- **URL Images**: Demonstrates using `imageUrl` property for remote images
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open the launcher (DankLauncherV2)
|
||||
2. Type `img` to activate the plugin
|
||||
3. Browse DankMaterialShell screenshots in tile view
|
||||
|
||||
## Plugin Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"viewMode": "tile",
|
||||
"viewModeEnforced": true
|
||||
}
|
||||
```
|
||||
|
||||
- `viewMode`: Sets the default view mode ("list", "grid", or "tile")
|
||||
- `viewModeEnforced`: When true, users cannot switch view modes for this plugin
|
||||
|
||||
## Item Data Structure
|
||||
|
||||
To display images in tile mode, set `imageUrl` directly on the item:
|
||||
|
||||
```javascript
|
||||
{
|
||||
name: "Image Title",
|
||||
icon: "material:image",
|
||||
comment: "Image description",
|
||||
categories: ["Category"],
|
||||
imageUrl: "https://example.com/image.png"
|
||||
}
|
||||
```
|
||||
|
||||
The `imageUrl` property supports remote URLs or local files, use `file://` prefix for local files.
|
||||
14
quickshell/PLUGINS/LauncherImageExample/plugin.json
Normal file
14
quickshell/PLUGINS/LauncherImageExample/plugin.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"id": "launcherImageExample",
|
||||
"name": "Image Gallery Example",
|
||||
"description": "Example launcher plugin demonstrating tile mode with images",
|
||||
"version": "1.0.0",
|
||||
"author": "DMS Team",
|
||||
"icon": "photo_library",
|
||||
"type": "launcher",
|
||||
"trigger": "img",
|
||||
"viewMode": "tile",
|
||||
"viewModeEnforced": true,
|
||||
"component": "./LauncherImageExample.qml",
|
||||
"permissions": []
|
||||
}
|
||||
@@ -41,7 +41,7 @@ property var popoutService: null
|
||||
| ------------------ | ------------------------- | ------------------------- | -------------------------------------------------- |
|
||||
| Settings | `openSettings()` | `closeSettings()` | Full settings interface |
|
||||
| Clipboard History | `openClipboardHistory()` | `closeClipboardHistory()` | Clipboard integration |
|
||||
| Spotlight | `openSpotlight()` | `closeSpotlight()` | Command launcher |
|
||||
| Launcher | `openDankLauncherV2()` | `closeDankLauncherV2()` | Command launcher, also has `toggleDankLauncherV2()` |
|
||||
| Power Menu | `openPowerMenu()` | `closePowerMenu()` | Also has `togglePowerMenu()` |
|
||||
| Process List Modal | `showProcessListModal()` | `hideProcessListModal()` | Fullscreen version, has `toggleProcessListModal()` |
|
||||
| Color Picker | `showColorPicker()` | `hideColorPicker()` | Theme color selection |
|
||||
|
||||
@@ -28,20 +28,62 @@ PluginSettings {
|
||||
label: "Popout to Open"
|
||||
description: "Select which popout or modal opens when you click the widget"
|
||||
options: [
|
||||
{label: "Control Center", value: "controlCenter"},
|
||||
{label: "Notification Center", value: "notificationCenter"},
|
||||
{label: "App Drawer", value: "appDrawer"},
|
||||
{label: "Process List", value: "processList"},
|
||||
{label: "DankDash", value: "dankDash"},
|
||||
{label: "Battery Info", value: "battery"},
|
||||
{label: "VPN", value: "vpn"},
|
||||
{label: "System Update", value: "systemUpdate"},
|
||||
{label: "Settings", value: "settings"},
|
||||
{label: "Clipboard History", value: "clipboardHistory"},
|
||||
{label: "Spotlight", value: "spotlight"},
|
||||
{label: "Power Menu", value: "powerMenu"},
|
||||
{label: "Color Picker", value: "colorPicker"},
|
||||
{label: "Notepad", value: "notepad"}
|
||||
{
|
||||
label: "Control Center",
|
||||
value: "controlCenter"
|
||||
},
|
||||
{
|
||||
label: "Notification Center",
|
||||
value: "notificationCenter"
|
||||
},
|
||||
{
|
||||
label: "App Drawer",
|
||||
value: "appDrawer"
|
||||
},
|
||||
{
|
||||
label: "Process List",
|
||||
value: "processList"
|
||||
},
|
||||
{
|
||||
label: "DankDash",
|
||||
value: "dankDash"
|
||||
},
|
||||
{
|
||||
label: "Battery Info",
|
||||
value: "battery"
|
||||
},
|
||||
{
|
||||
label: "VPN",
|
||||
value: "vpn"
|
||||
},
|
||||
{
|
||||
label: "System Update",
|
||||
value: "systemUpdate"
|
||||
},
|
||||
{
|
||||
label: "Settings",
|
||||
value: "settings"
|
||||
},
|
||||
{
|
||||
label: "Clipboard History",
|
||||
value: "clipboardHistory"
|
||||
},
|
||||
{
|
||||
label: "Spotlight",
|
||||
value: "spotlight"
|
||||
},
|
||||
{
|
||||
label: "Power Menu",
|
||||
value: "powerMenu"
|
||||
},
|
||||
{
|
||||
label: "Color Picker",
|
||||
value: "colorPicker"
|
||||
},
|
||||
{
|
||||
label: "Notepad",
|
||||
value: "notepad"
|
||||
}
|
||||
]
|
||||
defaultValue: "controlCenter"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
@@ -13,42 +11,42 @@ PluginComponent {
|
||||
property string selectedPopout: pluginData.selectedPopout || "controlCenter"
|
||||
|
||||
property var popoutActions: ({
|
||||
"controlCenter": (x, y, w, s, scr) => popoutService?.toggleControlCenter(x, y, w, s, scr),
|
||||
"notificationCenter": (x, y, w, s, scr) => popoutService?.toggleNotificationCenter(x, y, w, s, scr),
|
||||
"appDrawer": (x, y, w, s, scr) => popoutService?.toggleAppDrawer(x, y, w, s, scr),
|
||||
"processList": (x, y, w, s, scr) => popoutService?.toggleProcessList(x, y, w, s, scr),
|
||||
"dankDash": (x, y, w, s, scr) => popoutService?.toggleDankDash(0, x, y, w, s, scr),
|
||||
"battery": (x, y, w, s, scr) => popoutService?.toggleBattery(x, y, w, s, scr),
|
||||
"vpn": (x, y, w, s, scr) => popoutService?.toggleVpn(x, y, w, s, scr),
|
||||
"systemUpdate": (x, y, w, s, scr) => popoutService?.toggleSystemUpdate(x, y, w, s, scr),
|
||||
"settings": () => popoutService?.openSettings(),
|
||||
"clipboardHistory": () => popoutService?.openClipboardHistory(),
|
||||
"spotlight": () => popoutService?.openSpotlight(),
|
||||
"powerMenu": () => popoutService?.togglePowerMenu(),
|
||||
"colorPicker": () => popoutService?.showColorPicker(),
|
||||
"notepad": () => popoutService?.toggleNotepad()
|
||||
})
|
||||
"controlCenter": (x, y, w, s, scr) => popoutService?.toggleControlCenter(x, y, w, s, scr),
|
||||
"notificationCenter": (x, y, w, s, scr) => popoutService?.toggleNotificationCenter(x, y, w, s, scr),
|
||||
"appDrawer": (x, y, w, s, scr) => popoutService?.toggleAppDrawer(x, y, w, s, scr),
|
||||
"processList": (x, y, w, s, scr) => popoutService?.toggleProcessList(x, y, w, s, scr),
|
||||
"dankDash": (x, y, w, s, scr) => popoutService?.toggleDankDash(0, x, y, w, s, scr),
|
||||
"battery": (x, y, w, s, scr) => popoutService?.toggleBattery(x, y, w, s, scr),
|
||||
"vpn": (x, y, w, s, scr) => popoutService?.toggleVpn(x, y, w, s, scr),
|
||||
"systemUpdate": (x, y, w, s, scr) => popoutService?.toggleSystemUpdate(x, y, w, s, scr),
|
||||
"settings": () => popoutService?.openSettings(),
|
||||
"clipboardHistory": () => popoutService?.openClipboardHistory(),
|
||||
"spotlight": () => popoutService?.toggleDankLauncherV2(),
|
||||
"powerMenu": () => popoutService?.togglePowerMenu(),
|
||||
"colorPicker": () => popoutService?.showColorPicker(),
|
||||
"notepad": () => popoutService?.toggleNotepad()
|
||||
})
|
||||
|
||||
property var popoutNames: ({
|
||||
"controlCenter": "Control Center",
|
||||
"notificationCenter": "Notification Center",
|
||||
"appDrawer": "App Drawer",
|
||||
"processList": "Process List",
|
||||
"dankDash": "DankDash",
|
||||
"battery": "Battery Info",
|
||||
"vpn": "VPN",
|
||||
"systemUpdate": "System Update",
|
||||
"settings": "Settings",
|
||||
"clipboardHistory": "Clipboard",
|
||||
"spotlight": "Spotlight",
|
||||
"powerMenu": "Power Menu",
|
||||
"colorPicker": "Color Picker",
|
||||
"notepad": "Notepad"
|
||||
})
|
||||
"controlCenter": "Control Center",
|
||||
"notificationCenter": "Notification Center",
|
||||
"appDrawer": "App Drawer",
|
||||
"processList": "Process List",
|
||||
"dankDash": "DankDash",
|
||||
"battery": "Battery Info",
|
||||
"vpn": "VPN",
|
||||
"systemUpdate": "System Update",
|
||||
"settings": "Settings",
|
||||
"clipboardHistory": "Clipboard",
|
||||
"spotlight": "Spotlight",
|
||||
"powerMenu": "Power Menu",
|
||||
"colorPicker": "Color Picker",
|
||||
"notepad": "Notepad"
|
||||
})
|
||||
|
||||
pillClickAction: (x, y, width, section, screen) => {
|
||||
if (popoutActions[selectedPopout]) {
|
||||
popoutActions[selectedPopout](x, y, width, section, screen)
|
||||
popoutActions[selectedPopout](x, y, width, section, screen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,10 +84,11 @@ popoutService.openClipboardHistory()
|
||||
popoutService.closeClipboardHistory()
|
||||
```
|
||||
|
||||
#### Spotlight Modal
|
||||
#### Launcher Modal
|
||||
```qml
|
||||
popoutService.openSpotlight()
|
||||
popoutService.closeSpotlight()
|
||||
popoutService.openDankLauncherV2()
|
||||
popoutService.closeDankLauncherV2()
|
||||
popoutService.toggleDankLauncherV2()
|
||||
```
|
||||
|
||||
#### Power Menu Modal
|
||||
|
||||
@@ -13,6 +13,12 @@ Singleton {
|
||||
property var _cachedVisibleApps: null
|
||||
property var _hiddenAppsSet: new Set()
|
||||
|
||||
property var _transformCache: ({})
|
||||
property var _cachedDefaultSections: []
|
||||
property var _cachedDefaultFlatModel: []
|
||||
property bool _defaultCacheValid: false
|
||||
property int cacheVersion: 0
|
||||
|
||||
readonly property int maxResults: 10
|
||||
readonly property int frecencySampleSize: 10
|
||||
|
||||
@@ -43,6 +49,52 @@ Singleton {
|
||||
applications = DesktopEntries.applications.values;
|
||||
_cachedCategories = null;
|
||||
_cachedVisibleApps = null;
|
||||
invalidateLauncherCache();
|
||||
}
|
||||
|
||||
function invalidateLauncherCache() {
|
||||
_transformCache = {};
|
||||
_defaultCacheValid = false;
|
||||
_cachedDefaultSections = [];
|
||||
_cachedDefaultFlatModel = [];
|
||||
cacheVersion++;
|
||||
}
|
||||
|
||||
function getOrTransformApp(app, transformFn) {
|
||||
const id = app.id || app.execString || app.exec || "";
|
||||
if (!id)
|
||||
return transformFn(app);
|
||||
const cached = _transformCache[id];
|
||||
if (cached) {
|
||||
const currentIcon = app.icon || "";
|
||||
const cachedSourceIcon = cached._sourceIcon || "";
|
||||
if (currentIcon === cachedSourceIcon)
|
||||
return cached;
|
||||
}
|
||||
const transformed = transformFn(app);
|
||||
transformed._sourceIcon = app.icon || "";
|
||||
_transformCache[id] = transformed;
|
||||
return transformed;
|
||||
}
|
||||
|
||||
function getCachedDefaultSections() {
|
||||
if (!_defaultCacheValid)
|
||||
return null;
|
||||
return _cachedDefaultSections;
|
||||
}
|
||||
|
||||
function setCachedDefaultSections(sections, flatModel) {
|
||||
_cachedDefaultSections = sections.map(function (s) {
|
||||
return Object.assign({}, s, {
|
||||
items: s.items ? s.items.slice() : []
|
||||
});
|
||||
});
|
||||
_cachedDefaultFlatModel = flatModel.slice();
|
||||
_defaultCacheValid = true;
|
||||
}
|
||||
|
||||
function isCacheValid() {
|
||||
return _defaultCacheValid;
|
||||
}
|
||||
|
||||
function _rebuildHiddenSet() {
|
||||
@@ -68,9 +120,18 @@ Singleton {
|
||||
target: SessionData
|
||||
function onHiddenAppsChanged() {
|
||||
root._rebuildHiddenSet();
|
||||
root.invalidateLauncherCache();
|
||||
}
|
||||
function onAppOverridesChanged() {
|
||||
root._cachedVisibleApps = null;
|
||||
root.invalidateLauncherCache();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AppUsageHistoryData
|
||||
function onAppUsageRankingChanged() {
|
||||
root.invalidateLauncherCache();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ Singleton {
|
||||
|
||||
function setWorkspaces(newMap) {
|
||||
root.workspaces = newMap;
|
||||
allWorkspaces = Object.values(newMap).sort((a, b) => a.idx - b.idx);
|
||||
root.allWorkspaces = Object.values(newMap).sort((a, b) => a.idx - b.idx);
|
||||
}
|
||||
|
||||
Component.onCompleted: fetchOutputs()
|
||||
@@ -863,9 +863,13 @@ Singleton {
|
||||
return currentOutputWorkspaces.map(w => w.idx + 1);
|
||||
}
|
||||
|
||||
function getCurrentOutputWorkspaces() {
|
||||
return currentOutputWorkspaces.slice();
|
||||
}
|
||||
|
||||
function getCurrentWorkspaceNumber() {
|
||||
if (focusedWorkspaceIndex >= 0 && focusedWorkspaceIndex < allWorkspaces.length) {
|
||||
return allWorkspaces[focusedWorkspaceIndex].idx + 1;
|
||||
return allWorkspaces[focusedWorkspaceIndex].idx;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -293,7 +293,6 @@ Singleton {
|
||||
pluginDaemonComponents = newDaemons;
|
||||
} else if (isLauncher) {
|
||||
const instance = comp.createObject(root, {
|
||||
"pluginId": pluginId,
|
||||
"pluginService": root
|
||||
});
|
||||
if (!instance) {
|
||||
@@ -702,6 +701,17 @@ Singleton {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
function getPluginViewPreference(pluginId) {
|
||||
const plugin = availablePlugins[pluginId];
|
||||
if (!plugin)
|
||||
return null;
|
||||
|
||||
return {
|
||||
mode: plugin.viewMode || null,
|
||||
enforced: plugin.viewModeEnforced === true
|
||||
};
|
||||
}
|
||||
|
||||
function getGlobalVar(pluginId, varName, defaultValue) {
|
||||
if (globalVars[pluginId] && varName in globalVars[pluginId]) {
|
||||
return globalVars[pluginId][varName];
|
||||
|
||||
@@ -20,7 +20,8 @@ Singleton {
|
||||
property var settingsModal: null
|
||||
property var settingsModalLoader: null
|
||||
property var clipboardHistoryModal: null
|
||||
property var spotlightModal: null
|
||||
property var dankLauncherV2Modal: null
|
||||
property var dankLauncherV2ModalLoader: null
|
||||
property var powerMenuModal: null
|
||||
property var processListModal: null
|
||||
property var processListModalLoader: null
|
||||
@@ -353,12 +354,91 @@ Singleton {
|
||||
clipboardHistoryModal?.close();
|
||||
}
|
||||
|
||||
function openSpotlight() {
|
||||
spotlightModal?.show();
|
||||
property bool _dankLauncherV2WantsOpen: false
|
||||
property bool _dankLauncherV2WantsToggle: false
|
||||
property string _dankLauncherV2PendingQuery: ""
|
||||
property string _dankLauncherV2PendingMode: ""
|
||||
|
||||
function openDankLauncherV2() {
|
||||
if (dankLauncherV2Modal) {
|
||||
dankLauncherV2Modal.show();
|
||||
} else if (dankLauncherV2ModalLoader) {
|
||||
_dankLauncherV2WantsOpen = true;
|
||||
_dankLauncherV2WantsToggle = false;
|
||||
dankLauncherV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function closeSpotlight() {
|
||||
spotlightModal?.close();
|
||||
function openDankLauncherV2WithQuery(query: string) {
|
||||
if (dankLauncherV2Modal) {
|
||||
dankLauncherV2Modal.showWithQuery(query);
|
||||
} else if (dankLauncherV2ModalLoader) {
|
||||
_dankLauncherV2PendingQuery = query;
|
||||
_dankLauncherV2WantsOpen = true;
|
||||
_dankLauncherV2WantsToggle = false;
|
||||
dankLauncherV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function openDankLauncherV2WithMode(mode: string) {
|
||||
if (dankLauncherV2Modal) {
|
||||
dankLauncherV2Modal.showWithMode(mode);
|
||||
} else if (dankLauncherV2ModalLoader) {
|
||||
_dankLauncherV2PendingMode = mode;
|
||||
_dankLauncherV2WantsOpen = true;
|
||||
_dankLauncherV2WantsToggle = false;
|
||||
dankLauncherV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function closeDankLauncherV2() {
|
||||
dankLauncherV2Modal?.hide();
|
||||
}
|
||||
|
||||
function toggleDankLauncherV2() {
|
||||
if (dankLauncherV2Modal) {
|
||||
dankLauncherV2Modal.toggle();
|
||||
} else if (dankLauncherV2ModalLoader) {
|
||||
_dankLauncherV2WantsToggle = true;
|
||||
_dankLauncherV2WantsOpen = false;
|
||||
dankLauncherV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDankLauncherV2WithMode(mode: string) {
|
||||
if (dankLauncherV2Modal) {
|
||||
dankLauncherV2Modal.toggleWithMode(mode);
|
||||
} else if (dankLauncherV2ModalLoader) {
|
||||
_dankLauncherV2PendingMode = mode;
|
||||
_dankLauncherV2WantsToggle = true;
|
||||
_dankLauncherV2WantsOpen = false;
|
||||
dankLauncherV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function _onDankLauncherV2ModalLoaded() {
|
||||
if (_dankLauncherV2WantsOpen) {
|
||||
_dankLauncherV2WantsOpen = false;
|
||||
if (_dankLauncherV2PendingQuery) {
|
||||
dankLauncherV2Modal?.showWithQuery(_dankLauncherV2PendingQuery);
|
||||
_dankLauncherV2PendingQuery = "";
|
||||
} else if (_dankLauncherV2PendingMode) {
|
||||
dankLauncherV2Modal?.showWithMode(_dankLauncherV2PendingMode);
|
||||
_dankLauncherV2PendingMode = "";
|
||||
} else {
|
||||
dankLauncherV2Modal?.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_dankLauncherV2WantsToggle) {
|
||||
_dankLauncherV2WantsToggle = false;
|
||||
if (_dankLauncherV2PendingMode) {
|
||||
dankLauncherV2Modal?.toggleWithMode(_dankLauncherV2PendingMode);
|
||||
_dankLauncherV2PendingMode = "";
|
||||
} else {
|
||||
dankLauncherV2Modal?.toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openPowerMenu() {
|
||||
|
||||
@@ -26,8 +26,10 @@ Item {
|
||||
readonly property bool isUnicode: iconValue.startsWith("unicode:")
|
||||
readonly property bool isSvgCorner: iconValue.startsWith("svg+corner:")
|
||||
readonly property bool isSvg: !isSvgCorner && iconValue.startsWith("svg:")
|
||||
readonly property bool isImage: iconValue.startsWith("image:")
|
||||
readonly property string materialName: isMaterial ? iconValue.substring(9) : ""
|
||||
readonly property string unicodeChar: isUnicode ? iconValue.substring(8) : ""
|
||||
readonly property string imagePath: isImage ? iconValue.substring(6) : ""
|
||||
readonly property string svgSource: {
|
||||
if (isSvgCorner) {
|
||||
const parts = iconValue.substring(11).split("|");
|
||||
@@ -38,7 +40,7 @@ Item {
|
||||
return "";
|
||||
}
|
||||
readonly property string svgCornerIcon: isSvgCorner ? (iconValue.substring(11).split("|")[1] || "") : ""
|
||||
readonly property string iconPath: isMaterial || isUnicode || isSvg || isSvgCorner ? "" : Quickshell.iconPath(iconValue, true) || DesktopService.resolveIconPath(iconValue)
|
||||
readonly property string iconPath: isMaterial || isUnicode || isSvg || isSvgCorner || isImage ? "" : Quickshell.iconPath(iconValue, true) || DesktopService.resolveIconPath(iconValue)
|
||||
|
||||
visible: iconValue !== undefined && iconValue !== ""
|
||||
|
||||
@@ -66,14 +68,23 @@ Item {
|
||||
visible: root.isSvg || root.isSvgCorner
|
||||
}
|
||||
|
||||
CachingImage {
|
||||
id: cachingImg
|
||||
anchors.fill: parent
|
||||
imagePath: root.imagePath
|
||||
maxCacheSize: root.iconSize * 2
|
||||
visible: root.isImage && status === Image.Ready
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: iconImg
|
||||
|
||||
anchors.fill: parent
|
||||
source: root.iconPath
|
||||
smooth: true
|
||||
backer.sourceSize: Qt.size(root.iconSize, root.iconSize)
|
||||
mipmap: true
|
||||
asynchronous: true
|
||||
visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && root.iconPath !== "" && status === Image.Ready
|
||||
visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && !root.isImage && root.iconPath !== "" && status === Image.Ready
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -84,7 +95,7 @@ Item {
|
||||
anchors.rightMargin: root.fallbackRightMargin
|
||||
anchors.topMargin: root.fallbackTopMargin
|
||||
anchors.bottomMargin: root.fallbackBottomMargin
|
||||
visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && (root.iconPath === "" || iconImg.status !== Image.Ready)
|
||||
visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && !root.isImage && (root.iconPath === "" || iconImg.status !== Image.Ready)
|
||||
color: root.fallbackBackgroundColor
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
|
||||
@@ -7,6 +7,17 @@ Image {
|
||||
property string imagePath: ""
|
||||
property int maxCacheSize: 512
|
||||
|
||||
readonly property bool isRemoteUrl: imagePath.startsWith("http://") || imagePath.startsWith("https://")
|
||||
readonly property string normalizedPath: {
|
||||
if (!imagePath)
|
||||
return "";
|
||||
if (isRemoteUrl)
|
||||
return imagePath;
|
||||
if (imagePath.startsWith("file://"))
|
||||
return imagePath.substring(7);
|
||||
return imagePath;
|
||||
}
|
||||
|
||||
function djb2Hash(str) {
|
||||
if (!str)
|
||||
return "";
|
||||
@@ -18,9 +29,15 @@ Image {
|
||||
return hash.toString(16).padStart(8, '0');
|
||||
}
|
||||
|
||||
readonly property string imageHash: imagePath ? djb2Hash(imagePath) : ""
|
||||
readonly property string cachePath: imageHash ? `${Paths.stringify(Paths.imagecache)}/${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : ""
|
||||
readonly property string encodedImagePath: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
|
||||
readonly property string imageHash: normalizedPath ? djb2Hash(normalizedPath) : ""
|
||||
readonly property string cachePath: imageHash && !isRemoteUrl ? `${Paths.stringify(Paths.imagecache)}/${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : ""
|
||||
readonly property string encodedImagePath: {
|
||||
if (!normalizedPath)
|
||||
return "";
|
||||
if (isRemoteUrl)
|
||||
return normalizedPath;
|
||||
return "file://" + normalizedPath.split('/').map(s => encodeURIComponent(s)).join('/');
|
||||
}
|
||||
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
@@ -33,10 +50,14 @@ Image {
|
||||
source = "";
|
||||
return;
|
||||
}
|
||||
if (isRemoteUrl) {
|
||||
source = imagePath;
|
||||
return;
|
||||
}
|
||||
Paths.mkdir(Paths.imagecache);
|
||||
const hash = djb2Hash(imagePath);
|
||||
const hash = djb2Hash(normalizedPath);
|
||||
const cPath = hash ? `${Paths.stringify(Paths.imagecache)}/${hash}@${maxCacheSize}x${maxCacheSize}.png` : "";
|
||||
const encoded = "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/');
|
||||
const encoded = "file://" + normalizedPath.split('/').map(s => encodeURIComponent(s)).join('/');
|
||||
source = cPath || encoded;
|
||||
}
|
||||
|
||||
@@ -45,7 +66,7 @@ Image {
|
||||
source = encodedImagePath;
|
||||
return;
|
||||
}
|
||||
if (source != encodedImagePath || status !== Image.Ready || !cachePath)
|
||||
if (isRemoteUrl || source != encodedImagePath || status !== Image.Ready || !cachePath)
|
||||
return;
|
||||
Paths.mkdir(Paths.imagecache);
|
||||
const grabPath = cachePath;
|
||||
|
||||
@@ -8,17 +8,9 @@ StyledRect {
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
KeyNavigation.tab: keyNavigationTab
|
||||
KeyNavigation.backtab: keyNavigationBacktab
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus) {
|
||||
textInput.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
property alias text: textInput.text
|
||||
property string placeholderText: ""
|
||||
property alias font: textInput.font
|
||||
@@ -43,15 +35,15 @@ StyledRect {
|
||||
property real cornerRadius: Theme.cornerRadius
|
||||
readonly property real leftPadding: Theme.spacingM + (leftIconName ? leftIconSize + Theme.spacingM : 0)
|
||||
readonly property real rightPadding: {
|
||||
let p = Theme.spacingM;
|
||||
let p = Theme.spacingS;
|
||||
if (showPasswordToggle)
|
||||
p += 24 + Theme.spacingS;
|
||||
p += 20 + Theme.spacingXS;
|
||||
if (showClearButton && text.length > 0)
|
||||
p += 24 + Theme.spacingS;
|
||||
p += 20 + Theme.spacingXS;
|
||||
return p;
|
||||
}
|
||||
property real topPadding: Theme.spacingM
|
||||
property real bottomPadding: Theme.spacingM
|
||||
property real topPadding: Theme.spacingS
|
||||
property real bottomPadding: Theme.spacingS
|
||||
property bool ignoreLeftRightKeys: false
|
||||
property bool ignoreUpDownKeys: false
|
||||
property bool ignoreTabKeys: false
|
||||
@@ -84,7 +76,7 @@ StyledRect {
|
||||
}
|
||||
|
||||
width: 200
|
||||
height: Math.round(Theme.fontSizeMedium * 3.4)
|
||||
height: Math.round(Theme.fontSizeMedium * 3)
|
||||
radius: cornerRadius
|
||||
color: backgroundColor
|
||||
border.color: textInput.activeFocus ? focusedBorderColor : normalBorderColor
|
||||
@@ -172,7 +164,7 @@ StyledRect {
|
||||
id: rightButtonsRow
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
visible: showPasswordToggle || (showClearButton && text.length > 0)
|
||||
@@ -180,16 +172,16 @@ StyledRect {
|
||||
StyledRect {
|
||||
id: passwordToggleButton
|
||||
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
color: passwordToggleArea.containsMouse ? Theme.outlineStrong : "transparent"
|
||||
visible: showPasswordToggle
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: passwordVisible ? "visibility_off" : "visibility"
|
||||
size: 16
|
||||
size: 14
|
||||
color: passwordToggleArea.containsMouse ? Theme.outline : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
@@ -206,16 +198,16 @@ StyledRect {
|
||||
StyledRect {
|
||||
id: clearButton
|
||||
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
color: clearArea.containsMouse ? Theme.outlineStrong : "transparent"
|
||||
visible: showClearButton && text.length > 0
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "close"
|
||||
size: 16
|
||||
size: 14
|
||||
color: clearArea.containsMouse ? Theme.outline : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
|
||||
@@ -1109,6 +1109,7 @@
|
||||
"tabIndex": 5,
|
||||
"category": "Dock",
|
||||
"keywords": [
|
||||
"always",
|
||||
"area",
|
||||
"auto",
|
||||
"autohide",
|
||||
@@ -1122,7 +1123,7 @@
|
||||
"reveal",
|
||||
"taskbar"
|
||||
],
|
||||
"description": "Hide the dock when not in use and reveal it when hovering near the dock area"
|
||||
"description": "Always hide the dock and reveal it when hovering near the dock area"
|
||||
},
|
||||
{
|
||||
"section": "dockBehavior",
|
||||
@@ -1276,6 +1277,29 @@
|
||||
"taskbar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": "dockSmartAutoHide",
|
||||
"label": "Intelligent Auto-hide",
|
||||
"tabIndex": 5,
|
||||
"category": "Dock",
|
||||
"keywords": [
|
||||
"auto",
|
||||
"autohide",
|
||||
"dock",
|
||||
"floating",
|
||||
"hide",
|
||||
"intelligent",
|
||||
"launcher bar",
|
||||
"overlap",
|
||||
"panel",
|
||||
"show",
|
||||
"smart",
|
||||
"taskbar",
|
||||
"windows"
|
||||
],
|
||||
"description": "Show dock when floating windows don",
|
||||
"conditionKey": "isNiri"
|
||||
},
|
||||
{
|
||||
"section": "dockIsolateDisplays",
|
||||
"label": "Isolate Displays",
|
||||
@@ -1428,6 +1452,58 @@
|
||||
"icon": "computer",
|
||||
"conditionKey": "cupsAvailable"
|
||||
},
|
||||
{
|
||||
"section": "appOverrides",
|
||||
"label": "App Customizations",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"app",
|
||||
"customizations",
|
||||
"drawer",
|
||||
"launcher",
|
||||
"menu",
|
||||
"start"
|
||||
],
|
||||
"icon": "edit"
|
||||
},
|
||||
{
|
||||
"section": "dankLauncherV2Appearance",
|
||||
"label": "Appearance",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"appearance",
|
||||
"bottom",
|
||||
"drawer",
|
||||
"footer",
|
||||
"hints",
|
||||
"keyboard",
|
||||
"launcher",
|
||||
"menu",
|
||||
"mode",
|
||||
"shortcuts",
|
||||
"show",
|
||||
"start",
|
||||
"tabs"
|
||||
],
|
||||
"icon": "tune",
|
||||
"description": "Show mode tabs and keyboard hints at the bottom."
|
||||
},
|
||||
{
|
||||
"section": "dankLauncherV2BorderEnabled",
|
||||
"label": "Border",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"border",
|
||||
"drawer",
|
||||
"launcher",
|
||||
"menu",
|
||||
"outline",
|
||||
"start"
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": "launcherLogoBrightness",
|
||||
"label": "Brightness",
|
||||
@@ -1520,6 +1596,21 @@
|
||||
],
|
||||
"description": "Adjust the number of columns in grid view mode."
|
||||
},
|
||||
{
|
||||
"section": "hiddenApps",
|
||||
"label": "Hidden Apps",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"apps",
|
||||
"drawer",
|
||||
"hidden",
|
||||
"launcher",
|
||||
"menu",
|
||||
"start"
|
||||
],
|
||||
"icon": "visibility_off"
|
||||
},
|
||||
{
|
||||
"section": "launcherLogoColorInvertOnMode",
|
||||
"label": "Invert on mode change",
|
||||
@@ -1629,6 +1720,68 @@
|
||||
],
|
||||
"icon": "history"
|
||||
},
|
||||
{
|
||||
"section": "searchAppActions",
|
||||
"label": "Search App Actions",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"actions",
|
||||
"app",
|
||||
"desktop",
|
||||
"drawer",
|
||||
"include",
|
||||
"launcher",
|
||||
"menu",
|
||||
"results",
|
||||
"search",
|
||||
"shortcuts",
|
||||
"start"
|
||||
],
|
||||
"description": "Include desktop actions (shortcuts) in search results."
|
||||
},
|
||||
{
|
||||
"section": "searchOptions",
|
||||
"label": "Search Options",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"actions",
|
||||
"desktop",
|
||||
"drawer",
|
||||
"include",
|
||||
"launcher",
|
||||
"menu",
|
||||
"options",
|
||||
"results",
|
||||
"search",
|
||||
"shortcuts",
|
||||
"start"
|
||||
],
|
||||
"icon": "search",
|
||||
"description": "Include desktop actions (shortcuts) in search results."
|
||||
},
|
||||
{
|
||||
"section": "dankLauncherV2ShowFooter",
|
||||
"label": "Show Footer",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"bottom",
|
||||
"drawer",
|
||||
"footer",
|
||||
"hints",
|
||||
"keyboard",
|
||||
"launcher",
|
||||
"menu",
|
||||
"mode",
|
||||
"shortcuts",
|
||||
"show",
|
||||
"start",
|
||||
"tabs"
|
||||
],
|
||||
"description": "Show mode tabs and keyboard hints at the bottom."
|
||||
},
|
||||
{
|
||||
"section": "launcherLogoSizeOffset",
|
||||
"label": "Size Offset",
|
||||
@@ -1692,6 +1845,20 @@
|
||||
"icon": "sort_by_alpha",
|
||||
"description": "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency."
|
||||
},
|
||||
{
|
||||
"section": "dankLauncherV2BorderThickness",
|
||||
"label": "Thickness",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"border",
|
||||
"drawer",
|
||||
"launcher",
|
||||
"menu",
|
||||
"start",
|
||||
"thickness"
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": "matugenTemplateAlacritty",
|
||||
"label": "Alacritty",
|
||||
@@ -3369,6 +3536,40 @@
|
||||
],
|
||||
"icon": "security"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenPowerOffMonitorsOnLock",
|
||||
"label": "Power off monitors on lock",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock Screen",
|
||||
"keywords": [
|
||||
"activates",
|
||||
"display",
|
||||
"displays",
|
||||
"dpms",
|
||||
"hibernate",
|
||||
"immediately",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"monitor",
|
||||
"monitors",
|
||||
"off",
|
||||
"output",
|
||||
"outputs",
|
||||
"password",
|
||||
"power",
|
||||
"reboot",
|
||||
"restart",
|
||||
"screen",
|
||||
"screens",
|
||||
"security",
|
||||
"shutdown",
|
||||
"sleep",
|
||||
"suspend",
|
||||
"turn"
|
||||
],
|
||||
"description": "Turn off all displays immediately when the lock screen activates"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowPasswordField",
|
||||
"label": "Show Password Field",
|
||||
@@ -5047,6 +5248,26 @@
|
||||
],
|
||||
"description": "Maximum size per clipboard entry"
|
||||
},
|
||||
{
|
||||
"section": "maxPinned",
|
||||
"label": "Maximum Pinned Entries",
|
||||
"tabIndex": 23,
|
||||
"category": "System",
|
||||
"keywords": [
|
||||
"clipboard",
|
||||
"entries",
|
||||
"limit",
|
||||
"linux",
|
||||
"max",
|
||||
"maximum",
|
||||
"number",
|
||||
"os",
|
||||
"pinned",
|
||||
"saved",
|
||||
"system"
|
||||
],
|
||||
"description": "Maximum number of entries that can be saved"
|
||||
},
|
||||
{
|
||||
"section": "_tab_24",
|
||||
"label": "Displays",
|
||||
|
||||
Reference in New Issue
Block a user