mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-31 00:42:50 -05:00
Compare commits
11 Commits
0ea0602aec
...
f236706d6a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f236706d6a | ||
|
|
b097700591 | ||
|
|
50b112c9d6 | ||
|
|
c2f478b088 | ||
|
|
dccbb137d7 | ||
|
|
90f9940dbd | ||
|
|
f3f7cc9077 | ||
|
|
c331e2f39e | ||
|
|
1c7ebc4323 | ||
|
|
5f5427266f | ||
|
|
33e655becd |
2
Makefile
2
Makefile
@@ -81,7 +81,7 @@ install: build install-bin install-shell install-completions install-systemd ins
|
|||||||
@echo ""
|
@echo ""
|
||||||
@echo "Installation complete!"
|
@echo "Installation complete!"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "=== The DMS Team! ==="
|
@echo "=== Cheers, the DMS Team! ==="
|
||||||
|
|
||||||
# Uninstallation targets
|
# Uninstallation targets
|
||||||
uninstall-bin:
|
uninstall-bin:
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ After=graphical-session.target
|
|||||||
Requisite=graphical-session.target
|
Requisite=graphical-session.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=dbus
|
||||||
|
BusName=org.freedesktop.Notifications
|
||||||
ExecStart=/usr/bin/dms run --session
|
ExecStart=/usr/bin/dms run --session
|
||||||
ExecReload=/usr/bin/pkill -USR1 -x dms
|
ExecReload=/usr/bin/pkill -USR1 -x dms
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||||
@@ -53,14 +54,29 @@ func (n *NiriProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
|||||||
n.parsed = true
|
n.parsed = true
|
||||||
|
|
||||||
categorizedBinds := make(map[string][]keybinds.Keybind)
|
categorizedBinds := make(map[string][]keybinds.Keybind)
|
||||||
n.convertSection(result.Section, "", categorizedBinds)
|
n.convertSection(result.Section, "", categorizedBinds, result.ConflictingConfigs)
|
||||||
|
|
||||||
return &keybinds.CheatSheet{
|
sheet := &keybinds.CheatSheet{
|
||||||
Title: "Niri Keybinds",
|
Title: "Niri Keybinds",
|
||||||
Provider: n.Name(),
|
Provider: n.Name(),
|
||||||
Binds: categorizedBinds,
|
Binds: categorizedBinds,
|
||||||
DMSBindsIncluded: result.DMSBindsIncluded,
|
DMSBindsIncluded: result.DMSBindsIncluded,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
if result.DMSStatus != nil {
|
||||||
|
sheet.DMSStatus = &keybinds.DMSBindsStatus{
|
||||||
|
Exists: result.DMSStatus.Exists,
|
||||||
|
Included: result.DMSStatus.Included,
|
||||||
|
IncludePosition: result.DMSStatus.IncludePosition,
|
||||||
|
TotalIncludes: result.DMSStatus.TotalIncludes,
|
||||||
|
BindsAfterDMS: result.DMSStatus.BindsAfterDMS,
|
||||||
|
Effective: result.DMSStatus.Effective,
|
||||||
|
OverriddenBy: result.DMSStatus.OverriddenBy,
|
||||||
|
StatusMessage: result.DMSStatus.StatusMessage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sheet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NiriProvider) HasDMSBindsIncluded() bool {
|
func (n *NiriProvider) HasDMSBindsIncluded() bool {
|
||||||
@@ -78,7 +94,7 @@ func (n *NiriProvider) HasDMSBindsIncluded() bool {
|
|||||||
return n.dmsBindsIncluded
|
return n.dmsBindsIncluded
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NiriProvider) convertSection(section *NiriSection, subcategory string, categorizedBinds map[string][]keybinds.Keybind) {
|
func (n *NiriProvider) convertSection(section *NiriSection, subcategory string, categorizedBinds map[string][]keybinds.Keybind, conflicts map[string]*NiriKeyBinding) {
|
||||||
currentSubcat := subcategory
|
currentSubcat := subcategory
|
||||||
if section.Name != "" {
|
if section.Name != "" {
|
||||||
currentSubcat = section.Name
|
currentSubcat = section.Name
|
||||||
@@ -86,12 +102,12 @@ func (n *NiriProvider) convertSection(section *NiriSection, subcategory string,
|
|||||||
|
|
||||||
for _, kb := range section.Keybinds {
|
for _, kb := range section.Keybinds {
|
||||||
category := n.categorizeByAction(kb.Action)
|
category := n.categorizeByAction(kb.Action)
|
||||||
bind := n.convertKeybind(&kb, currentSubcat)
|
bind := n.convertKeybind(&kb, currentSubcat, conflicts)
|
||||||
categorizedBinds[category] = append(categorizedBinds[category], bind)
|
categorizedBinds[category] = append(categorizedBinds[category], bind)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, child := range section.Children {
|
for _, child := range section.Children {
|
||||||
n.convertSection(&child, currentSubcat, categorizedBinds)
|
n.convertSection(&child, currentSubcat, categorizedBinds, conflicts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,21 +144,35 @@ func (n *NiriProvider) categorizeByAction(action string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NiriProvider) convertKeybind(kb *NiriKeyBinding, subcategory string) keybinds.Keybind {
|
func (n *NiriProvider) convertKeybind(kb *NiriKeyBinding, subcategory string, conflicts map[string]*NiriKeyBinding) keybinds.Keybind {
|
||||||
rawAction := n.formatRawAction(kb.Action, kb.Args)
|
rawAction := n.formatRawAction(kb.Action, kb.Args)
|
||||||
|
keyStr := n.formatKey(kb)
|
||||||
|
|
||||||
source := "config"
|
source := "config"
|
||||||
if strings.Contains(kb.Source, "dms/binds.kdl") {
|
if strings.Contains(kb.Source, "dms/binds.kdl") {
|
||||||
source = "dms"
|
source = "dms"
|
||||||
}
|
}
|
||||||
|
|
||||||
return keybinds.Keybind{
|
bind := keybinds.Keybind{
|
||||||
Key: n.formatKey(kb),
|
Key: keyStr,
|
||||||
Description: kb.Description,
|
Description: kb.Description,
|
||||||
Action: rawAction,
|
Action: rawAction,
|
||||||
Subcategory: subcategory,
|
Subcategory: subcategory,
|
||||||
Source: source,
|
Source: source,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if source == "dms" && conflicts != nil {
|
||||||
|
if conflictKb, ok := conflicts[keyStr]; ok {
|
||||||
|
bind.Conflict = &keybinds.Keybind{
|
||||||
|
Key: keyStr,
|
||||||
|
Description: conflictKb.Description,
|
||||||
|
Action: n.formatRawAction(conflictKb.Action, conflictKb.Args),
|
||||||
|
Source: "config",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bind
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NiriProvider) formatRawAction(action string, args []string) string {
|
func (n *NiriProvider) formatRawAction(action string, args []string) string {
|
||||||
@@ -386,6 +416,29 @@ func (n *NiriProvider) writeOverrideBinds(binds map[string]*overrideBind) error
|
|||||||
return os.WriteFile(overridePath, []byte(content), 0644)
|
return os.WriteFile(overridePath, []byte(content), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NiriProvider) getBindSortPriority(action string) int {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(action, "spawn") && strings.Contains(action, "dms"):
|
||||||
|
return 0
|
||||||
|
case strings.Contains(action, "workspace"):
|
||||||
|
return 1
|
||||||
|
case strings.Contains(action, "window") || strings.Contains(action, "column") ||
|
||||||
|
strings.Contains(action, "focus") || strings.Contains(action, "move") ||
|
||||||
|
strings.Contains(action, "swap") || strings.Contains(action, "resize"):
|
||||||
|
return 2
|
||||||
|
case strings.HasPrefix(action, "focus-monitor") || strings.Contains(action, "monitor"):
|
||||||
|
return 3
|
||||||
|
case strings.Contains(action, "screenshot"):
|
||||||
|
return 4
|
||||||
|
case action == "quit" || action == "power-off-monitors" || strings.Contains(action, "dpms"):
|
||||||
|
return 5
|
||||||
|
case strings.HasPrefix(action, "spawn"):
|
||||||
|
return 6
|
||||||
|
default:
|
||||||
|
return 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (n *NiriProvider) generateBindsContent(binds map[string]*overrideBind) string {
|
func (n *NiriProvider) generateBindsContent(binds map[string]*overrideBind) string {
|
||||||
if len(binds) == 0 {
|
if len(binds) == 0 {
|
||||||
return "binds {}\n"
|
return "binds {}\n"
|
||||||
@@ -401,6 +454,18 @@ func (n *NiriProvider) generateBindsContent(binds map[string]*overrideBind) stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Slice(regularBinds, func(i, j int) bool {
|
||||||
|
pi, pj := n.getBindSortPriority(regularBinds[i].Action), n.getBindSortPriority(regularBinds[j].Action)
|
||||||
|
if pi != pj {
|
||||||
|
return pi < pj
|
||||||
|
}
|
||||||
|
return regularBinds[i].Key < regularBinds[j].Key
|
||||||
|
})
|
||||||
|
|
||||||
|
sort.Slice(recentWindowsBinds, func(i, j int) bool {
|
||||||
|
return recentWindowsBinds[i].Key < recentWindowsBinds[j].Key
|
||||||
|
})
|
||||||
|
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
|
||||||
sb.WriteString("binds {\n")
|
sb.WriteString("binds {\n")
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ type NiriParser struct {
|
|||||||
bindOrder []string
|
bindOrder []string
|
||||||
currentSource string
|
currentSource string
|
||||||
dmsBindsIncluded bool
|
dmsBindsIncluded bool
|
||||||
|
dmsBindsExists bool
|
||||||
|
includeCount int
|
||||||
|
dmsIncludePos int
|
||||||
|
bindsBeforeDMS int
|
||||||
|
bindsAfterDMS int
|
||||||
|
dmsBindKeys map[string]bool
|
||||||
|
configBindKeys map[string]bool
|
||||||
|
dmsProcessed bool
|
||||||
|
dmsBindMap map[string]*NiriKeyBinding
|
||||||
|
conflictingConfigs map[string]*NiriKeyBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNiriParser(configDir string) *NiriParser {
|
func NewNiriParser(configDir string) *NiriParser {
|
||||||
@@ -41,20 +51,53 @@ func NewNiriParser(configDir string) *NiriParser {
|
|||||||
bindMap: make(map[string]*NiriKeyBinding),
|
bindMap: make(map[string]*NiriKeyBinding),
|
||||||
bindOrder: []string{},
|
bindOrder: []string{},
|
||||||
currentSource: "",
|
currentSource: "",
|
||||||
|
dmsIncludePos: -1,
|
||||||
|
dmsBindKeys: make(map[string]bool),
|
||||||
|
configBindKeys: make(map[string]bool),
|
||||||
|
dmsBindMap: make(map[string]*NiriKeyBinding),
|
||||||
|
conflictingConfigs: make(map[string]*NiriKeyBinding),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *NiriParser) Parse() (*NiriSection, error) {
|
func (p *NiriParser) Parse() (*NiriSection, error) {
|
||||||
|
dmsBindsPath := filepath.Join(p.configDir, "dms", "binds.kdl")
|
||||||
|
if _, err := os.Stat(dmsBindsPath); err == nil {
|
||||||
|
p.dmsBindsExists = true
|
||||||
|
}
|
||||||
|
|
||||||
configPath := filepath.Join(p.configDir, "config.kdl")
|
configPath := filepath.Join(p.configDir, "config.kdl")
|
||||||
section, err := p.parseFile(configPath, "")
|
section, err := p.parseFile(configPath, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.dmsBindsExists && !p.dmsProcessed {
|
||||||
|
p.parseDMSBindsDirectly(dmsBindsPath, section)
|
||||||
|
}
|
||||||
|
|
||||||
section.Keybinds = p.finalizeBinds()
|
section.Keybinds = p.finalizeBinds()
|
||||||
return section, nil
|
return section, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *NiriParser) parseDMSBindsDirectly(dmsBindsPath string, section *NiriSection) {
|
||||||
|
data, err := os.ReadFile(dmsBindsPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := kdl.Parse(strings.NewReader(string(data)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prevSource := p.currentSource
|
||||||
|
p.currentSource = dmsBindsPath
|
||||||
|
baseDir := filepath.Dir(dmsBindsPath)
|
||||||
|
p.processNodes(doc.Nodes, section, baseDir)
|
||||||
|
p.currentSource = prevSource
|
||||||
|
p.dmsProcessed = true
|
||||||
|
}
|
||||||
|
|
||||||
func (p *NiriParser) finalizeBinds() []NiriKeyBinding {
|
func (p *NiriParser) finalizeBinds() []NiriKeyBinding {
|
||||||
binds := make([]NiriKeyBinding, 0, len(p.bindOrder))
|
binds := make([]NiriKeyBinding, 0, len(p.bindOrder))
|
||||||
for _, key := range p.bindOrder {
|
for _, key := range p.bindOrder {
|
||||||
@@ -67,6 +110,20 @@ func (p *NiriParser) finalizeBinds() []NiriKeyBinding {
|
|||||||
|
|
||||||
func (p *NiriParser) addBind(kb *NiriKeyBinding) {
|
func (p *NiriParser) addBind(kb *NiriKeyBinding) {
|
||||||
key := p.formatBindKey(kb)
|
key := p.formatBindKey(kb)
|
||||||
|
isDMSBind := strings.Contains(kb.Source, "dms/binds.kdl")
|
||||||
|
|
||||||
|
if isDMSBind {
|
||||||
|
p.dmsBindKeys[key] = true
|
||||||
|
p.dmsBindMap[key] = kb
|
||||||
|
} else if p.dmsBindKeys[key] {
|
||||||
|
p.bindsAfterDMS++
|
||||||
|
p.conflictingConfigs[key] = kb
|
||||||
|
p.configBindKeys[key] = true
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
p.configBindKeys[key] = true
|
||||||
|
}
|
||||||
|
|
||||||
if _, exists := p.bindMap[key]; !exists {
|
if _, exists := p.bindMap[key]; !exists {
|
||||||
p.bindOrder = append(p.bindOrder, key)
|
p.bindOrder = append(p.bindOrder, key)
|
||||||
}
|
}
|
||||||
@@ -105,9 +162,11 @@ func (p *NiriParser) parseFile(filePath, sectionName string) (*NiriSection, erro
|
|||||||
Name: sectionName,
|
Name: sectionName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevSource := p.currentSource
|
||||||
p.currentSource = absPath
|
p.currentSource = absPath
|
||||||
baseDir := filepath.Dir(absPath)
|
baseDir := filepath.Dir(absPath)
|
||||||
p.processNodes(doc.Nodes, section, baseDir)
|
p.processNodes(doc.Nodes, section, baseDir)
|
||||||
|
p.currentSource = prevSource
|
||||||
|
|
||||||
return section, nil
|
return section, nil
|
||||||
}
|
}
|
||||||
@@ -133,8 +192,13 @@ func (p *NiriParser) handleInclude(node *document.Node, section *NiriSection, ba
|
|||||||
}
|
}
|
||||||
|
|
||||||
includePath := strings.Trim(node.Arguments[0].String(), "\"")
|
includePath := strings.Trim(node.Arguments[0].String(), "\"")
|
||||||
if includePath == "dms/binds.kdl" || strings.HasSuffix(includePath, "/dms/binds.kdl") {
|
isDMSInclude := includePath == "dms/binds.kdl" || strings.HasSuffix(includePath, "/dms/binds.kdl")
|
||||||
|
|
||||||
|
p.includeCount++
|
||||||
|
if isDMSInclude {
|
||||||
p.dmsBindsIncluded = true
|
p.dmsBindsIncluded = true
|
||||||
|
p.dmsIncludePos = p.includeCount
|
||||||
|
p.bindsBeforeDMS = len(p.bindMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := filepath.Join(baseDir, includePath)
|
fullPath := filepath.Join(baseDir, includePath)
|
||||||
@@ -142,6 +206,10 @@ func (p *NiriParser) handleInclude(node *document.Node, section *NiriSection, ba
|
|||||||
fullPath = includePath
|
fullPath = includePath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isDMSInclude {
|
||||||
|
p.dmsProcessed = true
|
||||||
|
}
|
||||||
|
|
||||||
includedSection, err := p.parseFile(fullPath, "")
|
includedSection, err := p.parseFile(fullPath, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -232,6 +300,47 @@ func (p *NiriParser) parseKeyCombo(combo string) ([]string, string) {
|
|||||||
type NiriParseResult struct {
|
type NiriParseResult struct {
|
||||||
Section *NiriSection
|
Section *NiriSection
|
||||||
DMSBindsIncluded bool
|
DMSBindsIncluded bool
|
||||||
|
DMSStatus *DMSBindsStatusInfo
|
||||||
|
ConflictingConfigs map[string]*NiriKeyBinding
|
||||||
|
}
|
||||||
|
|
||||||
|
type DMSBindsStatusInfo struct {
|
||||||
|
Exists bool
|
||||||
|
Included bool
|
||||||
|
IncludePosition int
|
||||||
|
TotalIncludes int
|
||||||
|
BindsAfterDMS int
|
||||||
|
Effective bool
|
||||||
|
OverriddenBy int
|
||||||
|
StatusMessage string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *NiriParser) buildDMSStatus() *DMSBindsStatusInfo {
|
||||||
|
status := &DMSBindsStatusInfo{
|
||||||
|
Exists: p.dmsBindsExists,
|
||||||
|
Included: p.dmsBindsIncluded,
|
||||||
|
IncludePosition: p.dmsIncludePos,
|
||||||
|
TotalIncludes: p.includeCount,
|
||||||
|
BindsAfterDMS: p.bindsAfterDMS,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !p.dmsBindsExists:
|
||||||
|
status.Effective = false
|
||||||
|
status.StatusMessage = "dms/binds.kdl does not exist"
|
||||||
|
case !p.dmsBindsIncluded:
|
||||||
|
status.Effective = false
|
||||||
|
status.StatusMessage = "dms/binds.kdl is not included in config.kdl"
|
||||||
|
case p.bindsAfterDMS > 0:
|
||||||
|
status.Effective = true
|
||||||
|
status.OverriddenBy = p.bindsAfterDMS
|
||||||
|
status.StatusMessage = "Some DMS binds may be overridden by config binds"
|
||||||
|
default:
|
||||||
|
status.Effective = true
|
||||||
|
status.StatusMessage = "DMS binds are active"
|
||||||
|
}
|
||||||
|
|
||||||
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseNiriKeys(configDir string) (*NiriParseResult, error) {
|
func ParseNiriKeys(configDir string) (*NiriParseResult, error) {
|
||||||
@@ -243,5 +352,7 @@ func ParseNiriKeys(configDir string) (*NiriParseResult, error) {
|
|||||||
return &NiriParseResult{
|
return &NiriParseResult{
|
||||||
Section: section,
|
Section: section,
|
||||||
DMSBindsIncluded: parser.HasDMSBindsIncluded(),
|
DMSBindsIncluded: parser.HasDMSBindsIncluded(),
|
||||||
|
DMSStatus: parser.buildDMSStatus(),
|
||||||
|
ConflictingConfigs: parser.conflictingConfigs,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,18 @@ type Keybind struct {
|
|||||||
Action string `json:"action,omitempty"`
|
Action string `json:"action,omitempty"`
|
||||||
Subcategory string `json:"subcat,omitempty"`
|
Subcategory string `json:"subcat,omitempty"`
|
||||||
Source string `json:"source,omitempty"`
|
Source string `json:"source,omitempty"`
|
||||||
|
Conflict *Keybind `json:"conflict,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DMSBindsStatus struct {
|
||||||
|
Exists bool `json:"exists"`
|
||||||
|
Included bool `json:"included"`
|
||||||
|
IncludePosition int `json:"includePosition"`
|
||||||
|
TotalIncludes int `json:"totalIncludes"`
|
||||||
|
BindsAfterDMS int `json:"bindsAfterDms"`
|
||||||
|
Effective bool `json:"effective"`
|
||||||
|
OverriddenBy int `json:"overriddenBy"`
|
||||||
|
StatusMessage string `json:"statusMessage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheatSheet struct {
|
type CheatSheet struct {
|
||||||
@@ -13,6 +25,7 @@ type CheatSheet struct {
|
|||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
Binds map[string][]Keybind `json:"binds"`
|
Binds map[string][]Keybind `json:"binds"`
|
||||||
DMSBindsIncluded bool `json:"dmsBindsIncluded"`
|
DMSBindsIncluded bool `json:"dmsBindsIncluded"`
|
||||||
|
DMSStatus *DMSBindsStatus `json:"dmsStatus,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
|
|||||||
@@ -1,466 +0,0 @@
|
|||||||
pragma Singleton
|
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Hyprland
|
|
||||||
import qs.Services
|
|
||||||
import qs.Widgets
|
|
||||||
|
|
||||||
Singleton {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property var activeModal: null
|
|
||||||
property bool windowsVisible: false
|
|
||||||
property var targetScreen: null
|
|
||||||
property var persistentModal: null
|
|
||||||
property Item currentDirectContent: null
|
|
||||||
|
|
||||||
readonly property bool hasActiveModal: activeModal !== null
|
|
||||||
readonly property bool hasPersistentModal: persistentModal !== null
|
|
||||||
readonly property bool isPersistentModalActive: hasActiveModal && activeModal === persistentModal
|
|
||||||
readonly property bool shouldShowModal: hasActiveModal
|
|
||||||
readonly property bool shouldKeepWindowsAlive: hasPersistentModal && targetScreen !== null
|
|
||||||
|
|
||||||
onPersistentModalChanged: {
|
|
||||||
if (!persistentModal) {
|
|
||||||
if (!hasActiveModal)
|
|
||||||
targetScreen = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!targetScreen)
|
|
||||||
targetScreen = CompositorService.focusedScreen;
|
|
||||||
cachedModal = persistentModal;
|
|
||||||
updateCachedModalProperties(persistentModal);
|
|
||||||
}
|
|
||||||
|
|
||||||
onActiveModalChanged: updateDirectContent()
|
|
||||||
|
|
||||||
function updateCachedModalProperties(modal) {
|
|
||||||
if (!modal)
|
|
||||||
return;
|
|
||||||
cachedModalWidth = Theme.px(modal.modalWidth, dpr);
|
|
||||||
cachedModalHeight = Theme.px(modal.modalHeight, dpr);
|
|
||||||
cachedModalX = calculateX(modal);
|
|
||||||
cachedModalY = calculateY(modal);
|
|
||||||
cachedAnimationDuration = modal.animationDuration ?? Theme.shortDuration;
|
|
||||||
cachedEnterCurve = modal.animationEnterCurve ?? Theme.expressiveCurves.expressiveFastSpatial;
|
|
||||||
cachedExitCurve = modal.animationExitCurve ?? Theme.expressiveCurves.expressiveFastSpatial;
|
|
||||||
cachedScaleCollapsed = modal.animationScaleCollapsed ?? 0.96;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDirectContent() {
|
|
||||||
if (currentDirectContent) {
|
|
||||||
currentDirectContent.visible = false;
|
|
||||||
currentDirectContent.parent = null;
|
|
||||||
currentDirectContent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activeModal?.directContent)
|
|
||||||
return;
|
|
||||||
|
|
||||||
currentDirectContent = activeModal.directContent;
|
|
||||||
currentDirectContent.parent = directContentWrapper;
|
|
||||||
currentDirectContent.anchors.fill = directContentWrapper;
|
|
||||||
currentDirectContent.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isScreenValid(screen) {
|
|
||||||
if (!screen)
|
|
||||||
return false;
|
|
||||||
for (const s of Quickshell.screens) {
|
|
||||||
if (s === screen || s.name === screen.name)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleScreensChanged() {
|
|
||||||
if (!targetScreen)
|
|
||||||
return;
|
|
||||||
if (isScreenValid(targetScreen))
|
|
||||||
return;
|
|
||||||
|
|
||||||
const newScreen = CompositorService.focusedScreen;
|
|
||||||
if (hasActiveModal) {
|
|
||||||
targetScreen = newScreen;
|
|
||||||
if (cachedModal)
|
|
||||||
updateCachedModalProperties(cachedModal);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasPersistentModal) {
|
|
||||||
targetScreen = newScreen;
|
|
||||||
updateCachedModalProperties(persistentModal);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
targetScreen = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Quickshell
|
|
||||||
function onScreensChanged() {
|
|
||||||
root.handleScreensChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property var screen: backgroundWindow.screen
|
|
||||||
readonly property real dpr: screen ? CompositorService.getScreenScale(screen) : 1
|
|
||||||
readonly property real shadowBuffer: 5
|
|
||||||
|
|
||||||
property bool wantsToHide: false
|
|
||||||
|
|
||||||
property real cachedModalWidth: 400
|
|
||||||
property real cachedModalHeight: 300
|
|
||||||
property real cachedModalX: 0
|
|
||||||
property real cachedModalY: 0
|
|
||||||
property var cachedModal: null
|
|
||||||
property int cachedAnimationDuration: Theme.shortDuration
|
|
||||||
property var cachedEnterCurve: Theme.expressiveCurves.expressiveFastSpatial
|
|
||||||
property var cachedExitCurve: Theme.expressiveCurves.expressiveFastSpatial
|
|
||||||
property real cachedScaleCollapsed: 0.96
|
|
||||||
|
|
||||||
readonly property real modalWidth: cachedModalWidth
|
|
||||||
readonly property real modalHeight: cachedModalHeight
|
|
||||||
readonly property real modalX: cachedModalX
|
|
||||||
readonly property real modalY: cachedModalY
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root.cachedModal
|
|
||||||
function onModalWidthChanged() {
|
|
||||||
if (!root.hasActiveModal)
|
|
||||||
return;
|
|
||||||
root.cachedModalWidth = Theme.px(root.cachedModal.modalWidth, root.dpr);
|
|
||||||
root.cachedModalX = root.calculateX(root.cachedModal);
|
|
||||||
}
|
|
||||||
function onModalHeightChanged() {
|
|
||||||
if (!root.hasActiveModal)
|
|
||||||
return;
|
|
||||||
root.cachedModalHeight = Theme.px(root.cachedModal.modalHeight, root.dpr);
|
|
||||||
root.cachedModalY = root.calculateY(root.cachedModal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onScreenChanged: {
|
|
||||||
if (!cachedModal || !screen)
|
|
||||||
return;
|
|
||||||
cachedModalWidth = Theme.px(cachedModal.modalWidth, dpr);
|
|
||||||
cachedModalHeight = Theme.px(cachedModal.modalHeight, dpr);
|
|
||||||
cachedModalX = calculateX(cachedModal);
|
|
||||||
cachedModalY = calculateY(cachedModal);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showModal(modal) {
|
|
||||||
wantsToHide = false;
|
|
||||||
targetScreen = CompositorService.focusedScreen;
|
|
||||||
activeModal = modal;
|
|
||||||
cachedModal = modal;
|
|
||||||
windowsVisible = true;
|
|
||||||
updateCachedModalProperties(modal);
|
|
||||||
|
|
||||||
if (modal.directContent)
|
|
||||||
Qt.callLater(focusDirectContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusDirectContent() {
|
|
||||||
if (!hasActiveModal)
|
|
||||||
return;
|
|
||||||
if (!cachedModal?.directContent)
|
|
||||||
return;
|
|
||||||
cachedModal.directContent.forceActiveFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideModal() {
|
|
||||||
wantsToHide = true;
|
|
||||||
Qt.callLater(completeHide);
|
|
||||||
}
|
|
||||||
|
|
||||||
function completeHide() {
|
|
||||||
if (!wantsToHide)
|
|
||||||
return;
|
|
||||||
activeModal = null;
|
|
||||||
wantsToHide = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideModalInstant() {
|
|
||||||
wantsToHide = false;
|
|
||||||
const closingModal = activeModal;
|
|
||||||
activeModal = null;
|
|
||||||
|
|
||||||
if (shouldKeepWindowsAlive) {
|
|
||||||
cachedModal = persistentModal;
|
|
||||||
updateCachedModalProperties(persistentModal);
|
|
||||||
} else {
|
|
||||||
windowsVisible = false;
|
|
||||||
targetScreen = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupInputMethod();
|
|
||||||
if (closingModal && typeof closingModal.onFullyClosed === "function")
|
|
||||||
closingModal.onFullyClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCloseAnimationFinished() {
|
|
||||||
if (hasActiveModal)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (cachedModal && typeof cachedModal.onFullyClosed === "function")
|
|
||||||
cachedModal.onFullyClosed();
|
|
||||||
|
|
||||||
cleanupInputMethod();
|
|
||||||
|
|
||||||
if (shouldKeepWindowsAlive) {
|
|
||||||
cachedModal = persistentModal;
|
|
||||||
updateCachedModalProperties(persistentModal);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
windowsVisible = false;
|
|
||||||
targetScreen = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanupInputMethod() {
|
|
||||||
if (!Qt.inputMethod)
|
|
||||||
return;
|
|
||||||
Qt.inputMethod.hide();
|
|
||||||
Qt.inputMethod.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateX(m) {
|
|
||||||
const screen = backgroundWindow.screen;
|
|
||||||
if (!screen)
|
|
||||||
return 0;
|
|
||||||
const w = Theme.px(m.modalWidth, dpr);
|
|
||||||
switch (m.positioning) {
|
|
||||||
case "center":
|
|
||||||
return Theme.snap((screen.width - w) / 2, dpr);
|
|
||||||
case "top-right":
|
|
||||||
return Theme.snap(Math.max(Theme.spacingL, screen.width - w - Theme.spacingL), dpr);
|
|
||||||
case "custom":
|
|
||||||
return Theme.snap(m.customPosition.x, dpr);
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateY(m) {
|
|
||||||
const screen = backgroundWindow.screen;
|
|
||||||
if (!screen)
|
|
||||||
return 0;
|
|
||||||
const h = Theme.px(m.modalHeight, dpr);
|
|
||||||
switch (m.positioning) {
|
|
||||||
case "center":
|
|
||||||
return Theme.snap((screen.height - h) / 2, dpr);
|
|
||||||
case "top-right":
|
|
||||||
return Theme.snap(Theme.barHeight + Theme.spacingXS, dpr);
|
|
||||||
case "custom":
|
|
||||||
return Theme.snap(m.customPosition.y, dpr);
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: backgroundWindow
|
|
||||||
visible: root.windowsVisible || root.shouldKeepWindowsAlive
|
|
||||||
screen: root.targetScreen
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:modal:background"
|
|
||||||
WlrLayershell.layer: WlrLayer.Top
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
top: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
mask: Region {
|
|
||||||
item: backgroundMaskRect
|
|
||||||
intersection: Intersection.Xor
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: backgroundMaskRect
|
|
||||||
x: root.shouldShowModal ? root.modalX : 0
|
|
||||||
y: root.shouldShowModal ? root.modalY : 0
|
|
||||||
width: root.shouldShowModal ? root.modalWidth : (backgroundWindow.screen?.width ?? 1920)
|
|
||||||
height: root.shouldShowModal ? root.modalHeight : (backgroundWindow.screen?.height ?? 1080)
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: root.windowsVisible
|
|
||||||
onClicked: mouse => {
|
|
||||||
if (!root.cachedModal || !root.shouldShowModal)
|
|
||||||
return;
|
|
||||||
if (!(root.cachedModal.closeOnBackgroundClick ?? true))
|
|
||||||
return;
|
|
||||||
const outside = mouse.x < root.modalX || mouse.x > root.modalX + root.modalWidth || mouse.y < root.modalY || mouse.y > root.modalY + root.modalHeight;
|
|
||||||
if (!outside)
|
|
||||||
return;
|
|
||||||
root.cachedModal.backgroundClicked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "black"
|
|
||||||
opacity: root.shouldShowModal && SettingsData.modalDarkenBackground ? (root.cachedModal?.backgroundOpacity ?? 0.5) : 0
|
|
||||||
visible: SettingsData.modalDarkenBackground
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: root.cachedAnimationDuration
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: root.shouldShowModal ? root.cachedEnterCurve : root.cachedExitCurve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: contentWindow
|
|
||||||
visible: root.windowsVisible || root.shouldKeepWindowsAlive
|
|
||||||
screen: root.targetScreen
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
WlrLayershell.namespace: root.cachedModal?.layerNamespace ?? "dms:modal"
|
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: {
|
|
||||||
if (!root.hasActiveModal)
|
|
||||||
return WlrKeyboardFocus.None;
|
|
||||||
if (root.cachedModal?.customKeyboardFocus !== null && root.cachedModal?.customKeyboardFocus !== undefined)
|
|
||||||
return root.cachedModal.customKeyboardFocus;
|
|
||||||
if (CompositorService.isHyprland)
|
|
||||||
return WlrKeyboardFocus.OnDemand;
|
|
||||||
return WlrKeyboardFocus.Exclusive;
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
left: true
|
|
||||||
top: true
|
|
||||||
}
|
|
||||||
|
|
||||||
WlrLayershell.margins {
|
|
||||||
left: Math.max(0, Theme.snap(root.modalX - root.shadowBuffer, root.dpr))
|
|
||||||
top: Math.max(0, Theme.snap(root.modalY - root.shadowBuffer, root.dpr))
|
|
||||||
}
|
|
||||||
|
|
||||||
implicitWidth: root.modalWidth + (root.shadowBuffer * 2)
|
|
||||||
implicitHeight: root.modalHeight + (root.shadowBuffer * 2)
|
|
||||||
|
|
||||||
mask: Region {
|
|
||||||
item: contentMaskRect
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: contentMaskRect
|
|
||||||
x: root.shadowBuffer
|
|
||||||
y: root.shadowBuffer
|
|
||||||
width: root.shouldShowModal ? root.modalWidth : 0
|
|
||||||
height: root.shouldShowModal ? root.modalHeight : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
|
||||||
windows: [contentWindow]
|
|
||||||
active: CompositorService.isHyprland && root.hasActiveModal && (root.cachedModal?.shouldHaveFocus ?? false)
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: contentContainer
|
|
||||||
x: root.shadowBuffer
|
|
||||||
y: root.shadowBuffer
|
|
||||||
width: root.modalWidth
|
|
||||||
height: root.modalHeight
|
|
||||||
|
|
||||||
readonly property bool hasDirectContent: root.currentDirectContent !== null
|
|
||||||
|
|
||||||
opacity: root.shouldShowModal ? 1 : 0
|
|
||||||
scale: root.shouldShowModal ? 1 : root.cachedScaleCollapsed
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
id: opacityAnimation
|
|
||||||
duration: root.cachedAnimationDuration
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: root.shouldShowModal ? root.cachedEnterCurve : root.cachedExitCurve
|
|
||||||
onRunningChanged: {
|
|
||||||
if (running || root.shouldShowModal)
|
|
||||||
return;
|
|
||||||
root.onCloseAnimationFinished();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
id: scaleAnimation
|
|
||||||
duration: root.cachedAnimationDuration
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: root.shouldShowModal ? root.cachedEnterCurve : root.cachedExitCurve
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankRectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: root.cachedModal?.backgroundColor ?? Theme.surfaceContainer
|
|
||||||
borderColor: root.cachedModal?.borderColor ?? Theme.outlineMedium
|
|
||||||
borderWidth: root.cachedModal?.borderWidth ?? 1
|
|
||||||
radius: root.cachedModal?.cornerRadius ?? Theme.cornerRadius
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
FocusScope {
|
|
||||||
id: modalFocusScope
|
|
||||||
anchors.fill: parent
|
|
||||||
focus: root.hasActiveModal
|
|
||||||
|
|
||||||
Keys.onEscapePressed: event => {
|
|
||||||
if (!root.cachedModal?.closeOnEscapeKey)
|
|
||||||
return;
|
|
||||||
root.cachedModal.close();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.forwardTo: contentContainer.hasDirectContent ? [directContentWrapper] : (contentLoader.item ? [contentLoader.item] : [])
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: directContentWrapper
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: contentContainer.hasDirectContent
|
|
||||||
focus: contentContainer.hasDirectContent && root.hasActiveModal
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: contentLoader
|
|
||||||
anchors.fill: parent
|
|
||||||
active: !contentContainer.hasDirectContent && root.windowsVisible
|
|
||||||
asynchronous: false
|
|
||||||
sourceComponent: root.cachedModal?.content ?? null
|
|
||||||
visible: !contentContainer.hasDirectContent
|
|
||||||
focus: !contentContainer.hasDirectContent && root.hasActiveModal
|
|
||||||
onLoaded: {
|
|
||||||
if (!item)
|
|
||||||
return;
|
|
||||||
if (root.cachedModal)
|
|
||||||
root.cachedModal.loadedContent = item;
|
|
||||||
if (root.hasActiveModal)
|
|
||||||
item.forceActiveFocus();
|
|
||||||
}
|
|
||||||
onActiveChanged: {
|
|
||||||
if (active || !root.cachedModal)
|
|
||||||
return;
|
|
||||||
root.cachedModal.loadedContent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,8 +25,6 @@ import qs.Services
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property bool _forceDisplayService: DisplayService.brightnessAvailable !== undefined
|
|
||||||
|
|
||||||
Instantiator {
|
Instantiator {
|
||||||
id: daemonPluginInstantiator
|
id: daemonPluginInstantiator
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell.Hyprland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -9,6 +10,11 @@ DankModal {
|
|||||||
|
|
||||||
layerNamespace: "dms:bluetooth-pairing"
|
layerNamespace: "dms:bluetooth-pairing"
|
||||||
|
|
||||||
|
HyprlandFocusGrab {
|
||||||
|
windows: [root.contentWindow]
|
||||||
|
active: CompositorService.isHyprland && root.shouldHaveFocus
|
||||||
|
}
|
||||||
|
|
||||||
property string deviceName: ""
|
property string deviceName: ""
|
||||||
property string deviceAddress: ""
|
property string deviceAddress: ""
|
||||||
property string requestType: ""
|
property string requestType: ""
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
import qs.Modals.Clipboard
|
import qs.Modals.Clipboard
|
||||||
|
|
||||||
FocusScope {
|
Item {
|
||||||
id: clipboardContent
|
id: clipboardContent
|
||||||
|
|
||||||
required property var modal
|
required property var modal
|
||||||
@@ -14,7 +15,6 @@ FocusScope {
|
|||||||
property alias clipboardListView: clipboardListView
|
property alias clipboardListView: clipboardListView
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -31,13 +31,14 @@ FocusScope {
|
|||||||
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
|
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
|
||||||
onClearAllClicked: {
|
onClearAllClicked: {
|
||||||
clearConfirmDialog.show(I18n.tr("Clear All History?"), I18n.tr("This will permanently delete all clipboard history."), function () {
|
clearConfirmDialog.show(I18n.tr("Clear All History?"), I18n.tr("This will permanently delete all clipboard history."), function () {
|
||||||
modal.clearAll();
|
modal.clearAll()
|
||||||
modal.hide();
|
modal.hide()
|
||||||
}, function () {});
|
}, function () {})
|
||||||
}
|
}
|
||||||
onCloseClicked: modal.hide()
|
onCloseClicked: modal.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search Field
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: searchField
|
id: searchField
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -46,24 +47,27 @@ FocusScope {
|
|||||||
showClearButton: true
|
showClearButton: true
|
||||||
focus: true
|
focus: true
|
||||||
ignoreTabKeys: true
|
ignoreTabKeys: true
|
||||||
|
keyForwardTargets: [modal.modalFocusScope]
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
modal.searchText = text;
|
modal.searchText = text
|
||||||
modal.updateFilteredModel();
|
modal.updateFilteredModel()
|
||||||
}
|
}
|
||||||
Keys.onPressed: event => {
|
Keys.onEscapePressed: function (event) {
|
||||||
if (event.key === Qt.Key_Escape) {
|
modal.hide()
|
||||||
modal.hide();
|
event.accepted = true
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
modal.keyboardController?.handleKey(event);
|
Component.onCompleted: {
|
||||||
|
Qt.callLater(function () {
|
||||||
|
forceActiveFocus()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Component.onCompleted: Qt.callLater(() => forceActiveFocus())
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: modal
|
target: modal
|
||||||
function onOpened() {
|
function onOpened() {
|
||||||
Qt.callLater(() => searchField.forceActiveFocus());
|
Qt.callLater(function () {
|
||||||
|
searchField.forceActiveFocus()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,21 +97,21 @@ FocusScope {
|
|||||||
|
|
||||||
function ensureVisible(index) {
|
function ensureVisible(index) {
|
||||||
if (index < 0 || index >= count) {
|
if (index < 0 || index >= count) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
const itemHeight = ClipboardConstants.itemHeight + spacing;
|
const itemHeight = ClipboardConstants.itemHeight + spacing
|
||||||
const itemY = index * itemHeight;
|
const itemY = index * itemHeight
|
||||||
const itemBottom = itemY + itemHeight;
|
const itemBottom = itemY + itemHeight
|
||||||
if (itemY < contentY) {
|
if (itemY < contentY) {
|
||||||
contentY = itemY;
|
contentY = itemY
|
||||||
} else if (itemBottom > contentY + height) {
|
} else if (itemBottom > contentY + height) {
|
||||||
contentY = itemBottom - height;
|
contentY = itemBottom - height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onCurrentIndexChanged: {
|
onCurrentIndexChanged: {
|
||||||
if (clipboardContent.modal && clipboardContent.modal.keyboardNavigationActive && currentIndex >= 0) {
|
if (clipboardContent.modal && clipboardContent.modal.keyboardNavigationActive && currentIndex >= 0) {
|
||||||
ensureVisible(currentIndex);
|
ensureVisible(currentIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
@@ -12,6 +13,11 @@ DankModal {
|
|||||||
|
|
||||||
layerNamespace: "dms:clipboard"
|
layerNamespace: "dms:clipboard"
|
||||||
|
|
||||||
|
HyprlandFocusGrab {
|
||||||
|
windows: [clipboardHistoryModal.contentWindow]
|
||||||
|
active: CompositorService.isHyprland && clipboardHistoryModal.shouldHaveFocus
|
||||||
|
}
|
||||||
|
|
||||||
property int totalCount: 0
|
property int totalCount: 0
|
||||||
property var clipboardEntries: []
|
property var clipboardEntries: []
|
||||||
property string searchText: ""
|
property string searchText: ""
|
||||||
@@ -142,10 +148,11 @@ DankModal {
|
|||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
enableShadow: true
|
enableShadow: true
|
||||||
onBackgroundClicked: hide()
|
onBackgroundClicked: hide()
|
||||||
|
modalFocusScope.Keys.onPressed: function (event) {
|
||||||
|
keyboardController.handleKey(event);
|
||||||
|
}
|
||||||
content: clipboardContent
|
content: clipboardContent
|
||||||
|
|
||||||
property alias keyboardController: keyboardController
|
|
||||||
|
|
||||||
ClipboardKeyboardController {
|
ClipboardKeyboardController {
|
||||||
id: keyboardController
|
id: keyboardController
|
||||||
modal: clipboardHistoryModal
|
modal: clipboardHistoryModal
|
||||||
@@ -158,11 +165,13 @@ DankModal {
|
|||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
clipboardHistoryModal.shouldHaveFocus = false;
|
clipboardHistoryModal.shouldHaveFocus = false;
|
||||||
return;
|
} else if (clipboardHistoryModal.shouldBeVisible) {
|
||||||
}
|
|
||||||
if (!clipboardHistoryModal.shouldBeVisible)
|
|
||||||
return;
|
|
||||||
clipboardHistoryModal.shouldHaveFocus = true;
|
clipboardHistoryModal.shouldHaveFocus = true;
|
||||||
|
clipboardHistoryModal.modalFocusScope.forceActiveFocus();
|
||||||
|
if (clipboardHistoryModal.contentLoader.item && clipboardHistoryModal.contentLoader.item.searchField) {
|
||||||
|
clipboardHistoryModal.contentLoader.item.searchField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,21 +61,29 @@ DankModal {
|
|||||||
shouldBeVisible: false
|
shouldBeVisible: false
|
||||||
allowStacking: true
|
allowStacking: true
|
||||||
modalWidth: 350
|
modalWidth: 350
|
||||||
modalHeight: 160
|
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 160
|
||||||
enableShadow: true
|
enableShadow: true
|
||||||
shouldHaveFocus: true
|
shouldHaveFocus: true
|
||||||
onBackgroundClicked: {
|
onBackgroundClicked: {
|
||||||
close();
|
close();
|
||||||
if (onCancel)
|
if (onCancel) {
|
||||||
onCancel();
|
onCancel();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
function handleKey(event) {
|
onOpened: {
|
||||||
|
Qt.callLater(function () {
|
||||||
|
modalFocusScope.forceActiveFocus();
|
||||||
|
modalFocusScope.focus = true;
|
||||||
|
shouldHaveFocus = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
modalFocusScope.Keys.onPressed: function (event) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_Escape:
|
case Qt.Key_Escape:
|
||||||
close();
|
close();
|
||||||
if (onCancel)
|
if (onCancel) {
|
||||||
onCancel();
|
onCancel();
|
||||||
|
}
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
break;
|
break;
|
||||||
case Qt.Key_Left:
|
case Qt.Key_Left:
|
||||||
@@ -91,46 +99,46 @@ DankModal {
|
|||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
break;
|
break;
|
||||||
case Qt.Key_N:
|
case Qt.Key_N:
|
||||||
if (!(event.modifiers & Qt.ControlModifier))
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
return;
|
|
||||||
keyboardNavigation = true;
|
keyboardNavigation = true;
|
||||||
selectedButton = (selectedButton + 1) % 2;
|
selectedButton = (selectedButton + 1) % 2;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_P:
|
case Qt.Key_P:
|
||||||
if (!(event.modifiers & Qt.ControlModifier))
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
return;
|
|
||||||
keyboardNavigation = true;
|
keyboardNavigation = true;
|
||||||
selectedButton = selectedButton === -1 ? 1 : (selectedButton - 1 + 2) % 2;
|
selectedButton = selectedButton === -1 ? 1 : (selectedButton - 1 + 2) % 2;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_J:
|
case Qt.Key_J:
|
||||||
if (!(event.modifiers & Qt.ControlModifier))
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
return;
|
|
||||||
keyboardNavigation = true;
|
keyboardNavigation = true;
|
||||||
selectedButton = 1;
|
selectedButton = 1;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_K:
|
case Qt.Key_K:
|
||||||
if (!(event.modifiers & Qt.ControlModifier))
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
return;
|
|
||||||
keyboardNavigation = true;
|
keyboardNavigation = true;
|
||||||
selectedButton = 0;
|
selectedButton = 0;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_H:
|
case Qt.Key_H:
|
||||||
if (!(event.modifiers & Qt.ControlModifier))
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
return;
|
|
||||||
keyboardNavigation = true;
|
keyboardNavigation = true;
|
||||||
selectedButton = 0;
|
selectedButton = 0;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_L:
|
case Qt.Key_L:
|
||||||
if (!(event.modifiers & Qt.ControlModifier))
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
return;
|
|
||||||
keyboardNavigation = true;
|
keyboardNavigation = true;
|
||||||
selectedButton = 1;
|
selectedButton = 1;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt.Key_Tab:
|
case Qt.Key_Tab:
|
||||||
keyboardNavigation = true;
|
keyboardNavigation = true;
|
||||||
@@ -139,9 +147,9 @@ DankModal {
|
|||||||
break;
|
break;
|
||||||
case Qt.Key_Return:
|
case Qt.Key_Return:
|
||||||
case Qt.Key_Enter:
|
case Qt.Key_Enter:
|
||||||
if (selectedButton !== -1)
|
if (selectedButton !== -1) {
|
||||||
selectButton();
|
selectButton();
|
||||||
else {
|
} else {
|
||||||
selectedButton = 1;
|
selectedButton = 1;
|
||||||
selectButton();
|
selectButton();
|
||||||
}
|
}
|
||||||
@@ -151,13 +159,10 @@ DankModal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
FocusScope {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
|
||||||
implicitHeight: mainColumn.implicitHeight
|
implicitHeight: mainColumn.implicitHeight
|
||||||
|
|
||||||
Keys.onPressed: event => root.handleKey(event)
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: mainColumn
|
id: mainColumn
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string layerNamespace: "dms:modal"
|
property string layerNamespace: "dms:modal"
|
||||||
property Component content: null
|
property alias content: contentLoader.sourceComponent
|
||||||
|
property alias contentLoader: contentLoader
|
||||||
property Item directContent: null
|
property Item directContent: null
|
||||||
property Item loadedContent: null
|
|
||||||
readonly property var contentLoader: QtObject {
|
|
||||||
readonly property var item: root.directContent ?? root.loadedContent
|
|
||||||
}
|
|
||||||
property real modalWidth: 400
|
property real modalWidth: 400
|
||||||
property real modalHeight: 300
|
property real modalHeight: 300
|
||||||
property var targetScreen: null
|
property var targetScreen
|
||||||
|
readonly property var effectiveScreen: targetScreen || contentWindow.screen
|
||||||
|
readonly property real screenWidth: effectiveScreen?.width
|
||||||
|
readonly property real screenHeight: effectiveScreen?.height
|
||||||
|
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
|
||||||
property bool showBackground: true
|
property bool showBackground: true
|
||||||
property real backgroundOpacity: 0.5
|
property real backgroundOpacity: 0.5
|
||||||
property string positioning: "center"
|
property string positioning: "center"
|
||||||
@@ -31,6 +36,7 @@ Item {
|
|||||||
property real borderWidth: 1
|
property real borderWidth: 1
|
||||||
property real cornerRadius: Theme.cornerRadius
|
property real cornerRadius: Theme.cornerRadius
|
||||||
property bool enableShadow: false
|
property bool enableShadow: false
|
||||||
|
property alias modalFocusScope: focusScope
|
||||||
property bool shouldBeVisible: false
|
property bool shouldBeVisible: false
|
||||||
property bool shouldHaveFocus: shouldBeVisible
|
property bool shouldHaveFocus: shouldBeVisible
|
||||||
property bool allowFocusOverride: false
|
property bool allowFocusOverride: false
|
||||||
@@ -39,41 +45,48 @@ Item {
|
|||||||
property bool keepPopoutsOpen: false
|
property bool keepPopoutsOpen: false
|
||||||
property var customKeyboardFocus: null
|
property var customKeyboardFocus: null
|
||||||
property bool useOverlayLayer: false
|
property bool useOverlayLayer: false
|
||||||
|
readonly property alias contentWindow: contentWindow
|
||||||
|
readonly property alias backgroundWindow: backgroundWindow
|
||||||
|
|
||||||
signal opened
|
signal opened
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
signal backgroundClicked
|
signal backgroundClicked
|
||||||
|
|
||||||
onBackgroundClicked: {
|
property bool animationsEnabled: true
|
||||||
if (closeOnBackgroundClick)
|
readonly property bool useBackgroundWindow: true
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
ModalManager.openModal(root);
|
ModalManager.openModal(root);
|
||||||
|
closeTimer.stop();
|
||||||
shouldBeVisible = true;
|
shouldBeVisible = true;
|
||||||
shouldHaveFocus = true;
|
contentWindow.visible = false;
|
||||||
DankModalWindow.showModal(root);
|
if (useBackgroundWindow)
|
||||||
opened();
|
backgroundWindow.visible = true;
|
||||||
}
|
Qt.callLater(() => {
|
||||||
|
contentWindow.visible = true;
|
||||||
function openCentered() {
|
shouldHaveFocus = false;
|
||||||
positioning = "center";
|
Qt.callLater(() => {
|
||||||
open();
|
shouldHaveFocus = Qt.binding(() => shouldBeVisible);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
shouldBeVisible = false;
|
shouldBeVisible = false;
|
||||||
shouldHaveFocus = false;
|
shouldHaveFocus = false;
|
||||||
DankModalWindow.hideModal();
|
closeTimer.restart();
|
||||||
dialogClosed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function instantClose() {
|
function instantClose() {
|
||||||
|
animationsEnabled = false;
|
||||||
shouldBeVisible = false;
|
shouldBeVisible = false;
|
||||||
shouldHaveFocus = false;
|
shouldHaveFocus = false;
|
||||||
DankModalWindow.hideModalInstant();
|
closeTimer.stop();
|
||||||
|
contentWindow.visible = false;
|
||||||
|
if (useBackgroundWindow)
|
||||||
|
backgroundWindow.visible = false;
|
||||||
dialogClosed();
|
dialogClosed();
|
||||||
|
Qt.callLater(() => animationsEnabled = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
@@ -83,9 +96,322 @@ Item {
|
|||||||
Connections {
|
Connections {
|
||||||
target: ModalManager
|
target: ModalManager
|
||||||
function onCloseAllModalsExcept(excludedModal) {
|
function onCloseAllModalsExcept(excludedModal) {
|
||||||
if (excludedModal === root || allowStacking || !shouldBeVisible)
|
if (excludedModal !== root && !allowStacking && shouldBeVisible) {
|
||||||
return;
|
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: closeTimer
|
||||||
|
interval: animationDuration + 120
|
||||||
|
onTriggered: {
|
||||||
|
if (!shouldBeVisible) {
|
||||||
|
contentWindow.visible = false;
|
||||||
|
if (useBackgroundWindow)
|
||||||
|
backgroundWindow.visible = false;
|
||||||
|
dialogClosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real shadowBuffer: 5
|
||||||
|
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
||||||
|
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
||||||
|
|
||||||
|
readonly property real alignedX: Theme.snap((() => {
|
||||||
|
switch (positioning) {
|
||||||
|
case "center":
|
||||||
|
return (screenWidth - alignedWidth) / 2;
|
||||||
|
case "top-right":
|
||||||
|
return Math.max(Theme.spacingL, screenWidth - alignedWidth - Theme.spacingL);
|
||||||
|
case "custom":
|
||||||
|
return customPosition.x;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
})(), dpr)
|
||||||
|
|
||||||
|
readonly property real alignedY: Theme.snap((() => {
|
||||||
|
switch (positioning) {
|
||||||
|
case "center":
|
||||||
|
return (screenHeight - alignedHeight) / 2;
|
||||||
|
case "top-right":
|
||||||
|
return Theme.barHeight + Theme.spacingXS;
|
||||||
|
case "custom":
|
||||||
|
return customPosition.y;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
})(), dpr)
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: backgroundWindow
|
||||||
|
visible: false
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
WlrLayershell.namespace: root.layerNamespace + ":background"
|
||||||
|
WlrLayershell.layer: WlrLayershell.Top
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: Rectangle {
|
||||||
|
x: root.alignedX
|
||||||
|
y: root.alignedY
|
||||||
|
width: root.shouldBeVisible ? root.alignedWidth : 0
|
||||||
|
height: root.shouldBeVisible ? root.alignedHeight : 0
|
||||||
|
}
|
||||||
|
intersection: Intersection.Xor
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
|
||||||
|
onClicked: mouse => {
|
||||||
|
const clickX = mouse.x;
|
||||||
|
const clickY = mouse.y;
|
||||||
|
const outsideContent = clickX < root.alignedX || clickX > root.alignedX + root.alignedWidth || clickY < root.alignedY || clickY > root.alignedY + root.alignedHeight;
|
||||||
|
|
||||||
|
if (!outsideContent)
|
||||||
|
return;
|
||||||
|
root.backgroundClicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: background
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "black"
|
||||||
|
opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||||
|
visible: root.showBackground && SettingsData.modalDarkenBackground
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animationDuration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: contentWindow
|
||||||
|
visible: false
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
WlrLayershell.namespace: root.layerNamespace
|
||||||
|
WlrLayershell.layer: {
|
||||||
|
if (root.useOverlayLayer)
|
||||||
|
return WlrLayershell.Overlay;
|
||||||
|
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.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: {
|
||||||
|
if (customKeyboardFocus !== null)
|
||||||
|
return customKeyboardFocus;
|
||||||
|
if (!shouldHaveFocus)
|
||||||
|
return WlrKeyboardFocus.None;
|
||||||
|
if (CompositorService.isHyprland)
|
||||||
|
return WlrKeyboardFocus.OnDemand;
|
||||||
|
return WlrKeyboardFocus.Exclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
left: true
|
||||||
|
top: true
|
||||||
|
}
|
||||||
|
|
||||||
|
WlrLayershell.margins {
|
||||||
|
left: Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
||||||
|
top: Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: root.alignedWidth + (shadowBuffer * 2)
|
||||||
|
implicitHeight: root.alignedHeight + (shadowBuffer * 2)
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) {
|
||||||
|
opened();
|
||||||
|
} else {
|
||||||
|
if (Qt.inputMethod) {
|
||||||
|
Qt.inputMethod.hide();
|
||||||
|
Qt.inputMethod.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: modalContainer
|
||||||
|
x: shadowBuffer
|
||||||
|
y: shadowBuffer
|
||||||
|
width: root.alignedWidth
|
||||||
|
height: root.alignedHeight
|
||||||
|
|
||||||
|
readonly property bool slide: root.animationType === "slide"
|
||||||
|
readonly property real offsetX: slide ? 15 : 0
|
||||||
|
readonly property real offsetY: slide ? -30 : root.animationOffset
|
||||||
|
|
||||||
|
property real animX: 0
|
||||||
|
property real animY: 0
|
||||||
|
property real scaleValue: root.animationScaleCollapsed
|
||||||
|
|
||||||
|
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
|
||||||
|
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onShouldBeVisibleChanged() {
|
||||||
|
modalContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetX, root.dpr);
|
||||||
|
modalContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetY, root.dpr);
|
||||||
|
modalContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on animX {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animationDuration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on animY {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animationDuration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scaleValue {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animationDuration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: contentContainer
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
clip: false
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: animatedContent
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: false
|
||||||
|
opacity: root.shouldBeVisible ? 1 : 0
|
||||||
|
scale: modalContainer.scaleValue
|
||||||
|
x: Theme.snap(modalContainer.animX, root.dpr) + (parent.width - width) * (1 - modalContainer.scaleValue) * 0.5
|
||||||
|
y: Theme.snap(modalContainer.animY, root.dpr) + (parent.height - height) * (1 - modalContainer.scaleValue) * 0.5
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
|
NumberAnimation {
|
||||||
|
duration: animationDuration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankRectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: root.backgroundColor
|
||||||
|
borderColor: root.borderColor
|
||||||
|
borderWidth: root.borderWidth
|
||||||
|
radius: root.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: root.shouldBeVisible
|
||||||
|
clip: false
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: directContentWrapper
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: root.directContent !== null
|
||||||
|
focus: true
|
||||||
|
clip: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (root.directContent) {
|
||||||
|
root.directContent.parent = directContentWrapper;
|
||||||
|
root.directContent.anchors.fill = directContentWrapper;
|
||||||
|
Qt.callLater(() => root.directContent.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onDirectContentChanged() {
|
||||||
|
if (root.directContent) {
|
||||||
|
root.directContent.parent = directContentWrapper;
|
||||||
|
root.directContent.anchors.fill = directContentWrapper;
|
||||||
|
Qt.callLater(() => root.directContent.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: contentLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible)
|
||||||
|
asynchronous: false
|
||||||
|
focus: true
|
||||||
|
clip: false
|
||||||
|
visible: root.directContent === null
|
||||||
|
|
||||||
|
onLoaded: {
|
||||||
|
if (item) {
|
||||||
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
id: focusScope
|
||||||
|
objectName: "modalFocusScope"
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: root.shouldBeVisible || contentWindow.visible
|
||||||
|
focus: root.shouldBeVisible
|
||||||
|
Keys.onEscapePressed: event => {
|
||||||
|
if (root.closeOnEscapeKey && shouldHaveFocus) {
|
||||||
|
root.close();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -10,6 +11,11 @@ DankModal {
|
|||||||
|
|
||||||
layerNamespace: "dms:color-picker"
|
layerNamespace: "dms:color-picker"
|
||||||
|
|
||||||
|
HyprlandFocusGrab {
|
||||||
|
windows: [root.contentWindow]
|
||||||
|
active: CompositorService.isHyprland && root.shouldHaveFocus
|
||||||
|
}
|
||||||
|
|
||||||
property string pickerTitle: I18n.tr("Choose Color")
|
property string pickerTitle: I18n.tr("Choose Color")
|
||||||
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
|
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
|
||||||
property var onColorSelectedCallback: null
|
property var onColorSelectedCallback: null
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell.Hyprland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -16,6 +17,12 @@ DankModal {
|
|||||||
modalWidth: _maxW
|
modalWidth: _maxW
|
||||||
modalHeight: _maxH
|
modalHeight: _maxH
|
||||||
onBackgroundClicked: close()
|
onBackgroundClicked: close()
|
||||||
|
onOpened: () => Qt.callLater(() => modalFocusScope.forceActiveFocus())
|
||||||
|
|
||||||
|
HyprlandFocusGrab {
|
||||||
|
windows: [root.contentWindow]
|
||||||
|
active: CompositorService.isHyprland && root.shouldHaveFocus
|
||||||
|
}
|
||||||
|
|
||||||
function scrollDown() {
|
function scrollDown() {
|
||||||
if (!root.activeFlickable)
|
if (!root.activeFlickable)
|
||||||
@@ -33,35 +40,25 @@ DankModal {
|
|||||||
root.activeFlickable.contentY = newY;
|
root.activeFlickable.contentY = newY;
|
||||||
}
|
}
|
||||||
|
|
||||||
content: Component {
|
modalFocusScope.Keys.onPressed: event => {
|
||||||
FocusScope {
|
if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
||||||
anchors.fill: parent
|
scrollDown();
|
||||||
focus: true
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
||||||
|
scrollUp();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_Down) {
|
||||||
|
scrollDown();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_Up) {
|
||||||
|
scrollUp();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
content: Component {
|
||||||
switch (event.key) {
|
Item {
|
||||||
case Qt.Key_J:
|
anchors.fill: parent
|
||||||
if (!(event.modifiers & Qt.ControlModifier))
|
|
||||||
return;
|
|
||||||
root.scrollDown();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
case Qt.Key_K:
|
|
||||||
if (!(event.modifiers & Qt.ControlModifier))
|
|
||||||
return;
|
|
||||||
root.scrollUp();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
case Qt.Key_Down:
|
|
||||||
root.scrollDown();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
case Qt.Key_Up:
|
|
||||||
root.scrollUp();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell.Hyprland
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
@@ -10,6 +11,11 @@ DankModal {
|
|||||||
|
|
||||||
layerNamespace: "dms:notification-center-modal"
|
layerNamespace: "dms:notification-center-modal"
|
||||||
|
|
||||||
|
HyprlandFocusGrab {
|
||||||
|
windows: [notificationModal.contentWindow]
|
||||||
|
active: CompositorService.isHyprland && notificationModal.shouldHaveFocus
|
||||||
|
}
|
||||||
|
|
||||||
property bool notificationModalOpen: false
|
property bool notificationModalOpen: false
|
||||||
property var notificationListRef: null
|
property var notificationListRef: null
|
||||||
|
|
||||||
@@ -55,6 +61,9 @@ DankModal {
|
|||||||
modalHeight: 700
|
modalHeight: 700
|
||||||
visible: false
|
visible: false
|
||||||
onBackgroundClicked: hide()
|
onBackgroundClicked: hide()
|
||||||
|
onOpened: () => {
|
||||||
|
Qt.callLater(() => modalFocusScope.forceActiveFocus());
|
||||||
|
}
|
||||||
onShouldBeVisibleChanged: shouldBeVisible => {
|
onShouldBeVisibleChanged: shouldBeVisible => {
|
||||||
if (!shouldBeVisible) {
|
if (!shouldBeVisible) {
|
||||||
notificationModalOpen = false;
|
notificationModalOpen = false;
|
||||||
@@ -62,6 +71,7 @@ DankModal {
|
|||||||
NotificationService.onOverlayClose();
|
NotificationService.onOverlayClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
modalFocusScope.Keys.onPressed: event => modalKeyboardController.handleKey(event)
|
||||||
|
|
||||||
NotificationKeyboardController {
|
NotificationKeyboardController {
|
||||||
id: modalKeyboardController
|
id: modalKeyboardController
|
||||||
@@ -91,12 +101,10 @@ DankModal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
FocusScope {
|
Item {
|
||||||
id: notificationKeyHandler
|
id: notificationKeyHandler
|
||||||
anchors.fill: parent
|
|
||||||
focus: true
|
|
||||||
|
|
||||||
Keys.onPressed: event => modalKeyboardController.handleKey(event)
|
anchors.fill: parent
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -12,6 +13,11 @@ DankModal {
|
|||||||
layerNamespace: "dms:power-menu"
|
layerNamespace: "dms:power-menu"
|
||||||
keepPopoutsOpen: true
|
keepPopoutsOpen: true
|
||||||
|
|
||||||
|
HyprlandFocusGrab {
|
||||||
|
windows: [root.contentWindow]
|
||||||
|
active: CompositorService.isHyprland && root.shouldHaveFocus
|
||||||
|
}
|
||||||
|
|
||||||
property int selectedIndex: 0
|
property int selectedIndex: 0
|
||||||
property int selectedRow: 0
|
property int selectedRow: 0
|
||||||
property int selectedCol: 0
|
property int selectedCol: 0
|
||||||
@@ -269,11 +275,34 @@ DankModal {
|
|||||||
} else {
|
} else {
|
||||||
selectedIndex = defaultIndex;
|
selectedIndex = defaultIndex;
|
||||||
}
|
}
|
||||||
|
Qt.callLater(() => modalFocusScope.forceActiveFocus());
|
||||||
}
|
}
|
||||||
onDialogClosed: () => {
|
onDialogClosed: () => {
|
||||||
cancelHold();
|
cancelHold();
|
||||||
}
|
}
|
||||||
Component.onCompleted: updateVisibleActions()
|
Component.onCompleted: updateVisibleActions()
|
||||||
|
modalFocusScope.Keys.onPressed: event => {
|
||||||
|
if (event.isAutoRepeat) {
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (SettingsData.powerMenuGridLayout) {
|
||||||
|
handleGridNavigation(event, true);
|
||||||
|
} else {
|
||||||
|
handleListNavigation(event, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modalFocusScope.Keys.onReleased: event => {
|
||||||
|
if (event.isAutoRepeat) {
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (SettingsData.powerMenuGridLayout) {
|
||||||
|
handleGridNavigation(event, false);
|
||||||
|
} else {
|
||||||
|
handleListNavigation(event, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleListNavigation(event, isPressed) {
|
function handleListNavigation(event, isPressed) {
|
||||||
if (!isPressed) {
|
if (!isPressed) {
|
||||||
@@ -452,33 +481,10 @@ DankModal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
FocusScope {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
|
||||||
implicitHeight: (SettingsData.powerMenuGridLayout ? buttonGrid.implicitHeight : buttonColumn.implicitHeight) + Theme.spacingL * 2 + (root.needsConfirmation ? hintRow.height + Theme.spacingM : 0)
|
implicitHeight: (SettingsData.powerMenuGridLayout ? buttonGrid.implicitHeight : buttonColumn.implicitHeight) + Theme.spacingL * 2 + (root.needsConfirmation ? hintRow.height + Theme.spacingM : 0)
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
|
||||||
if (event.isAutoRepeat) {
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (SettingsData.powerMenuGridLayout)
|
|
||||||
root.handleGridNavigation(event, true);
|
|
||||||
else
|
|
||||||
root.handleListNavigation(event, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onReleased: event => {
|
|
||||||
if (event.isAutoRepeat) {
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (SettingsData.powerMenuGridLayout)
|
|
||||||
root.handleGridNavigation(event, false);
|
|
||||||
else
|
|
||||||
root.handleListNavigation(event, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Grid {
|
Grid {
|
||||||
id: buttonGrid
|
id: buttonGrid
|
||||||
visible: SettingsData.powerMenuGridLayout
|
visible: SettingsData.powerMenuGridLayout
|
||||||
|
|||||||
@@ -51,21 +51,7 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
focus: true
|
||||||
clip: false
|
clip: false
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
|
||||||
if (!activeFocus)
|
|
||||||
return;
|
|
||||||
if (!searchField)
|
|
||||||
return;
|
|
||||||
searchField.forceActiveFocus();
|
|
||||||
}
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
|
||||||
if (menu?.visible) {
|
|
||||||
menu.handleKey(event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
if (parentModal)
|
if (parentModal)
|
||||||
parentModal.hide();
|
parentModal.hide();
|
||||||
@@ -211,6 +197,7 @@ Item {
|
|||||||
|
|
||||||
parent: spotlightKeyHandler
|
parent: spotlightKeyHandler
|
||||||
appLauncher: spotlightKeyHandler.appLauncher
|
appLauncher: spotlightKeyHandler.appLauncher
|
||||||
|
parentHandler: spotlightKeyHandler
|
||||||
searchField: spotlightKeyHandler.searchField
|
searchField: spotlightKeyHandler.searchField
|
||||||
visible: false
|
visible: false
|
||||||
z: 1000
|
z: 1000
|
||||||
@@ -231,6 +218,8 @@ Item {
|
|||||||
sourceComponent: Component {
|
sourceComponent: Component {
|
||||||
SpotlightContextMenu {
|
SpotlightContextMenu {
|
||||||
appLauncher: spotlightKeyHandler.appLauncher
|
appLauncher: spotlightKeyHandler.appLauncher
|
||||||
|
parentHandler: spotlightKeyHandler
|
||||||
|
parentModal: spotlightKeyHandler.parentModal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,12 +280,6 @@ Item {
|
|||||||
updateSearchMode();
|
updateSearchMode();
|
||||||
}
|
}
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
const menu = spotlightKeyHandler.usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
|
||||||
if (menu?.visible) {
|
|
||||||
menu.handleKey(event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
if (parentModal)
|
if (parentModal)
|
||||||
parentModal.hide();
|
parentModal.hide();
|
||||||
@@ -329,7 +312,7 @@ Item {
|
|||||||
Row {
|
Row {
|
||||||
id: viewModeButtons
|
id: viewModeButtons
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
visible: searchMode === "apps"
|
visible: searchMode === "apps" && appLauncher.model.count > 0
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Spotlight
|
import qs.Modals.Spotlight
|
||||||
|
|
||||||
@@ -10,15 +11,17 @@ PanelWindow {
|
|||||||
WlrLayershell.namespace: "dms:spotlight-context-menu"
|
WlrLayershell.namespace: "dms:spotlight-context-menu"
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
WlrLayershell.layer: WlrLayershell.Overlay
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||||
|
|
||||||
property var appLauncher: null
|
property var appLauncher: null
|
||||||
|
property var parentHandler: null
|
||||||
|
property var parentModal: null
|
||||||
property real menuPositionX: 0
|
property real menuPositionX: 0
|
||||||
property real menuPositionY: 0
|
property real menuPositionY: 0
|
||||||
|
|
||||||
readonly property real shadowBuffer: 5
|
readonly property real shadowBuffer: 5
|
||||||
|
|
||||||
screen: DankModalWindow.targetScreen
|
screen: parentModal?.effectiveScreen
|
||||||
|
|
||||||
function show(x, y, app, fromKeyboard) {
|
function show(x, y, app, fromKeyboard) {
|
||||||
fromKeyboard = fromKeyboard || false;
|
fromKeyboard = fromKeyboard || false;
|
||||||
@@ -27,15 +30,14 @@ PanelWindow {
|
|||||||
let screenX = x;
|
let screenX = x;
|
||||||
let screenY = y;
|
let screenY = y;
|
||||||
|
|
||||||
const modalX = DankModalWindow.modalX;
|
if (parentModal) {
|
||||||
const modalY = DankModalWindow.modalY;
|
|
||||||
|
|
||||||
if (fromKeyboard) {
|
if (fromKeyboard) {
|
||||||
screenX = x + modalX;
|
screenX = x + parentModal.alignedX;
|
||||||
screenY = y + modalY;
|
screenY = y + parentModal.alignedY;
|
||||||
} else {
|
} else {
|
||||||
screenX = x + (modalX - shadowBuffer);
|
screenX = x + (parentModal.alignedX - shadowBuffer);
|
||||||
screenY = y + (modalY - shadowBuffer);
|
screenY = y + (parentModal.alignedY - shadowBuffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
menuPositionX = screenX;
|
menuPositionX = screenX;
|
||||||
@@ -44,32 +46,19 @@ PanelWindow {
|
|||||||
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
|
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
|
||||||
menuContent.keyboardNavigation = true;
|
menuContent.keyboardNavigation = true;
|
||||||
visible = true;
|
visible = true;
|
||||||
}
|
|
||||||
|
|
||||||
function handleKey(event) {
|
if (parentHandler) {
|
||||||
switch (event.key) {
|
parentHandler.enabled = false;
|
||||||
case Qt.Key_Down:
|
|
||||||
menuContent.selectNext();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
case Qt.Key_Up:
|
|
||||||
menuContent.selectPrevious();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
case Qt.Key_Return:
|
|
||||||
case Qt.Key_Enter:
|
|
||||||
menuContent.activateSelected();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
case Qt.Key_Escape:
|
|
||||||
case Qt.Key_Menu:
|
|
||||||
hide();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
Qt.callLater(() => {
|
||||||
|
menuContent.keyboardHandler.forceActiveFocus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
|
if (parentHandler) {
|
||||||
|
parentHandler.enabled = true;
|
||||||
|
}
|
||||||
visible = false;
|
visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +71,11 @@ PanelWindow {
|
|||||||
bottom: true
|
bottom: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (!visible && parentHandler) {
|
||||||
|
parentHandler.enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SpotlightContextMenuContent {
|
SpotlightContextMenuContent {
|
||||||
id: menuContent
|
id: menuContent
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Popup {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var appLauncher: null
|
property var appLauncher: null
|
||||||
|
property var parentHandler: null
|
||||||
property var searchField: null
|
property var searchField: null
|
||||||
|
|
||||||
function show(x, y, app, fromKeyboard) {
|
function show(x, y, app, fromKeyboard) {
|
||||||
@@ -20,45 +21,38 @@ Popup {
|
|||||||
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
|
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
|
||||||
menuContent.keyboardNavigation = true;
|
menuContent.keyboardNavigation = true;
|
||||||
|
|
||||||
|
if (parentHandler) {
|
||||||
|
parentHandler.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKey(event) {
|
onOpened: {
|
||||||
switch (event.key) {
|
Qt.callLater(() => {
|
||||||
case Qt.Key_Down:
|
menuContent.keyboardHandler.forceActiveFocus();
|
||||||
menuContent.selectNext();
|
});
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
case Qt.Key_Up:
|
|
||||||
menuContent.selectPrevious();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
case Qt.Key_Return:
|
|
||||||
case Qt.Key_Enter:
|
|
||||||
menuContent.activateSelected();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
case Qt.Key_Escape:
|
|
||||||
case Qt.Key_Menu:
|
|
||||||
hide();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
|
if (parentHandler) {
|
||||||
|
parentHandler.enabled = true;
|
||||||
|
}
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
width: menuContent.implicitWidth
|
width: menuContent.implicitWidth
|
||||||
height: menuContent.implicitHeight
|
height: menuContent.implicitHeight
|
||||||
padding: 0
|
padding: 0
|
||||||
closePolicy: Popup.CloseOnPressOutside
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
modal: true
|
modal: true
|
||||||
dim: false
|
dim: false
|
||||||
background: Item {}
|
background: Item {}
|
||||||
|
|
||||||
onClosed: {
|
onClosed: {
|
||||||
|
if (parentHandler) {
|
||||||
|
parentHandler.enabled = true;
|
||||||
|
}
|
||||||
if (searchField) {
|
if (searchField) {
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
searchField.forceActiveFocus();
|
searchField.forceActiveFocus();
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell.Hyprland
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
DankModal {
|
DankModal {
|
||||||
id: spotlightModal
|
id: spotlightModal
|
||||||
|
|
||||||
layerNamespace: "dms:spotlight"
|
layerNamespace: "dms:spotlight"
|
||||||
|
|
||||||
|
HyprlandFocusGrab {
|
||||||
|
windows: [spotlightModal.contentWindow]
|
||||||
|
active: CompositorService.isHyprland && spotlightModal.shouldHaveFocus
|
||||||
|
}
|
||||||
|
|
||||||
property bool spotlightOpen: false
|
property bool spotlightOpen: false
|
||||||
property alias spotlightContent: spotlightContentInstance
|
property alias spotlightContent: spotlightContentInstance
|
||||||
property bool openedFromOverview: false
|
property bool openedFromOverview: false
|
||||||
@@ -16,18 +23,32 @@ DankModal {
|
|||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
spotlightOpen = true;
|
spotlightOpen = true;
|
||||||
open();
|
open();
|
||||||
|
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (spotlightContent && spotlightContent.searchField) {
|
||||||
|
spotlightContent.searchField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showWithQuery(query) {
|
function showWithQuery(query) {
|
||||||
if (spotlightContent) {
|
if (spotlightContent) {
|
||||||
if (spotlightContent.appLauncher)
|
if (spotlightContent.appLauncher) {
|
||||||
spotlightContent.appLauncher.searchQuery = query;
|
spotlightContent.appLauncher.searchQuery = query;
|
||||||
if (spotlightContent.searchField)
|
}
|
||||||
|
if (spotlightContent.searchField) {
|
||||||
spotlightContent.searchField.text = query;
|
spotlightContent.searchField.text = query;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
spotlightOpen = true;
|
spotlightOpen = true;
|
||||||
open();
|
open();
|
||||||
|
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (spotlightContent && spotlightContent.searchField) {
|
||||||
|
spotlightContent.searchField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
@@ -36,25 +57,24 @@ DankModal {
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFullyClosed() {
|
onDialogClosed: {
|
||||||
resetContent();
|
if (spotlightContent) {
|
||||||
}
|
|
||||||
|
|
||||||
function resetContent() {
|
|
||||||
if (!spotlightContent)
|
|
||||||
return;
|
|
||||||
if (spotlightContent.appLauncher) {
|
if (spotlightContent.appLauncher) {
|
||||||
spotlightContent.appLauncher.searchQuery = "";
|
spotlightContent.appLauncher.searchQuery = "";
|
||||||
spotlightContent.appLauncher.selectedIndex = 0;
|
spotlightContent.appLauncher.selectedIndex = 0;
|
||||||
spotlightContent.appLauncher.setCategory(I18n.tr("All"));
|
spotlightContent.appLauncher.setCategory(I18n.tr("All"));
|
||||||
}
|
}
|
||||||
if (spotlightContent.fileSearchController)
|
if (spotlightContent.fileSearchController) {
|
||||||
spotlightContent.fileSearchController.reset();
|
spotlightContent.fileSearchController.reset();
|
||||||
if (spotlightContent.resetScroll)
|
}
|
||||||
|
if (spotlightContent.resetScroll) {
|
||||||
spotlightContent.resetScroll();
|
spotlightContent.resetScroll();
|
||||||
if (spotlightContent.searchField)
|
}
|
||||||
|
if (spotlightContent.searchField) {
|
||||||
spotlightContent.searchField.text = "";
|
spotlightContent.searchField.text = "";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
if (spotlightOpen) {
|
if (spotlightOpen) {
|
||||||
@@ -78,11 +98,17 @@ DankModal {
|
|||||||
animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||||
animationExitCurve: Theme.expressiveCurves.emphasized
|
animationExitCurve: Theme.expressiveCurves.emphasized
|
||||||
onVisibleChanged: () => {
|
onVisibleChanged: () => {
|
||||||
if (!visible)
|
if (visible && !spotlightOpen) {
|
||||||
return;
|
|
||||||
if (!spotlightOpen)
|
|
||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
|
if (visible && spotlightContent) {
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (spotlightContent.searchField) {
|
||||||
|
spotlightContent.searchField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
onBackgroundClicked: () => {
|
onBackgroundClicked: () => {
|
||||||
return hide();
|
return hide();
|
||||||
}
|
}
|
||||||
@@ -130,10 +156,6 @@ DankModal {
|
|||||||
target: "spotlight"
|
target: "spotlight"
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
DankModalWindow.persistentModal = spotlightModal;
|
|
||||||
}
|
|
||||||
|
|
||||||
SpotlightContent {
|
SpotlightContent {
|
||||||
id: spotlightContentInstance
|
id: spotlightContentInstance
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import QtQuick.Controls
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Modals.Spotlight
|
||||||
import qs.Modules.AppDrawer
|
import qs.Modules.AppDrawer
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -12,6 +13,27 @@ DankPopout {
|
|||||||
|
|
||||||
layerNamespace: "dms:app-launcher"
|
layerNamespace: "dms:app-launcher"
|
||||||
|
|
||||||
|
property string searchMode: "apps"
|
||||||
|
property alias fileSearch: fileSearchController
|
||||||
|
|
||||||
|
function updateSearchMode(text) {
|
||||||
|
if (text.startsWith("/")) {
|
||||||
|
if (searchMode === "files") {
|
||||||
|
fileSearchController.searchQuery = text.substring(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
searchMode = "files";
|
||||||
|
fileSearchController.searchQuery = text.substring(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (searchMode === "apps") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
searchMode = "apps";
|
||||||
|
fileSearchController.reset();
|
||||||
|
appLauncher.searchQuery = text;
|
||||||
|
}
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
@@ -29,9 +51,11 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
|
searchMode = "apps";
|
||||||
appLauncher.searchQuery = "";
|
appLauncher.searchQuery = "";
|
||||||
appLauncher.selectedIndex = 0;
|
appLauncher.selectedIndex = 0;
|
||||||
appLauncher.setCategory(I18n.tr("All"));
|
appLauncher.setCategory(I18n.tr("All"));
|
||||||
|
fileSearchController.reset();
|
||||||
if (contentLoader.item?.searchField) {
|
if (contentLoader.item?.searchField) {
|
||||||
contentLoader.item.searchField.text = "";
|
contentLoader.item.searchField.text = "";
|
||||||
contentLoader.item.searchField.forceActiveFocus();
|
contentLoader.item.searchField.forceActiveFocus();
|
||||||
@@ -50,6 +74,23 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileSearchController {
|
||||||
|
id: fileSearchController
|
||||||
|
|
||||||
|
onFileOpened: appDrawerPopout.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearchModeChanged: {
|
||||||
|
switch (searchMode) {
|
||||||
|
case "files":
|
||||||
|
appLauncher.keyboardNavigationActive = false;
|
||||||
|
break;
|
||||||
|
case "apps":
|
||||||
|
fileSearchController.keyboardNavigationActive = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: launcherPanel
|
id: launcherPanel
|
||||||
@@ -96,17 +137,48 @@ DankPopout {
|
|||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
|
function selectNext() {
|
||||||
|
switch (appDrawerPopout.searchMode) {
|
||||||
|
case "files":
|
||||||
|
fileSearchController.selectNext();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
appLauncher.selectNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPrevious() {
|
||||||
|
switch (appDrawerPopout.searchMode) {
|
||||||
|
case "files":
|
||||||
|
fileSearchController.selectPrevious();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
appLauncher.selectPrevious();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateSelected() {
|
||||||
|
switch (appDrawerPopout.searchMode) {
|
||||||
|
case "files":
|
||||||
|
fileSearchController.openSelected();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
appLauncher.launchSelected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var keyMappings: {
|
readonly property var keyMappings: {
|
||||||
const mappings = {};
|
const mappings = {};
|
||||||
mappings[Qt.Key_Escape] = () => appDrawerPopout.close();
|
mappings[Qt.Key_Escape] = () => appDrawerPopout.close();
|
||||||
mappings[Qt.Key_Down] = () => appLauncher.selectNext();
|
mappings[Qt.Key_Down] = () => keyHandler.selectNext();
|
||||||
mappings[Qt.Key_Up] = () => appLauncher.selectPrevious();
|
mappings[Qt.Key_Up] = () => keyHandler.selectPrevious();
|
||||||
mappings[Qt.Key_Return] = () => appLauncher.launchSelected();
|
mappings[Qt.Key_Return] = () => keyHandler.activateSelected();
|
||||||
mappings[Qt.Key_Enter] = () => appLauncher.launchSelected();
|
mappings[Qt.Key_Enter] = () => keyHandler.activateSelected();
|
||||||
mappings[Qt.Key_Tab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectNextInRow() : appLauncher.selectNext();
|
mappings[Qt.Key_Tab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectNextInRow() : keyHandler.selectNext();
|
||||||
mappings[Qt.Key_Backtab] = () => appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : appLauncher.selectPrevious();
|
mappings[Qt.Key_Backtab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : keyHandler.selectPrevious();
|
||||||
|
|
||||||
if (appLauncher.viewMode === "grid") {
|
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||||
mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow();
|
mappings[Qt.Key_Right] = () => appLauncher.selectNextInRow();
|
||||||
mappings[Qt.Key_Left] = () => appLauncher.selectPreviousInRow();
|
mappings[Qt.Key_Left] = () => appLauncher.selectPreviousInRow();
|
||||||
}
|
}
|
||||||
@@ -121,42 +193,34 @@ DankPopout {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
|
const hasCtrl = event.modifiers & Qt.ControlModifier;
|
||||||
appLauncher.selectNext();
|
if (!hasCtrl) {
|
||||||
event.accepted = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
|
switch (event.key) {
|
||||||
appLauncher.selectPrevious();
|
case Qt.Key_N:
|
||||||
|
case Qt.Key_J:
|
||||||
|
keyHandler.selectNext();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
case Qt.Key_P:
|
||||||
|
case Qt.Key_K:
|
||||||
if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
keyHandler.selectPrevious();
|
||||||
appLauncher.selectNext();
|
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
case Qt.Key_L:
|
||||||
|
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||||
if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
|
||||||
appLauncher.selectPrevious();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appLauncher.viewMode === "grid") {
|
|
||||||
if (event.key === Qt.Key_L && event.modifiers & Qt.ControlModifier) {
|
|
||||||
appLauncher.selectNextInRow();
|
appLauncher.selectNextInRow();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
if (event.key === Qt.Key_H && event.modifiers & Qt.ControlModifier) {
|
case Qt.Key_H:
|
||||||
|
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||||
appLauncher.selectPreviousInRow();
|
appLauncher.selectPreviousInRow();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +239,7 @@ DankPopout {
|
|||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.spacingS
|
anchors.leftMargin: Theme.spacingS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: I18n.tr("Applications")
|
text: appDrawerPopout.searchMode === "files" ? I18n.tr("Files") : I18n.tr("Applications")
|
||||||
font.pixelSize: Theme.fontSizeLarge + 4
|
font.pixelSize: Theme.fontSizeLarge + 4
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
@@ -185,7 +249,14 @@ DankPopout {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: Theme.spacingS
|
anchors.rightMargin: Theme.spacingS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: appLauncher.model.count + " apps"
|
text: {
|
||||||
|
switch (appDrawerPopout.searchMode) {
|
||||||
|
case "files":
|
||||||
|
return fileSearchController.model.count + " " + I18n.tr("files");
|
||||||
|
default:
|
||||||
|
return appLauncher.model.count + " " + I18n.tr("apps");
|
||||||
|
}
|
||||||
|
}
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
}
|
}
|
||||||
@@ -201,19 +272,24 @@ DankPopout {
|
|||||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
normalBorderColor: Theme.outlineMedium
|
normalBorderColor: Theme.outlineMedium
|
||||||
focusedBorderColor: Theme.primary
|
focusedBorderColor: Theme.primary
|
||||||
leftIconName: "search"
|
leftIconName: appDrawerPopout.searchMode === "files" ? "folder" : "search"
|
||||||
leftIconSize: Theme.iconSize
|
leftIconSize: Theme.iconSize
|
||||||
leftIconColor: Theme.surfaceVariantText
|
leftIconColor: Theme.surfaceVariantText
|
||||||
leftIconFocusedColor: Theme.primary
|
leftIconFocusedColor: Theme.primary
|
||||||
showClearButton: true
|
showClearButton: true
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
enabled: appDrawerPopout.shouldBeVisible
|
enabled: appDrawerPopout.shouldBeVisible
|
||||||
ignoreLeftRightKeys: appLauncher.viewMode !== "list"
|
ignoreLeftRightKeys: appDrawerPopout.searchMode === "apps" && appLauncher.viewMode !== "list"
|
||||||
ignoreTabKeys: true
|
ignoreTabKeys: true
|
||||||
keyForwardTargets: [keyHandler]
|
keyForwardTargets: [keyHandler]
|
||||||
onTextEdited: {
|
onTextChanged: {
|
||||||
|
if (appDrawerPopout.searchMode === "apps") {
|
||||||
appLauncher.searchQuery = text;
|
appLauncher.searchQuery = text;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
onTextEdited: {
|
||||||
|
appDrawerPopout.updateSearchMode(text);
|
||||||
|
}
|
||||||
Keys.onPressed: function (event) {
|
Keys.onPressed: function (event) {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
appDrawerPopout.close();
|
appDrawerPopout.close();
|
||||||
@@ -225,6 +301,14 @@ DankPopout {
|
|||||||
const hasText = text.length > 0;
|
const hasText = text.length > 0;
|
||||||
|
|
||||||
if (isEnterKey && hasText) {
|
if (isEnterKey && hasText) {
|
||||||
|
switch (appDrawerPopout.searchMode) {
|
||||||
|
case "files":
|
||||||
|
if (fileSearchController.model.count > 0) {
|
||||||
|
fileSearchController.openSelected();
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
default:
|
||||||
if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0) {
|
if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0) {
|
||||||
appLauncher.launchSelected();
|
appLauncher.launchSelected();
|
||||||
} else if (appLauncher.model.count > 0) {
|
} else if (appLauncher.model.count > 0) {
|
||||||
@@ -233,6 +317,7 @@ DankPopout {
|
|||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right, Qt.Key_Tab, Qt.Key_Backtab];
|
const navigationKeys = [Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right, Qt.Key_Tab, Qt.Key_Backtab];
|
||||||
const isNavigationKey = navigationKeys.includes(event.key);
|
const isNavigationKey = navigationKeys.includes(event.key);
|
||||||
@@ -256,7 +341,7 @@ DankPopout {
|
|||||||
width: parent.width - Theme.spacingS * 2
|
width: parent.width - Theme.spacingS * 2
|
||||||
height: 40
|
height: 40
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: searchField.text.length === 0
|
visible: searchField.text.length === 0 && appDrawerPopout.searchMode === "apps"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 180
|
width: 180
|
||||||
@@ -316,7 +401,7 @@ DankPopout {
|
|||||||
height: {
|
height: {
|
||||||
let usedHeight = 40 + Theme.spacingS;
|
let usedHeight = 40 + Theme.spacingS;
|
||||||
usedHeight += 52 + Theme.spacingS;
|
usedHeight += 52 + Theme.spacingS;
|
||||||
usedHeight += (searchField.text.length === 0 ? 40 : 0);
|
usedHeight += (searchField.text.length === 0 && appDrawerPopout.searchMode === "apps" ? 40 : 0);
|
||||||
return parent.height - usedHeight;
|
return parent.height - usedHeight;
|
||||||
}
|
}
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
@@ -349,7 +434,7 @@ DankPopout {
|
|||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.bottomMargin: Theme.spacingS
|
anchors.bottomMargin: Theme.spacingS
|
||||||
visible: appLauncher.viewMode === "list"
|
visible: appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "list"
|
||||||
model: appLauncher.model
|
model: appLauncher.model
|
||||||
currentIndex: appLauncher.selectedIndex
|
currentIndex: appLauncher.selectedIndex
|
||||||
clip: true
|
clip: true
|
||||||
@@ -434,7 +519,7 @@ DankPopout {
|
|||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.bottomMargin: Theme.spacingS
|
anchors.bottomMargin: Theme.spacingS
|
||||||
visible: appLauncher.viewMode === "grid"
|
visible: appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid"
|
||||||
model: appLauncher.model
|
model: appLauncher.model
|
||||||
clip: true
|
clip: true
|
||||||
cellWidth: baseCellWidth
|
cellWidth: baseCellWidth
|
||||||
@@ -484,6 +569,12 @@ DankPopout {
|
|||||||
onKeyboardNavigationReset: appGrid.keyboardNavigationReset
|
onKeyboardNavigationReset: appGrid.keyboardNavigationReset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileSearchResults {
|
||||||
|
anchors.fill: parent
|
||||||
|
fileSearchController: appDrawerPopout.fileSearch
|
||||||
|
visible: appDrawerPopout.searchMode === "files"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ Item {
|
|||||||
property int _lastDataVersion: -1
|
property int _lastDataVersion: -1
|
||||||
property var _cachedCategories: []
|
property var _cachedCategories: []
|
||||||
property var _filteredBinds: []
|
property var _filteredBinds: []
|
||||||
|
property real _savedScrollY: 0
|
||||||
|
property bool _preserveScroll: false
|
||||||
|
property string _editingKey: ""
|
||||||
|
|
||||||
function _updateFiltered() {
|
function _updateFiltered() {
|
||||||
const allBinds = KeybindsService.getFlatBinds();
|
const allBinds = KeybindsService.getFlatBinds();
|
||||||
@@ -87,6 +90,9 @@ Item {
|
|||||||
function saveNewBind(bindData) {
|
function saveNewBind(bindData) {
|
||||||
KeybindsService.saveBind("", bindData);
|
KeybindsService.saveBind("", bindData);
|
||||||
showingNewBind = false;
|
showingNewBind = false;
|
||||||
|
selectedCategory = "";
|
||||||
|
_editingKey = bindData.key;
|
||||||
|
expandedKey = bindData.action;
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToTop() {
|
function scrollToTop() {
|
||||||
@@ -102,9 +108,24 @@ Item {
|
|||||||
Connections {
|
Connections {
|
||||||
target: KeybindsService
|
target: KeybindsService
|
||||||
function onBindsLoaded() {
|
function onBindsLoaded() {
|
||||||
|
const savedY = keybindsTab._savedScrollY;
|
||||||
|
const wasPreserving = keybindsTab._preserveScroll;
|
||||||
keybindsTab._lastDataVersion = KeybindsService._dataVersion;
|
keybindsTab._lastDataVersion = KeybindsService._dataVersion;
|
||||||
keybindsTab._updateCategories();
|
keybindsTab._updateCategories();
|
||||||
keybindsTab._updateFiltered();
|
keybindsTab._updateFiltered();
|
||||||
|
keybindsTab._preserveScroll = false;
|
||||||
|
if (wasPreserving)
|
||||||
|
Qt.callLater(() => flickable.contentY = savedY);
|
||||||
|
}
|
||||||
|
function onBindSaved(key) {
|
||||||
|
keybindsTab._savedScrollY = flickable.contentY;
|
||||||
|
keybindsTab._preserveScroll = true;
|
||||||
|
keybindsTab._editingKey = key;
|
||||||
|
}
|
||||||
|
function onBindRemoved(key) {
|
||||||
|
keybindsTab._savedScrollY = flickable.contentY;
|
||||||
|
keybindsTab._preserveScroll = true;
|
||||||
|
keybindsTab._editingKey = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,14 +249,33 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledRect {
|
StyledRect {
|
||||||
|
id: warningBox
|
||||||
width: Math.min(650, parent.width - Theme.spacingL * 2)
|
width: Math.min(650, parent.width - Theme.spacingL * 2)
|
||||||
height: warningSection.implicitHeight + Theme.spacingL * 2
|
height: warningSection.implicitHeight + Theme.spacingL * 2
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.withAlpha(Theme.error, 0.15)
|
|
||||||
border.color: Theme.withAlpha(Theme.error, 0.3)
|
readonly property var status: KeybindsService.dmsStatus
|
||||||
|
readonly property bool showError: !status.included && status.exists
|
||||||
|
readonly property bool showWarning: status.included && status.overriddenBy > 0
|
||||||
|
readonly property bool showSetup: !status.exists
|
||||||
|
|
||||||
|
color: {
|
||||||
|
if (showError || showSetup)
|
||||||
|
return Theme.withAlpha(Theme.error, 0.15);
|
||||||
|
if (showWarning)
|
||||||
|
return Theme.withAlpha(Theme.warning ?? Theme.tertiary, 0.15);
|
||||||
|
return "transparent";
|
||||||
|
}
|
||||||
|
border.color: {
|
||||||
|
if (showError || showSetup)
|
||||||
|
return Theme.withAlpha(Theme.error, 0.3);
|
||||||
|
if (showWarning)
|
||||||
|
return Theme.withAlpha(Theme.warning ?? Theme.tertiary, 0.3);
|
||||||
|
return "transparent";
|
||||||
|
}
|
||||||
border.width: 1
|
border.width: 1
|
||||||
visible: !KeybindsService.dmsBindsIncluded && !KeybindsService.loading
|
visible: (showError || showWarning || showSetup) && !KeybindsService.loading
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: warningSection
|
id: warningSection
|
||||||
@@ -248,26 +288,44 @@ Item {
|
|||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "warning"
|
name: warningBox.showWarning ? "info" : "warning"
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
color: Theme.error
|
color: warningBox.showWarning ? (Theme.warning ?? Theme.tertiary) : Theme.error
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width - Theme.iconSize - 100 - Theme.spacingM * 2
|
width: parent.width - Theme.iconSize - (fixButton.visible ? fixButton.width + Theme.spacingM : 0) - Theme.spacingM
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("Binds Include Missing")
|
text: {
|
||||||
|
if (warningBox.showSetup)
|
||||||
|
return I18n.tr("First Time Setup");
|
||||||
|
if (warningBox.showError)
|
||||||
|
return I18n.tr("Binds Include Missing");
|
||||||
|
if (warningBox.showWarning)
|
||||||
|
return I18n.tr("Possible Override Conflicts");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.error
|
color: warningBox.showWarning ? (Theme.warning ?? Theme.tertiary) : Theme.error
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("dms/binds.kdl is not included in config.kdl. Custom keybinds will not work until this is fixed.")
|
text: {
|
||||||
|
if (warningBox.showSetup)
|
||||||
|
return I18n.tr("Click 'Setup' to create dms/binds.kdl and add include to config.kdl.");
|
||||||
|
if (warningBox.showError)
|
||||||
|
return I18n.tr("dms/binds.kdl exists but is not included in config.kdl. Custom keybinds will not work until this is fixed.");
|
||||||
|
if (warningBox.showWarning) {
|
||||||
|
const count = warningBox.status.overriddenBy;
|
||||||
|
return I18n.tr("%1 DMS bind(s) may be overridden by config binds that come after the include.").arg(count);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
@@ -280,12 +338,19 @@ Item {
|
|||||||
width: fixButtonText.implicitWidth + Theme.spacingL * 2
|
width: fixButtonText.implicitWidth + Theme.spacingL * 2
|
||||||
height: 36
|
height: 36
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
|
visible: warningBox.showError || warningBox.showSetup
|
||||||
color: KeybindsService.fixing ? Theme.withAlpha(Theme.error, 0.6) : Theme.error
|
color: KeybindsService.fixing ? Theme.withAlpha(Theme.error, 0.6) : Theme.error
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: fixButtonText
|
id: fixButtonText
|
||||||
text: KeybindsService.fixing ? I18n.tr("Fixing...") : I18n.tr("Fix Now")
|
text: {
|
||||||
|
if (KeybindsService.fixing)
|
||||||
|
return I18n.tr("Fixing...");
|
||||||
|
if (warningBox.showSetup)
|
||||||
|
return I18n.tr("Setup");
|
||||||
|
return I18n.tr("Fix Now");
|
||||||
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: Theme.surface
|
color: Theme.surface
|
||||||
@@ -532,6 +597,7 @@ Item {
|
|||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
bindData: modelData
|
bindData: modelData
|
||||||
isExpanded: keybindsTab.expandedKey === modelData.action
|
isExpanded: keybindsTab.expandedKey === modelData.action
|
||||||
|
restoreKey: isExpanded ? keybindsTab._editingKey : ""
|
||||||
panelWindow: keybindsTab.parentModal
|
panelWindow: keybindsTab.parentModal
|
||||||
onToggleExpand: keybindsTab.toggleExpanded(modelData.action)
|
onToggleExpand: keybindsTab.toggleExpanded(modelData.action)
|
||||||
onSaveBind: (originalKey, newData) => {
|
onSaveBind: (originalKey, newData) => {
|
||||||
@@ -539,6 +605,7 @@ Item {
|
|||||||
keybindsTab.expandedKey = modelData.action;
|
keybindsTab.expandedKey = modelData.action;
|
||||||
}
|
}
|
||||||
onRemoveBind: key => KeybindsService.removeBind(key)
|
onRemoveBind: key => KeybindsService.removeBind(key)
|
||||||
|
onRestoreKeyConsumed: keybindsTab._editingKey = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,10 @@ FloatingWindow {
|
|||||||
if (!parentModal)
|
if (!parentModal)
|
||||||
return;
|
return;
|
||||||
parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible);
|
parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible);
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (parentModal.modalFocusScope)
|
||||||
|
parentModal.modalFocusScope.forceActiveFocus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
objectName: "pluginBrowser"
|
objectName: "pluginBrowser"
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ FloatingWindow {
|
|||||||
if (!parentModal)
|
if (!parentModal)
|
||||||
return;
|
return;
|
||||||
parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible);
|
parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible);
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (parentModal && parentModal.modalFocusScope)
|
||||||
|
parentModal.modalFocusScope.forceActiveFocus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
objectName: "widgetSelectionPopup"
|
objectName: "widgetSelectionPopup"
|
||||||
@@ -108,6 +112,10 @@ FloatingWindow {
|
|||||||
if (!parentModal)
|
if (!parentModal)
|
||||||
return;
|
return;
|
||||||
parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible);
|
parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible);
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (parentModal && parentModal.modalFocusScope)
|
||||||
|
parentModal.modalFocusScope.forceActiveFocus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
id: overviewScope
|
id: overviewScope
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import QtQuick
|
|||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -13,6 +12,7 @@ Item {
|
|||||||
required property var panelWindow
|
required property var panelWindow
|
||||||
required property bool overviewOpen
|
required property bool overviewOpen
|
||||||
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
|
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
|
||||||
|
readonly property real dpr: CompositorService.getScreenScale(panelWindow.screen)
|
||||||
readonly property int workspacesShown: SettingsData.overviewRows * SettingsData.overviewColumns
|
readonly property int workspacesShown: SettingsData.overviewRows * SettingsData.overviewColumns
|
||||||
|
|
||||||
readonly property var allWorkspaces: Hyprland.workspaces?.values || []
|
readonly property var allWorkspaces: Hyprland.workspaces?.values || []
|
||||||
@@ -77,6 +77,9 @@ Item {
|
|||||||
readonly property int maxWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[displayedWorkspaceIds.length - 1] : workspacesShown
|
readonly property int maxWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[displayedWorkspaceIds.length - 1] : workspacesShown
|
||||||
readonly property int displayWorkspaceCount: displayedWorkspaceIds.length
|
readonly property int displayWorkspaceCount: displayedWorkspaceIds.length
|
||||||
|
|
||||||
|
readonly property int effectiveColumns: SettingsData.overviewColumns
|
||||||
|
readonly property int effectiveRows: Math.max(SettingsData.overviewRows, Math.ceil(displayWorkspaceCount / effectiveColumns))
|
||||||
|
|
||||||
function getWorkspaceMonitorName(workspaceId) {
|
function getWorkspaceMonitorName(workspaceId) {
|
||||||
if (!allWorkspaces || !workspaceId) return ""
|
if (!allWorkspaces || !workspaceId) return ""
|
||||||
try {
|
try {
|
||||||
@@ -103,8 +106,10 @@ Item {
|
|||||||
property real scale: SettingsData.overviewScale
|
property real scale: SettingsData.overviewScale
|
||||||
property color activeBorderColor: Theme.primary
|
property color activeBorderColor: Theme.primary
|
||||||
|
|
||||||
property real workspaceImplicitWidth: ((monitor.width / monitor.scale) * root.scale)
|
readonly property real monitorPhysicalWidth: panelWindow.screen ? (panelWindow.screen.width / root.dpr) : (monitor?.width ?? 1920)
|
||||||
property real workspaceImplicitHeight: ((monitor.height / monitor.scale) * root.scale)
|
readonly property real monitorPhysicalHeight: panelWindow.screen ? (panelWindow.screen.height / root.dpr) : (monitor?.height ?? 1080)
|
||||||
|
property real workspaceImplicitWidth: monitorPhysicalWidth * root.scale
|
||||||
|
property real workspaceImplicitHeight: monitorPhysicalHeight * root.scale
|
||||||
|
|
||||||
property int workspaceZ: 0
|
property int workspaceZ: 0
|
||||||
property int windowZ: 1
|
property int windowZ: 1
|
||||||
@@ -162,18 +167,18 @@ Item {
|
|||||||
spacing: workspaceSpacing
|
spacing: workspaceSpacing
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: SettingsData.overviewRows
|
model: root.effectiveRows
|
||||||
delegate: RowLayout {
|
delegate: RowLayout {
|
||||||
id: row
|
id: row
|
||||||
property int rowIndex: index
|
property int rowIndex: index
|
||||||
spacing: workspaceSpacing
|
spacing: workspaceSpacing
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: SettingsData.overviewColumns
|
model: root.effectiveColumns
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: workspace
|
id: workspace
|
||||||
property int colIndex: index
|
property int colIndex: index
|
||||||
property int workspaceIndex: rowIndex * SettingsData.overviewColumns + colIndex
|
property int workspaceIndex: rowIndex * root.effectiveColumns + colIndex
|
||||||
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1
|
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1
|
||||||
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
|
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
|
||||||
property var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null
|
property var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null
|
||||||
@@ -292,11 +297,12 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly property int workspaceIndex: getWorkspaceIndex()
|
readonly property int workspaceIndex: getWorkspaceIndex()
|
||||||
readonly property int workspaceColIndex: workspaceIndex % SettingsData.overviewColumns
|
readonly property int workspaceColIndex: workspaceIndex % root.effectiveColumns
|
||||||
readonly property int workspaceRowIndex: Math.floor(workspaceIndex / SettingsData.overviewColumns)
|
readonly property int workspaceRowIndex: Math.floor(workspaceIndex / root.effectiveColumns)
|
||||||
|
|
||||||
toplevel: modelData
|
toplevel: modelData
|
||||||
scale: root.scale
|
scale: root.scale
|
||||||
|
monitorDpr: root.dpr
|
||||||
availableWorkspaceWidth: root.workspaceImplicitWidth
|
availableWorkspaceWidth: root.workspaceImplicitWidth
|
||||||
availableWorkspaceHeight: root.workspaceImplicitHeight
|
availableWorkspaceHeight: root.workspaceImplicitHeight
|
||||||
widgetMonitorId: root.monitor.id
|
widgetMonitorId: root.monitor.id
|
||||||
@@ -376,7 +382,7 @@ Item {
|
|||||||
z: root.monitorLabelZ
|
z: root.monitorLabelZ
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: SettingsData.overviewRows
|
model: root.effectiveRows
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
id: labelRow
|
id: labelRow
|
||||||
property int rowIndex: index
|
property int rowIndex: index
|
||||||
@@ -385,11 +391,11 @@ Item {
|
|||||||
height: root.workspaceImplicitHeight
|
height: root.workspaceImplicitHeight
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: SettingsData.overviewColumns
|
model: root.effectiveColumns
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
id: labelItem
|
id: labelItem
|
||||||
property int colIndex: index
|
property int colIndex: index
|
||||||
property int workspaceIndex: labelRow.rowIndex * SettingsData.overviewColumns + colIndex
|
property int workspaceIndex: labelRow.rowIndex * root.effectiveColumns + colIndex
|
||||||
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1
|
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1
|
||||||
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
|
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
|
||||||
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
|
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
|
||||||
|
|||||||
@@ -13,19 +13,21 @@ Item {
|
|||||||
property var availableWorkspaceWidth
|
property var availableWorkspaceWidth
|
||||||
property var availableWorkspaceHeight
|
property var availableWorkspaceHeight
|
||||||
property bool restrictToWorkspace: true
|
property bool restrictToWorkspace: true
|
||||||
|
property real monitorDpr: 1
|
||||||
|
|
||||||
readonly property var windowData: toplevel?.lastIpcObject || null
|
readonly property var windowData: toplevel?.lastIpcObject || null
|
||||||
readonly property var monitorObj: toplevel?.monitor
|
readonly property var monitorObj: toplevel?.monitor
|
||||||
readonly property var monitorData: monitorObj?.lastIpcObject || null
|
readonly property var monitorData: monitorObj?.lastIpcObject || null
|
||||||
|
readonly property real effectiveScale: root.scale / root.monitorDpr
|
||||||
|
|
||||||
property real initX: Math.max(((windowData?.at?.[0] ?? 0) - (monitorData?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale, 0) + xOffset
|
property real initX: Math.max(((windowData?.at?.[0] ?? 0) - (monitorData?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * effectiveScale, 0) + xOffset
|
||||||
property real initY: Math.max(((windowData?.at?.[1] ?? 0) - (monitorData?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale, 0) + yOffset
|
property real initY: Math.max(((windowData?.at?.[1] ?? 0) - (monitorData?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * effectiveScale, 0) + yOffset
|
||||||
property real xOffset: 0
|
property real xOffset: 0
|
||||||
property real yOffset: 0
|
property real yOffset: 0
|
||||||
property int widgetMonitorId: 0
|
property int widgetMonitorId: 0
|
||||||
|
|
||||||
property var targetWindowWidth: (windowData?.size?.[0] ?? 100) * scale
|
property var targetWindowWidth: (windowData?.size?.[0] ?? 100) * effectiveScale
|
||||||
property var targetWindowHeight: (windowData?.size?.[1] ?? 100) * scale
|
property var targetWindowHeight: (windowData?.size?.[1] ?? 100) * effectiveScale
|
||||||
property bool hovered: false
|
property bool hovered: false
|
||||||
property bool pressed: false
|
property bool pressed: false
|
||||||
|
|
||||||
@@ -37,8 +39,8 @@ Item {
|
|||||||
|
|
||||||
x: initX
|
x: initX
|
||||||
y: initY
|
y: initY
|
||||||
width: Math.min((windowData?.size?.[0] ?? 100) * root.scale, availableWorkspaceWidth)
|
width: Math.min((windowData?.size?.[0] ?? 100) * effectiveScale, availableWorkspaceWidth)
|
||||||
height: Math.min((windowData?.size?.[1] ?? 100) * root.scale, availableWorkspaceHeight)
|
height: Math.min((windowData?.size?.[1] ?? 100) * effectiveScale, availableWorkspaceHeight)
|
||||||
opacity: (monitorObj?.id ?? -1) == widgetMonitorId ? 1 : 0.4
|
opacity: (monitorObj?.id ?? -1) == widgetMonitorId ? 1 : 0.4
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.I3
|
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
@@ -24,31 +23,6 @@ Singleton {
|
|||||||
readonly property string labwcPid: Quickshell.env("LABWC_PID")
|
readonly property string labwcPid: Quickshell.env("LABWC_PID")
|
||||||
property bool useNiriSorting: isNiri && NiriService
|
property bool useNiriSorting: isNiri && NiriService
|
||||||
|
|
||||||
readonly property string focusedScreenName: {
|
|
||||||
if (isHyprland && Hyprland.focusedMonitor)
|
|
||||||
return Hyprland.focusedMonitor.name;
|
|
||||||
if (isNiri && NiriService.currentOutput)
|
|
||||||
return NiriService.currentOutput;
|
|
||||||
if (isDwl && DwlService.activeOutput)
|
|
||||||
return DwlService.activeOutput;
|
|
||||||
if (isSway) {
|
|
||||||
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
|
||||||
if (focusedWs?.monitor?.name)
|
|
||||||
return focusedWs.monitor.name;
|
|
||||||
}
|
|
||||||
return Quickshell.screens[0]?.name ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property var focusedScreen: {
|
|
||||||
if (!focusedScreenName)
|
|
||||||
return Quickshell.screens[0] ?? null;
|
|
||||||
for (const s of Quickshell.screens) {
|
|
||||||
if (s.name === focusedScreenName)
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
return Quickshell.screens[0] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
property var sortedToplevels: []
|
property var sortedToplevels: []
|
||||||
property bool _sortScheduled: false
|
property bool _sortScheduled: false
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,17 @@ Singleton {
|
|||||||
property string lastError: ""
|
property string lastError: ""
|
||||||
property bool dmsBindsIncluded: true
|
property bool dmsBindsIncluded: true
|
||||||
|
|
||||||
|
property var dmsStatus: ({
|
||||||
|
exists: true,
|
||||||
|
included: true,
|
||||||
|
includePosition: -1,
|
||||||
|
totalIncludes: 0,
|
||||||
|
bindsAfterDms: 0,
|
||||||
|
effective: true,
|
||||||
|
overriddenBy: 0,
|
||||||
|
statusMessage: ""
|
||||||
|
})
|
||||||
|
|
||||||
property var _rawData: null
|
property var _rawData: null
|
||||||
property var keybinds: ({})
|
property var keybinds: ({})
|
||||||
property var _allBinds: ({})
|
property var _allBinds: ({})
|
||||||
@@ -60,6 +71,14 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: NiriService
|
||||||
|
enabled: CompositorService.isNiri
|
||||||
|
function onConfigReloaded() {
|
||||||
|
Qt.callLater(root.loadBinds, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: loadProcess
|
id: loadProcess
|
||||||
running: false
|
running: false
|
||||||
@@ -178,7 +197,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadBinds(showLoading) {
|
function loadBinds(showLoading) {
|
||||||
if (loading || !available)
|
if (loadProcess.running || !available)
|
||||||
return;
|
return;
|
||||||
const hasData = Object.keys(_allBinds).length > 0;
|
const hasData = Object.keys(_allBinds).length > 0;
|
||||||
loading = showLoading !== false && !hasData;
|
loading = showLoading !== false && !hasData;
|
||||||
@@ -188,8 +207,22 @@ Singleton {
|
|||||||
|
|
||||||
function _processData() {
|
function _processData() {
|
||||||
keybinds = _rawData || {};
|
keybinds = _rawData || {};
|
||||||
if (currentProvider === "niri")
|
if (currentProvider === "niri") {
|
||||||
dmsBindsIncluded = _rawData?.dmsBindsIncluded ?? true;
|
dmsBindsIncluded = _rawData?.dmsBindsIncluded ?? true;
|
||||||
|
const status = _rawData?.dmsStatus;
|
||||||
|
if (status) {
|
||||||
|
dmsStatus = {
|
||||||
|
exists: status.exists ?? true,
|
||||||
|
included: status.included ?? true,
|
||||||
|
includePosition: status.includePosition ?? -1,
|
||||||
|
totalIncludes: status.totalIncludes ?? 0,
|
||||||
|
bindsAfterDms: status.bindsAfterDms ?? 0,
|
||||||
|
effective: status.effective ?? true,
|
||||||
|
overriddenBy: status.overriddenBy ?? 0,
|
||||||
|
statusMessage: status.statusMessage ?? ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!_rawData?.binds) {
|
if (!_rawData?.binds) {
|
||||||
_allBinds = {};
|
_allBinds = {};
|
||||||
@@ -239,12 +272,15 @@ Singleton {
|
|||||||
actionMap[action].keys.push(keyData);
|
actionMap[action].keys.push(keyData);
|
||||||
if (!actionMap[action].desc && bind.desc)
|
if (!actionMap[action].desc && bind.desc)
|
||||||
actionMap[action].desc = bind.desc;
|
actionMap[action].desc = bind.desc;
|
||||||
|
if (!actionMap[action].conflict && bind.conflict)
|
||||||
|
actionMap[action].conflict = bind.conflict;
|
||||||
} else {
|
} else {
|
||||||
const entry = {
|
const entry = {
|
||||||
category: category,
|
category: category,
|
||||||
action: action,
|
action: action,
|
||||||
desc: bind.desc || "",
|
desc: bind.desc || "",
|
||||||
keys: [keyData]
|
keys: [keyData],
|
||||||
|
conflict: bind.conflict || null
|
||||||
};
|
};
|
||||||
actionMap[action] = entry;
|
actionMap[action] = entry;
|
||||||
grouped.push(entry);
|
grouped.push(entry);
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ Singleton {
|
|||||||
property string pendingScreenshotPath: ""
|
property string pendingScreenshotPath: ""
|
||||||
|
|
||||||
signal windowUrgentChanged
|
signal windowUrgentChanged
|
||||||
|
signal configReloaded
|
||||||
|
|
||||||
function setWorkspaces(newMap) {
|
function setWorkspaces(newMap) {
|
||||||
root.workspaces = newMap;
|
root.workspaces = newMap;
|
||||||
@@ -508,17 +509,20 @@ Singleton {
|
|||||||
function handleConfigLoaded(data) {
|
function handleConfigLoaded(data) {
|
||||||
if (data.failed) {
|
if (data.failed) {
|
||||||
validateProcess.running = true;
|
validateProcess.running = true;
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
configValidationOutput = "";
|
configValidationOutput = "";
|
||||||
ToastService.dismissCategory("niri-config");
|
ToastService.dismissCategory("niri-config");
|
||||||
fetchOutputs();
|
fetchOutputs();
|
||||||
|
configReloaded();
|
||||||
|
|
||||||
if (hasInitialConnection && !suppressConfigToast && !suppressNextConfigToast && !matugenSuppression) {
|
if (hasInitialConnection && !suppressConfigToast && !suppressNextConfigToast && !matugenSuppression) {
|
||||||
ToastService.showInfo("niri: config reloaded", "", "", "niri-config");
|
ToastService.showInfo("niri: config reloaded", "", "", "niri-config");
|
||||||
} else if (suppressNextConfigToast) {
|
} else if (suppressNextConfigToast) {
|
||||||
suppressNextConfigToast = false;
|
suppressNextConfigToast = false;
|
||||||
suppressResetTimer.stop();
|
suppressResetTimer.stop();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasInitialConnection) {
|
if (!hasInitialConnection) {
|
||||||
hasInitialConnection = true;
|
hasInitialConnection = true;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ Item {
|
|||||||
property var panelWindow: null
|
property var panelWindow: null
|
||||||
property bool recording: false
|
property bool recording: false
|
||||||
property bool isNew: false
|
property bool isNew: false
|
||||||
|
property string restoreKey: ""
|
||||||
|
|
||||||
property int editingKeyIndex: -1
|
property int editingKeyIndex: -1
|
||||||
property string editKey: ""
|
property string editKey: ""
|
||||||
@@ -44,6 +45,8 @@ Item {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
readonly property var configConflict: bindData.conflict || null
|
||||||
|
readonly property bool hasConfigConflict: configConflict !== null
|
||||||
readonly property string _originalKey: editingKeyIndex >= 0 && editingKeyIndex < keys.length ? keys[editingKeyIndex].key : ""
|
readonly property string _originalKey: editingKeyIndex >= 0 && editingKeyIndex < keys.length ? keys[editingKeyIndex].key : ""
|
||||||
readonly property var _conflicts: editKey ? KeyUtils.getConflictingBinds(editKey, bindData.action, KeybindsService.getFlatBinds()) : []
|
readonly property var _conflicts: editKey ? KeyUtils.getConflictingBinds(editKey, bindData.action, KeybindsService.getFlatBinds()) : []
|
||||||
readonly property bool hasConflict: _conflicts.length > 0
|
readonly property bool hasConflict: _conflicts.length > 0
|
||||||
@@ -52,6 +55,7 @@ Item {
|
|||||||
signal saveBind(string originalKey, var newData)
|
signal saveBind(string originalKey, var newData)
|
||||||
signal removeBind(string key)
|
signal removeBind(string key)
|
||||||
signal cancelEdit
|
signal cancelEdit
|
||||||
|
signal restoreKeyConsumed
|
||||||
|
|
||||||
implicitHeight: contentColumn.implicitHeight
|
implicitHeight: contentColumn.implicitHeight
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
@@ -59,7 +63,36 @@ Item {
|
|||||||
Component.onDestruction: _destroyShortcutInhibitor()
|
Component.onDestruction: _destroyShortcutInhibitor()
|
||||||
|
|
||||||
onIsExpandedChanged: {
|
onIsExpandedChanged: {
|
||||||
if (isExpanded)
|
if (!isExpanded)
|
||||||
|
return;
|
||||||
|
if (restoreKey) {
|
||||||
|
restoreToKey(restoreKey);
|
||||||
|
restoreKeyConsumed();
|
||||||
|
} else {
|
||||||
|
resetEdits();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onRestoreKeyChanged: {
|
||||||
|
if (!isExpanded || !restoreKey)
|
||||||
|
return;
|
||||||
|
restoreToKey(restoreKey);
|
||||||
|
restoreKeyConsumed();
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreToKey(keyToFind) {
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
if (keys[i].key === keyToFind) {
|
||||||
|
editingKeyIndex = i;
|
||||||
|
editKey = keyToFind;
|
||||||
|
editAction = bindData.action || "";
|
||||||
|
editDesc = bindData.desc || "";
|
||||||
|
hasChanges = false;
|
||||||
|
_actionType = Actions.getActionType(editAction);
|
||||||
|
useCustomCompositor = _actionType === "compositor" && !Actions.isKnownCompositorAction(editAction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
resetEdits();
|
resetEdits();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +309,21 @@ Item {
|
|||||||
text: I18n.tr("Override")
|
text: I18n.tr("Override")
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
visible: root.hasOverride
|
visible: root.hasOverride && !root.hasConfigConflict
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "warning"
|
||||||
|
size: 14
|
||||||
|
color: Theme.warning ?? Theme.tertiary
|
||||||
|
visible: root.hasConfigConflict
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Overridden by config")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.warning ?? Theme.tertiary
|
||||||
|
visible: root.hasConfigConflict
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -334,6 +381,58 @@ Item {
|
|||||||
anchors.margins: Theme.spacingL
|
anchors.margins: Theme.spacingL
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: conflictColumn.implicitHeight + Theme.spacingM * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.withAlpha(Theme.warning ?? Theme.tertiary, 0.15)
|
||||||
|
border.color: Theme.withAlpha(Theme.warning ?? Theme.tertiary, 0.3)
|
||||||
|
border.width: 1
|
||||||
|
visible: root.hasConfigConflict
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: conflictColumn
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "warning"
|
||||||
|
size: 16
|
||||||
|
color: Theme.warning ?? Theme.tertiary
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("This bind is overridden by config.kdl")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.warning ?? Theme.tertiary
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Config action: %1").arg(root.configConflict?.action ?? "")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("To use this DMS bind, remove or change the keybind in your config.kdl")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|||||||
Reference in New Issue
Block a user