mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
Compare commits
7 Commits
ae066f42a4
...
ddda87c5a7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddda87c5a7 | ||
|
|
6b1bbca620 | ||
|
|
b5378e5d3c | ||
|
|
c69a55df29 | ||
|
|
5faa1a993a | ||
|
|
e56481f6d7 | ||
|
|
f9610d457c |
@@ -6,5 +6,5 @@ Exec=dms open %u
|
||||
Icon=danklogo
|
||||
Terminal=false
|
||||
NoDisplay=true
|
||||
MimeType=x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/file;text/html;
|
||||
MimeType=x-scheme-handler/http;x-scheme-handler/https;text/html;application/xhtml+xml;
|
||||
Categories=Utility;
|
||||
|
||||
@@ -333,35 +333,6 @@ func (n *NiriProvider) isRecentWindowsAction(action string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NiriProvider) parseSpawnArgs(s string) []string {
|
||||
var args []string
|
||||
var current strings.Builder
|
||||
var inQuote, escaped bool
|
||||
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case escaped:
|
||||
current.WriteRune(r)
|
||||
escaped = false
|
||||
case r == '\\':
|
||||
escaped = true
|
||||
case r == '"':
|
||||
inQuote = !inQuote
|
||||
case r == ' ' && !inQuote:
|
||||
if current.Len() > 0 {
|
||||
args = append(args, current.String())
|
||||
current.Reset()
|
||||
}
|
||||
default:
|
||||
current.WriteRune(r)
|
||||
}
|
||||
}
|
||||
if current.Len() > 0 {
|
||||
args = append(args, current.String())
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (n *NiriProvider) buildBindNode(bind *overrideBind) *document.Node {
|
||||
node := document.NewNode()
|
||||
node.SetName(bind.Key)
|
||||
@@ -392,19 +363,48 @@ func (n *NiriProvider) buildActionNode(action string) *document.Node {
|
||||
action = strings.TrimSpace(action)
|
||||
node := document.NewNode()
|
||||
|
||||
if !strings.HasPrefix(action, "spawn ") {
|
||||
parts := n.parseActionParts(action)
|
||||
if len(parts) == 0 {
|
||||
node.SetName(action)
|
||||
return node
|
||||
}
|
||||
|
||||
node.SetName("spawn")
|
||||
args := n.parseSpawnArgs(strings.TrimPrefix(action, "spawn "))
|
||||
for _, arg := range args {
|
||||
node.SetName(parts[0])
|
||||
for _, arg := range parts[1:] {
|
||||
node.AddArgument(arg, "")
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (n *NiriProvider) parseActionParts(action string) []string {
|
||||
var parts []string
|
||||
var current strings.Builder
|
||||
var inQuote, escaped bool
|
||||
|
||||
for _, r := range action {
|
||||
switch {
|
||||
case escaped:
|
||||
current.WriteRune(r)
|
||||
escaped = false
|
||||
case r == '\\':
|
||||
escaped = true
|
||||
case r == '"':
|
||||
inQuote = !inQuote
|
||||
case r == ' ' && !inQuote:
|
||||
if current.Len() > 0 {
|
||||
parts = append(parts, current.String())
|
||||
current.Reset()
|
||||
}
|
||||
default:
|
||||
current.WriteRune(r)
|
||||
}
|
||||
}
|
||||
if current.Len() > 0 {
|
||||
parts = append(parts, current.String())
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
func (n *NiriProvider) writeOverrideBinds(binds map[string]*overrideBind) error {
|
||||
overridePath := n.GetOverridePath()
|
||||
content := n.generateBindsContent(binds)
|
||||
@@ -501,21 +501,46 @@ func (n *NiriProvider) writeBindNode(sb *strings.Builder, bind *overrideBind, in
|
||||
sb.WriteString(" { ")
|
||||
if len(node.Children) > 0 {
|
||||
child := node.Children[0]
|
||||
sb.WriteString(child.Name.String())
|
||||
actionName := child.Name.String()
|
||||
sb.WriteString(actionName)
|
||||
forceQuote := actionName == "spawn"
|
||||
for _, arg := range child.Arguments {
|
||||
sb.WriteString(" ")
|
||||
n.writeQuotedArg(sb, arg.ValueString())
|
||||
n.writeArg(sb, arg.ValueString(), forceQuote)
|
||||
}
|
||||
}
|
||||
sb.WriteString("; }\n")
|
||||
}
|
||||
|
||||
func (n *NiriProvider) writeQuotedArg(sb *strings.Builder, val string) {
|
||||
func (n *NiriProvider) writeArg(sb *strings.Builder, val string, forceQuote bool) {
|
||||
if !forceQuote && n.isNumericArg(val) {
|
||||
sb.WriteString(val)
|
||||
return
|
||||
}
|
||||
sb.WriteString("\"")
|
||||
sb.WriteString(strings.ReplaceAll(val, "\"", "\\\""))
|
||||
sb.WriteString("\"")
|
||||
}
|
||||
|
||||
func (n *NiriProvider) isNumericArg(val string) bool {
|
||||
if val == "" {
|
||||
return false
|
||||
}
|
||||
start := 0
|
||||
if val[0] == '-' || val[0] == '+' {
|
||||
if len(val) == 1 {
|
||||
return false
|
||||
}
|
||||
start = 1
|
||||
}
|
||||
for i := start; i < len(val); i++ {
|
||||
if val[i] < '0' || val[i] > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (n *NiriProvider) validateBindsContent(content string) error {
|
||||
tmpFile, err := os.CreateTemp("", "dms-binds-*.kdl")
|
||||
if err != nil {
|
||||
|
||||
@@ -496,3 +496,119 @@ func TestNiriParseMultipleArgs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseNumericWorkspaceBinds(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
|
||||
content := `binds {
|
||||
Mod+1 hotkey-overlay-title="Focus Workspace 1" { focus-workspace 1; }
|
||||
Mod+2 hotkey-overlay-title="Focus Workspace 2" { focus-workspace 2; }
|
||||
Mod+0 hotkey-overlay-title="Focus Workspace 10" { focus-workspace 10; }
|
||||
Mod+Shift+1 hotkey-overlay-title="Move to Workspace 1" { move-column-to-workspace 1; }
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 4 {
|
||||
t.Errorf("Expected 4 keybinds, got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
|
||||
for _, kb := range result.Section.Keybinds {
|
||||
switch kb.Key {
|
||||
case "1":
|
||||
if len(kb.Mods) == 1 && kb.Mods[0] == "Mod" {
|
||||
if kb.Action != "focus-workspace" || len(kb.Args) != 1 || kb.Args[0] != "1" {
|
||||
t.Errorf("Mod+1 action/args mismatch: %+v", kb)
|
||||
}
|
||||
if kb.Description != "Focus Workspace 1" {
|
||||
t.Errorf("Mod+1 description = %q, want 'Focus Workspace 1'", kb.Description)
|
||||
}
|
||||
}
|
||||
case "0":
|
||||
if kb.Action != "focus-workspace" || len(kb.Args) != 1 || kb.Args[0] != "10" {
|
||||
t.Errorf("Mod+0 action/args mismatch: %+v", kb)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseQuotedStringArgs(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
|
||||
content := `binds {
|
||||
Super+Minus hotkey-overlay-title="Adjust Column Width -10%" { set-column-width "-10%"; }
|
||||
Super+Equal hotkey-overlay-title="Adjust Column Width +10%" { set-column-width "+10%"; }
|
||||
Super+Shift+Minus hotkey-overlay-title="Adjust Window Height -10%" { set-window-height "-10%"; }
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 3 {
|
||||
t.Errorf("Expected 3 keybinds, got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
|
||||
for _, kb := range result.Section.Keybinds {
|
||||
if kb.Action == "set-column-width" {
|
||||
if len(kb.Args) != 1 {
|
||||
t.Errorf("set-column-width should have 1 arg, got %d", len(kb.Args))
|
||||
continue
|
||||
}
|
||||
if kb.Args[0] != "-10%" && kb.Args[0] != "+10%" {
|
||||
t.Errorf("set-column-width arg = %q, want -10%% or +10%%", kb.Args[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseActionWithProperties(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
|
||||
content := `binds {
|
||||
Mod+Shift+1 hotkey-overlay-title="Move to Workspace 1" { move-column-to-workspace 1 focus=false; }
|
||||
Mod+Shift+2 hotkey-overlay-title="Move to Workspace 2" { move-column-to-workspace 2 focus=false; }
|
||||
Alt+Tab { next-window scope="output"; }
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 3 {
|
||||
t.Errorf("Expected 3 keybinds, got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
|
||||
for _, kb := range result.Section.Keybinds {
|
||||
switch kb.Action {
|
||||
case "move-column-to-workspace":
|
||||
if len(kb.Args) != 1 {
|
||||
t.Errorf("move-column-to-workspace should have 1 arg, got %d", len(kb.Args))
|
||||
}
|
||||
case "next-window":
|
||||
if kb.Key != "Tab" {
|
||||
t.Errorf("next-window key = %q, want 'Tab'", kb.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,3 +397,211 @@ recent-windows {
|
||||
t.Errorf("Expected at least 2 Alt-Tab binds, got %d", len(cheatSheet.Binds["Alt-Tab"]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriGenerateBindsContentNumericArgs(t *testing.T) {
|
||||
provider := NewNiriProvider("")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
binds map[string]*overrideBind
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "workspace with numeric arg",
|
||||
binds: map[string]*overrideBind{
|
||||
"Mod+1": {
|
||||
Key: "Mod+1",
|
||||
Action: "focus-workspace 1",
|
||||
Description: "Focus Workspace 1",
|
||||
},
|
||||
},
|
||||
expected: `binds {
|
||||
Mod+1 hotkey-overlay-title="Focus Workspace 1" { focus-workspace 1; }
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "workspace with large numeric arg",
|
||||
binds: map[string]*overrideBind{
|
||||
"Mod+0": {
|
||||
Key: "Mod+0",
|
||||
Action: "focus-workspace 10",
|
||||
Description: "Focus Workspace 10",
|
||||
},
|
||||
},
|
||||
expected: `binds {
|
||||
Mod+0 hotkey-overlay-title="Focus Workspace 10" { focus-workspace 10; }
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "percentage string arg (should be quoted)",
|
||||
binds: map[string]*overrideBind{
|
||||
"Super+Minus": {
|
||||
Key: "Super+Minus",
|
||||
Action: `set-column-width "-10%"`,
|
||||
Description: "Adjust Column Width -10%",
|
||||
},
|
||||
},
|
||||
expected: `binds {
|
||||
Super+Minus hotkey-overlay-title="Adjust Column Width -10%" { set-column-width "-10%"; }
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "positive percentage string arg",
|
||||
binds: map[string]*overrideBind{
|
||||
"Super+Equal": {
|
||||
Key: "Super+Equal",
|
||||
Action: `set-column-width "+10%"`,
|
||||
Description: "Adjust Column Width +10%",
|
||||
},
|
||||
},
|
||||
expected: `binds {
|
||||
Super+Equal hotkey-overlay-title="Adjust Column Width +10%" { set-column-width "+10%"; }
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := provider.generateBindsContent(tt.binds)
|
||||
if result != tt.expected {
|
||||
t.Errorf("generateBindsContent() =\n%q\nwant:\n%q", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriGenerateActionWithUnquotedPercentArg(t *testing.T) {
|
||||
provider := NewNiriProvider("")
|
||||
|
||||
binds := map[string]*overrideBind{
|
||||
"Super+Equal": {
|
||||
Key: "Super+Equal",
|
||||
Action: "set-window-height +10%",
|
||||
Description: "Adjust Window Height +10%",
|
||||
},
|
||||
}
|
||||
|
||||
content := provider.generateBindsContent(binds)
|
||||
expected := `binds {
|
||||
Super+Equal hotkey-overlay-title="Adjust Window Height +10%" { set-window-height "+10%"; }
|
||||
}
|
||||
`
|
||||
if content != expected {
|
||||
t.Errorf("Content mismatch.\nGot:\n%s\nWant:\n%s", content, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriGenerateSpawnWithNumericArgs(t *testing.T) {
|
||||
provider := NewNiriProvider("")
|
||||
|
||||
binds := map[string]*overrideBind{
|
||||
"XF86AudioLowerVolume": {
|
||||
Key: "XF86AudioLowerVolume",
|
||||
Action: `spawn "dms" "ipc" "call" "audio" "decrement" "3"`,
|
||||
Options: map[string]any{"allow-when-locked": true},
|
||||
},
|
||||
}
|
||||
|
||||
content := provider.generateBindsContent(binds)
|
||||
expected := `binds {
|
||||
XF86AudioLowerVolume allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "decrement" "3"; }
|
||||
}
|
||||
`
|
||||
if content != expected {
|
||||
t.Errorf("Content mismatch.\nGot:\n%s\nWant:\n%s", content, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriGenerateSpawnNumericArgFromCLI(t *testing.T) {
|
||||
provider := NewNiriProvider("")
|
||||
|
||||
binds := map[string]*overrideBind{
|
||||
"XF86AudioLowerVolume": {
|
||||
Key: "XF86AudioLowerVolume",
|
||||
Action: "spawn dms ipc call audio decrement 3",
|
||||
Options: map[string]any{"allow-when-locked": true},
|
||||
},
|
||||
}
|
||||
|
||||
content := provider.generateBindsContent(binds)
|
||||
expected := `binds {
|
||||
XF86AudioLowerVolume allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "decrement" "3"; }
|
||||
}
|
||||
`
|
||||
if content != expected {
|
||||
t.Errorf("Content mismatch.\nGot:\n%s\nWant:\n%s", content, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriGenerateWorkspaceBindsRoundTrip(t *testing.T) {
|
||||
provider := NewNiriProvider("")
|
||||
|
||||
binds := map[string]*overrideBind{
|
||||
"Mod+1": {
|
||||
Key: "Mod+1",
|
||||
Action: "focus-workspace 1",
|
||||
Description: "Focus Workspace 1",
|
||||
},
|
||||
"Mod+2": {
|
||||
Key: "Mod+2",
|
||||
Action: "focus-workspace 2",
|
||||
Description: "Focus Workspace 2",
|
||||
},
|
||||
"Mod+Shift+1": {
|
||||
Key: "Mod+Shift+1",
|
||||
Action: "move-column-to-workspace 1",
|
||||
Description: "Move to Workspace 1",
|
||||
},
|
||||
"Super+Minus": {
|
||||
Key: "Super+Minus",
|
||||
Action: "set-column-width -10%",
|
||||
Description: "Adjust Column Width -10%",
|
||||
},
|
||||
}
|
||||
|
||||
content := provider.generateBindsContent(binds)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write temp file: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse generated content: %v\nContent was:\n%s", err, content)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 4 {
|
||||
t.Errorf("Expected 4 keybinds after round-trip, got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
|
||||
foundFocusWS1 := false
|
||||
foundMoveWS1 := false
|
||||
foundSetWidth := false
|
||||
|
||||
for _, kb := range result.Section.Keybinds {
|
||||
switch {
|
||||
case kb.Action == "focus-workspace" && len(kb.Args) > 0 && kb.Args[0] == "1":
|
||||
foundFocusWS1 = true
|
||||
case kb.Action == "move-column-to-workspace" && len(kb.Args) > 0 && kb.Args[0] == "1":
|
||||
foundMoveWS1 = true
|
||||
case kb.Action == "set-column-width" && len(kb.Args) > 0 && kb.Args[0] == "-10%":
|
||||
foundSetWidth = true
|
||||
}
|
||||
}
|
||||
|
||||
if !foundFocusWS1 {
|
||||
t.Error("focus-workspace 1 not found after round-trip")
|
||||
}
|
||||
if !foundMoveWS1 {
|
||||
t.Error("move-column-to-workspace 1 not found after round-trip")
|
||||
}
|
||||
if !foundSetWidth {
|
||||
t.Error("set-column-width -10% not found after round-trip")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ const KEY_MAP = {
|
||||
96: "grave",
|
||||
32: "space",
|
||||
16777225: "Print",
|
||||
16777226: "Print",
|
||||
16777220: "Return",
|
||||
16777221: "Return",
|
||||
16777217: "Tab",
|
||||
@@ -93,20 +94,20 @@ function xkbKeyFromQtKey(qk) {
|
||||
|
||||
function modsFromEvent(mods) {
|
||||
var result = [];
|
||||
if (mods & 0x04000000)
|
||||
result.push("Ctrl");
|
||||
if (mods & 0x02000000)
|
||||
result.push("Shift");
|
||||
var hasAlt = mods & 0x08000000;
|
||||
var hasSuper = mods & 0x10000000;
|
||||
if (hasAlt && hasSuper) {
|
||||
result.push("Mod");
|
||||
} else {
|
||||
if (hasAlt)
|
||||
result.push("Alt");
|
||||
if (hasSuper)
|
||||
result.push("Super");
|
||||
if (hasAlt)
|
||||
result.push("Alt");
|
||||
}
|
||||
if (mods & 0x04000000)
|
||||
result.push("Ctrl");
|
||||
if (mods & 0x02000000)
|
||||
result.push("Shift");
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ const DMS_ACTIONS = [
|
||||
{ id: "spawn dms ipc call notepad toggle", label: "Notepad: Toggle" },
|
||||
{ id: "spawn dms ipc call notepad open", label: "Notepad: Open" },
|
||||
{ id: "spawn dms ipc call notepad close", label: "Notepad: Close" },
|
||||
{ id: "spawn dms ipc call dash toggle", label: "Dashboard: Toggle" },
|
||||
{ id: "spawn dms ipc call dash toggle \"\"", label: "Dashboard: Toggle" },
|
||||
{ id: "spawn dms ipc call dash open overview", label: "Dashboard: Overview" },
|
||||
{ id: "spawn dms ipc call dash open media", label: "Dashboard: Media" },
|
||||
{ id: "spawn dms ipc call dash open weather", label: "Dashboard: Weather" },
|
||||
@@ -109,9 +109,15 @@ const COMPOSITOR_ACTIONS = {
|
||||
{ id: "fullscreen-window", label: "Fullscreen" },
|
||||
{ id: "maximize-column", label: "Maximize Column" },
|
||||
{ id: "center-column", label: "Center Column" },
|
||||
{ id: "center-visible-columns", label: "Center Visible Columns" },
|
||||
{ id: "toggle-window-floating", label: "Toggle Floating" },
|
||||
{ id: "switch-focus-between-floating-and-tiling", label: "Switch Floating/Tiling Focus" },
|
||||
{ id: "switch-preset-column-width", label: "Cycle Column Width" },
|
||||
{ id: "switch-preset-window-height", label: "Cycle Window Height" },
|
||||
{ id: "set-column-width", label: "Set Column Width" },
|
||||
{ id: "set-window-height", label: "Set Window Height" },
|
||||
{ id: "reset-window-height", label: "Reset Window Height" },
|
||||
{ id: "expand-column-to-available-width", label: "Expand to Available Width" },
|
||||
{ id: "consume-or-expel-window-left", label: "Consume/Expel Left" },
|
||||
{ id: "consume-or-expel-window-right", label: "Consume/Expel Right" },
|
||||
{ id: "toggle-column-tabbed-display", label: "Toggle Tabbed" }
|
||||
@@ -136,8 +142,10 @@ const COMPOSITOR_ACTIONS = {
|
||||
{ id: "focus-workspace-down", label: "Focus Workspace Down" },
|
||||
{ id: "focus-workspace-up", label: "Focus Workspace Up" },
|
||||
{ id: "focus-workspace-previous", label: "Focus Previous Workspace" },
|
||||
{ id: "focus-workspace", label: "Focus Workspace (by index)" },
|
||||
{ id: "move-column-to-workspace-down", label: "Move to Workspace Down" },
|
||||
{ id: "move-column-to-workspace-up", label: "Move to Workspace Up" },
|
||||
{ id: "move-column-to-workspace", label: "Move to Workspace (by index)" },
|
||||
{ id: "move-workspace-down", label: "Move Workspace Down" },
|
||||
{ id: "move-workspace-up", label: "Move Workspace Up" }
|
||||
],
|
||||
@@ -173,6 +181,52 @@ const COMPOSITOR_ACTIONS = {
|
||||
|
||||
const CATEGORY_ORDER = ["DMS", "Execute", "Workspace", "Window", "Monitor", "Screenshot", "System", "Overview", "Alt-Tab", "Other"];
|
||||
|
||||
const ACTION_ARGS = {
|
||||
"set-column-width": {
|
||||
args: [{ name: "value", type: "text", label: "Width", placeholder: "+10%, -10%, 50%" }]
|
||||
},
|
||||
"set-window-height": {
|
||||
args: [{ name: "value", type: "text", label: "Height", placeholder: "+10%, -10%, 50%" }]
|
||||
},
|
||||
"focus-workspace": {
|
||||
args: [{ name: "index", type: "number", label: "Workspace", placeholder: "1, 2, 3..." }]
|
||||
},
|
||||
"move-column-to-workspace": {
|
||||
args: [
|
||||
{ name: "index", type: "number", label: "Workspace", placeholder: "1, 2, 3..." },
|
||||
{ name: "focus", type: "bool", label: "Follow focus", default: false }
|
||||
]
|
||||
},
|
||||
"screenshot": {
|
||||
args: [{ name: "opts", type: "screenshot", label: "Options" }]
|
||||
},
|
||||
"screenshot-screen": {
|
||||
args: [{ name: "opts", type: "screenshot", label: "Options" }]
|
||||
},
|
||||
"screenshot-window": {
|
||||
args: [{ name: "opts", type: "screenshot", label: "Options" }]
|
||||
}
|
||||
};
|
||||
|
||||
const DMS_ACTION_ARGS = {
|
||||
"audio increment": {
|
||||
base: "spawn dms ipc call audio increment",
|
||||
args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "" }]
|
||||
},
|
||||
"audio decrement": {
|
||||
base: "spawn dms ipc call audio decrement",
|
||||
args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "" }]
|
||||
},
|
||||
"brightness increment": {
|
||||
base: "spawn dms ipc call brightness increment",
|
||||
args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "" }]
|
||||
},
|
||||
"brightness decrement": {
|
||||
base: "spawn dms ipc call brightness decrement",
|
||||
args: [{ name: "amount", type: "number", label: "Amount %", placeholder: "5", default: "" }]
|
||||
}
|
||||
};
|
||||
|
||||
function getActionTypes() {
|
||||
return ACTION_TYPES;
|
||||
}
|
||||
@@ -322,3 +376,120 @@ function parseShellCommand(action) {
|
||||
content = content.slice(1, -1);
|
||||
return content.replace(/\\"/g, "\"");
|
||||
}
|
||||
|
||||
function getActionArgConfig(action) {
|
||||
if (!action)
|
||||
return null;
|
||||
|
||||
var baseAction = action.split(" ")[0];
|
||||
if (ACTION_ARGS[baseAction])
|
||||
return { type: "compositor", base: baseAction, config: ACTION_ARGS[baseAction] };
|
||||
|
||||
for (var key in DMS_ACTION_ARGS) {
|
||||
if (action.startsWith(DMS_ACTION_ARGS[key].base))
|
||||
return { type: "dms", base: key, config: DMS_ACTION_ARGS[key] };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseCompositorActionArgs(action) {
|
||||
if (!action)
|
||||
return { base: "", args: {} };
|
||||
|
||||
var parts = action.split(" ");
|
||||
var base = parts[0];
|
||||
var args = {};
|
||||
|
||||
if (!ACTION_ARGS[base])
|
||||
return { base: action, args: {} };
|
||||
|
||||
var argConfig = ACTION_ARGS[base];
|
||||
var argParts = parts.slice(1);
|
||||
|
||||
if (base === "move-column-to-workspace") {
|
||||
for (var i = 0; i < argParts.length; i++) {
|
||||
if (argParts[i] === "focus=true" || argParts[i] === "focus=false") {
|
||||
args.focus = argParts[i] === "focus=true";
|
||||
} else if (!args.index) {
|
||||
args.index = argParts[i];
|
||||
}
|
||||
}
|
||||
} else if (base.startsWith("screenshot")) {
|
||||
args.opts = {};
|
||||
for (var j = 0; j < argParts.length; j += 2) {
|
||||
if (j + 1 < argParts.length)
|
||||
args.opts[argParts[j]] = argParts[j + 1];
|
||||
}
|
||||
} else if (argParts.length > 0) {
|
||||
args.value = argParts.join(" ");
|
||||
}
|
||||
|
||||
return { base: base, args: args };
|
||||
}
|
||||
|
||||
function buildCompositorAction(base, args) {
|
||||
if (!base)
|
||||
return "";
|
||||
|
||||
var parts = [base];
|
||||
|
||||
if (!args || Object.keys(args).length === 0)
|
||||
return base;
|
||||
|
||||
if (base === "move-column-to-workspace") {
|
||||
if (args.index)
|
||||
parts.push(args.index);
|
||||
if (args.focus === true)
|
||||
parts.push("focus=true");
|
||||
else if (args.focus === false)
|
||||
parts.push("focus=false");
|
||||
} else if (base.startsWith("screenshot") && args.opts) {
|
||||
for (var key in args.opts) {
|
||||
if (args.opts[key] !== undefined && args.opts[key] !== "") {
|
||||
parts.push(key);
|
||||
parts.push(args.opts[key]);
|
||||
}
|
||||
}
|
||||
} else if (args.value) {
|
||||
parts.push(args.value);
|
||||
} else if (args.index) {
|
||||
parts.push(args.index);
|
||||
}
|
||||
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
function parseDmsActionArgs(action) {
|
||||
if (!action)
|
||||
return { base: "", args: {} };
|
||||
|
||||
for (var key in DMS_ACTION_ARGS) {
|
||||
var config = DMS_ACTION_ARGS[key];
|
||||
if (action.startsWith(config.base)) {
|
||||
var rest = action.slice(config.base.length).trim();
|
||||
return { base: key, args: { amount: rest || "" } };
|
||||
}
|
||||
}
|
||||
|
||||
return { base: action, args: {} };
|
||||
}
|
||||
|
||||
function buildDmsAction(baseKey, args) {
|
||||
var config = DMS_ACTION_ARGS[baseKey];
|
||||
if (!config)
|
||||
return "";
|
||||
|
||||
var action = config.base;
|
||||
if (args && args.amount)
|
||||
action += " " + args.amount;
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function getScreenshotOptions() {
|
||||
return [
|
||||
{ id: "write-to-disk", label: "Save to disk", type: "bool" },
|
||||
{ id: "show-pointer", label: "Show pointer", type: "bool" }
|
||||
];
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ DankModal {
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [root.contentWindow]
|
||||
active: CompositorService.isHyprland && root.shouldHaveFocus
|
||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
||||
}
|
||||
|
||||
property string deviceName: ""
|
||||
|
||||
@@ -15,7 +15,7 @@ DankModal {
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [clipboardHistoryModal.contentWindow]
|
||||
active: CompositorService.isHyprland && clipboardHistoryModal.shouldHaveFocus
|
||||
active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
property int totalCount: 0
|
||||
|
||||
@@ -47,6 +47,7 @@ Item {
|
||||
property bool useOverlayLayer: false
|
||||
readonly property alias contentWindow: contentWindow
|
||||
readonly property alias backgroundWindow: backgroundWindow
|
||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||
|
||||
signal opened
|
||||
signal dialogClosed
|
||||
@@ -262,7 +263,7 @@ Item {
|
||||
return customKeyboardFocus;
|
||||
if (!shouldHaveFocus)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.isHyprland)
|
||||
if (root.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ DankModal {
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [root.contentWindow]
|
||||
active: CompositorService.isHyprland && root.shouldHaveFocus
|
||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
||||
}
|
||||
|
||||
property string pickerTitle: I18n.tr("Choose Color")
|
||||
|
||||
@@ -21,7 +21,7 @@ DankModal {
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [root.contentWindow]
|
||||
active: CompositorService.isHyprland && root.shouldHaveFocus
|
||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
||||
}
|
||||
|
||||
function scrollDown() {
|
||||
|
||||
@@ -13,7 +13,7 @@ DankModal {
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [notificationModal.contentWindow]
|
||||
active: CompositorService.isHyprland && notificationModal.shouldHaveFocus
|
||||
active: notificationModal.useHyprlandFocusGrab && notificationModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
property bool notificationModalOpen: false
|
||||
|
||||
@@ -15,7 +15,7 @@ DankModal {
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [root.contentWindow]
|
||||
active: CompositorService.isHyprland && root.shouldHaveFocus
|
||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
||||
}
|
||||
|
||||
property int selectedIndex: 0
|
||||
|
||||
@@ -10,12 +10,31 @@ Rectangle {
|
||||
property var fileSearchController: null
|
||||
|
||||
function resetScroll() {
|
||||
filesList.contentY = 0
|
||||
filesList.contentY = 0;
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 32
|
||||
z: 100
|
||||
visible: filesList.contentHeight > filesList.height && (filesList.currentIndex < filesList.count - 1 || filesList.contentY < filesList.contentHeight - filesList.height - 1)
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: filesList
|
||||
|
||||
@@ -30,18 +49,22 @@ Rectangle {
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count)
|
||||
return
|
||||
|
||||
const itemY = index * (itemHeight + itemSpacing)
|
||||
const itemBottom = itemY + itemHeight
|
||||
return;
|
||||
const itemY = index * (itemHeight + itemSpacing);
|
||||
const itemBottom = itemY + itemHeight;
|
||||
const fadeHeight = 32;
|
||||
const isLastItem = index === count - 1;
|
||||
if (itemY < contentY)
|
||||
contentY = itemY
|
||||
else if (itemBottom > contentY + height)
|
||||
contentY = itemBottom - height
|
||||
contentY = itemY;
|
||||
else if (itemBottom > contentY + height - (isLastItem ? 0 : fadeHeight))
|
||||
contentY = Math.min(itemBottom - height + (isLastItem ? 0 : fadeHeight), contentHeight - height);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.bottomMargin: 1
|
||||
model: fileSearchController ? fileSearchController.model : null
|
||||
currentIndex: fileSearchController ? fileSearchController.selectedIndex : -1
|
||||
clip: true
|
||||
@@ -53,26 +76,26 @@ Rectangle {
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
if (keyboardNavigationActive)
|
||||
ensureVisible(currentIndex)
|
||||
ensureVisible(currentIndex);
|
||||
}
|
||||
|
||||
onItemClicked: function (index) {
|
||||
if (fileSearchController) {
|
||||
const item = fileSearchController.model.get(index)
|
||||
fileSearchController.openFile(item.filePath)
|
||||
const item = fileSearchController.model.get(index);
|
||||
fileSearchController.openFile(item.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
onItemRightClicked: function (index) {
|
||||
if (fileSearchController) {
|
||||
const item = fileSearchController.model.get(index)
|
||||
fileSearchController.openFolder(item.filePath)
|
||||
const item = fileSearchController.model.get(index);
|
||||
fileSearchController.openFolder(item.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
onKeyboardNavigationReset: {
|
||||
if (fileSearchController)
|
||||
fileSearchController.keyboardNavigationActive = false
|
||||
fileSearchController.keyboardNavigationActive = false;
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
@@ -86,7 +109,7 @@ Rectangle {
|
||||
width: ListView.view.width
|
||||
height: filesList.itemHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: ListView.isCurrentItem ? Theme.primaryPressed : fileMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: ListView.isCurrentItem ? Theme.widgetBaseHoverColor : fileMouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
@@ -109,16 +132,16 @@ Rectangle {
|
||||
id: nerdIcon
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
const lowerName = fileName.toLowerCase()
|
||||
const lowerName = fileName.toLowerCase();
|
||||
if (lowerName.startsWith("dockerfile"))
|
||||
return "docker"
|
||||
return "docker";
|
||||
if (lowerName.startsWith("makefile"))
|
||||
return "makefile"
|
||||
return "makefile";
|
||||
if (lowerName.startsWith("license"))
|
||||
return "license"
|
||||
return "license";
|
||||
if (lowerName.startsWith("readme"))
|
||||
return "readme"
|
||||
return fileExtension.toLowerCase()
|
||||
return "readme";
|
||||
return fileExtension.toLowerCase();
|
||||
}
|
||||
size: Theme.fontSizeXLarge
|
||||
color: Theme.surfaceText
|
||||
@@ -196,18 +219,18 @@ Rectangle {
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (filesList.hoverUpdatesSelection && !filesList.keyboardNavigationActive)
|
||||
filesList.currentIndex = index
|
||||
filesList.currentIndex = index;
|
||||
}
|
||||
onPositionChanged: {
|
||||
filesList.keyboardNavigationReset()
|
||||
filesList.keyboardNavigationReset();
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
filesList.itemClicked(index)
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
filesList.itemRightClicked(index)
|
||||
}
|
||||
}
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
filesList.itemClicked(index);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
filesList.itemRightClicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,21 +242,21 @@ Rectangle {
|
||||
StyledText {
|
||||
property string displayText: {
|
||||
if (!fileSearchController) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
if (!DSearchService.dsearchAvailable) {
|
||||
return I18n.tr("DankSearch not available")
|
||||
return I18n.tr("DankSearch not available");
|
||||
}
|
||||
if (fileSearchController.isSearching) {
|
||||
return I18n.tr("Searching...")
|
||||
return I18n.tr("Searching...");
|
||||
}
|
||||
if (fileSearchController.searchQuery.length === 0) {
|
||||
return I18n.tr("Enter a search query")
|
||||
return I18n.tr("Enter a search query");
|
||||
}
|
||||
if (!fileSearchController.model || fileSearchController.model.count === 0) {
|
||||
return I18n.tr("No files found")
|
||||
return I18n.tr("No files found");
|
||||
}
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
|
||||
text: displayText
|
||||
|
||||
@@ -12,7 +12,7 @@ DankModal {
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [spotlightModal.contentWindow]
|
||||
active: CompositorService.isHyprland && spotlightModal.shouldHaveFocus
|
||||
active: spotlightModal.useHyprlandFocusGrab && spotlightModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
property bool spotlightOpen: false
|
||||
|
||||
@@ -51,6 +51,33 @@ Rectangle {
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 32
|
||||
z: 100
|
||||
visible: {
|
||||
if (!appLauncher)
|
||||
return false;
|
||||
const view = appLauncher.viewMode === "list" ? resultsList : (gridLoader.item || resultsList);
|
||||
const isLastItem = appLauncher.viewMode === "list" ? view.currentIndex >= view.count - 1 : (gridLoader.item ? Math.floor(view.currentIndex / view.actualColumns) >= Math.floor((view.count - 1) / view.actualColumns) : false);
|
||||
const hasOverflow = view.contentHeight > view.height;
|
||||
const atBottom = view.contentY >= view.contentHeight - view.height - 1;
|
||||
return hasOverflow && (!isLastItem || !atBottom);
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: resultsList
|
||||
|
||||
@@ -70,14 +97,19 @@ Rectangle {
|
||||
return;
|
||||
const itemY = index * (itemHeight + itemSpacing);
|
||||
const itemBottom = itemY + itemHeight;
|
||||
const fadeHeight = 32;
|
||||
const isLastItem = index === count - 1;
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
else if (itemBottom > contentY + height)
|
||||
contentY = itemBottom - height;
|
||||
else if (itemBottom > contentY + height - (isLastItem ? 0 : fadeHeight))
|
||||
contentY = Math.min(itemBottom - height + (isLastItem ? 0 : fadeHeight), contentHeight - height);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.bottomMargin: 1
|
||||
visible: appLauncher && appLauncher.viewMode === "list"
|
||||
model: appLauncher ? appLauncher.model : null
|
||||
currentIndex: appLauncher ? appLauncher.selectedIndex : -1
|
||||
@@ -127,7 +159,10 @@ Rectangle {
|
||||
property real _lastWidth: 0
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.bottomMargin: 1
|
||||
visible: appLauncher && appLauncher.viewMode === "grid"
|
||||
active: appLauncher && appLauncher.viewMode === "grid"
|
||||
asynchronous: false
|
||||
@@ -177,10 +212,12 @@ Rectangle {
|
||||
return;
|
||||
const itemY = Math.floor(index / actualColumns) * cellHeight;
|
||||
const itemBottom = itemY + cellHeight;
|
||||
const fadeHeight = 32;
|
||||
const isLastRow = Math.floor(index / actualColumns) >= Math.floor((count - 1) / actualColumns);
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
else if (itemBottom > contentY + height)
|
||||
contentY = itemBottom - height;
|
||||
else if (itemBottom > contentY + height - (isLastRow ? 0 : fadeHeight))
|
||||
contentY = Math.min(itemBottom - height + (isLastRow ? 0 : fadeHeight), contentHeight - height);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -406,6 +406,34 @@ DankPopout {
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 32
|
||||
z: 100
|
||||
visible: {
|
||||
if (appDrawerPopout.searchMode !== "apps")
|
||||
return false;
|
||||
const view = appLauncher.viewMode === "list" ? appList : appGrid;
|
||||
const isLastItem = view.currentIndex >= view.count - 1;
|
||||
const hasOverflow = view.contentHeight > view.height;
|
||||
const atBottom = view.contentY >= view.contentHeight - view.height - 1;
|
||||
return hasOverflow && (!isLastItem || !atBottom);
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: appList
|
||||
@@ -426,14 +454,16 @@ DankPopout {
|
||||
return;
|
||||
var itemY = index * (itemHeight + itemSpacing);
|
||||
var itemBottom = itemY + itemHeight;
|
||||
var fadeHeight = 32;
|
||||
var isLastItem = index === count - 1;
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
else if (itemBottom > contentY + height)
|
||||
contentY = itemBottom - height;
|
||||
else if (itemBottom > contentY + height - (isLastItem ? 0 : fadeHeight))
|
||||
contentY = Math.min(itemBottom - height + (isLastItem ? 0 : fadeHeight), contentHeight - height);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
anchors.bottomMargin: 1
|
||||
visible: appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "list"
|
||||
model: appLauncher.model
|
||||
currentIndex: appLauncher.selectedIndex
|
||||
@@ -511,14 +541,16 @@ DankPopout {
|
||||
return;
|
||||
var itemY = Math.floor(index / actualColumns) * cellHeight;
|
||||
var itemBottom = itemY + cellHeight;
|
||||
var fadeHeight = 32;
|
||||
var isLastRow = Math.floor(index / actualColumns) >= Math.floor((count - 1) / actualColumns);
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
else if (itemBottom > contentY + height)
|
||||
contentY = itemBottom - height;
|
||||
else if (itemBottom > contentY + height - (isLastRow ? 0 : fadeHeight))
|
||||
contentY = Math.min(itemBottom - height + (isLastRow ? 0 : fadeHeight), contentHeight - height);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
anchors.bottomMargin: 1
|
||||
visible: appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid"
|
||||
model: appLauncher.model
|
||||
clip: true
|
||||
|
||||
@@ -76,7 +76,7 @@ DankPopout {
|
||||
return WlrKeyboardFocus.None;
|
||||
if (anyModalOpen)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.isHyprland)
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ Item {
|
||||
active: borderFullPathCorrectShape && borderEdgePathCorrectShape
|
||||
|
||||
readonly property real _scale: CompositorService.getScreenScale(barWindow.screen)
|
||||
readonly property real borderThickness: Theme.px(Math.max(1, barConfig?.borderThickness ?? 1), _scale)
|
||||
readonly property real borderThickness: Math.ceil(Math.max(1, barConfig?.borderThickness ?? 1) * _scale) / _scale
|
||||
readonly property real inset: borderThickness / 2
|
||||
readonly property string borderColorKey: barConfig?.borderColor || "surfaceText"
|
||||
readonly property color baseColor: (borderColorKey === "surfaceText") ? Theme.surfaceText : (borderColorKey === "primary") ? Theme.primary : Theme.secondary
|
||||
|
||||
@@ -427,7 +427,7 @@ Item {
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (!root.menuOpen)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.isHyprland)
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
@@ -436,7 +436,7 @@ Item {
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [overflowMenu]
|
||||
active: CompositorService.isHyprland && root.menuOpen
|
||||
active: CompositorService.useHyprlandFocusGrab && root.menuOpen
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -915,7 +915,7 @@ Item {
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (!menuRoot.showMenu)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.isHyprland)
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
@@ -923,7 +923,7 @@ Item {
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [menuWindow]
|
||||
active: CompositorService.isHyprland && menuRoot.showMenu
|
||||
active: CompositorService.useHyprlandFocusGrab && menuRoot.showMenu
|
||||
}
|
||||
|
||||
anchors {
|
||||
|
||||
@@ -89,12 +89,17 @@ Item {
|
||||
|
||||
function saveNewBind(bindData) {
|
||||
KeybindsService.saveBind("", bindData);
|
||||
showingNewBind = false;
|
||||
selectedCategory = "";
|
||||
_editingKey = bindData.key;
|
||||
expandedKey = bindData.action;
|
||||
}
|
||||
|
||||
function _onSaveSuccess() {
|
||||
if (showingNewBind) {
|
||||
showingNewBind = false;
|
||||
selectedCategory = "";
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToTop() {
|
||||
flickable.contentY = 0;
|
||||
}
|
||||
@@ -121,6 +126,10 @@ Item {
|
||||
keybindsTab._savedScrollY = flickable.contentY;
|
||||
keybindsTab._preserveScroll = true;
|
||||
}
|
||||
function onBindSaveCompleted(success) {
|
||||
if (success)
|
||||
keybindsTab._onSaveSuccess();
|
||||
}
|
||||
function onBindRemoved(key) {
|
||||
keybindsTab._savedScrollY = flickable.contentY;
|
||||
keybindsTab._preserveScroll = true;
|
||||
|
||||
@@ -4,6 +4,7 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Scope {
|
||||
id: overviewScope
|
||||
@@ -32,7 +33,13 @@ Scope {
|
||||
WlrLayershell.namespace: "dms:workspace-overview"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: overviewScope.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (!overviewScope.overviewOpen)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
@@ -63,7 +70,8 @@ Scope {
|
||||
function onOverviewOpenChanged() {
|
||||
if (overviewScope.overviewOpen) {
|
||||
grab.hasBeenActivated = false
|
||||
delayedGrabTimer.start()
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
delayedGrabTimer.start()
|
||||
} else {
|
||||
delayedGrabTimer.stop()
|
||||
grab.active = false
|
||||
@@ -75,6 +83,8 @@ Scope {
|
||||
Connections {
|
||||
target: root
|
||||
function onMonitorIsFocusedChanged() {
|
||||
if (!CompositorService.useHyprlandFocusGrab)
|
||||
return;
|
||||
if (overviewScope.overviewOpen && root.monitorIsFocused && !grab.active) {
|
||||
grab.hasBeenActivated = false
|
||||
grab.active = true
|
||||
@@ -89,7 +99,7 @@ Scope {
|
||||
interval: 150
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (overviewScope.overviewOpen && root.monitorIsFocused) {
|
||||
if (CompositorService.useHyprlandFocusGrab && overviewScope.overviewOpen && root.monitorIsFocused) {
|
||||
grab.active = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ Singleton {
|
||||
property bool isSway: false
|
||||
property bool isLabwc: false
|
||||
property string compositor: "unknown"
|
||||
readonly property bool useHyprlandFocusGrab: isHyprland && Quickshell.env("DMS_HYPRLAND_EXCLUSIVE_FOCUS") !== "1"
|
||||
|
||||
readonly property string hyprlandSignature: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE")
|
||||
readonly property string niriSocket: Quickshell.env("NIRI_SOCKET")
|
||||
|
||||
@@ -55,6 +55,7 @@ Singleton {
|
||||
|
||||
signal bindsLoaded
|
||||
signal bindSaved(string key)
|
||||
signal bindSaveCompleted(bool success)
|
||||
signal bindRemoved(string key)
|
||||
signal dmsBindsFixed
|
||||
|
||||
@@ -118,12 +119,14 @@ Singleton {
|
||||
|
||||
onExited: exitCode => {
|
||||
root.saving = false;
|
||||
if (exitCode === 0) {
|
||||
root.lastError = "";
|
||||
root.loadBinds(false);
|
||||
} else {
|
||||
if (exitCode !== 0) {
|
||||
console.error("[KeybindsService] Save failed with code:", exitCode);
|
||||
root.bindSaveCompleted(false);
|
||||
return;
|
||||
}
|
||||
root.lastError = "";
|
||||
root.bindSaveCompleted(true);
|
||||
root.loadBinds(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,12 +144,12 @@ Singleton {
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
root.lastError = "";
|
||||
root.loadBinds(false);
|
||||
} else {
|
||||
if (exitCode !== 0) {
|
||||
console.error("[KeybindsService] Remove failed with code:", exitCode);
|
||||
return;
|
||||
}
|
||||
root.lastError = "";
|
||||
root.loadBinds(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,15 +168,15 @@ Singleton {
|
||||
|
||||
onExited: exitCode => {
|
||||
root.fixing = false;
|
||||
if (exitCode === 0) {
|
||||
root.lastError = "";
|
||||
root.dmsBindsIncluded = true;
|
||||
root.dmsBindsFixed();
|
||||
ToastService.showSuccess(I18n.tr("Binds include added"), I18n.tr("dms/binds.kdl is now included in config.kdl"), "", "keybinds");
|
||||
Qt.callLater(root.forceReload);
|
||||
} else {
|
||||
if (exitCode !== 0) {
|
||||
console.error("[KeybindsService] Fix failed with code:", exitCode);
|
||||
return;
|
||||
}
|
||||
root.lastError = "";
|
||||
root.dmsBindsIncluded = true;
|
||||
root.dmsBindsFixed();
|
||||
ToastService.showSuccess(I18n.tr("Binds include added"), I18n.tr("dms/binds.kdl is now included in config.kdl"), "", "keybinds");
|
||||
Qt.callLater(root.forceReload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ Rectangle {
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
signal keyboardNavigationReset
|
||||
|
||||
width: cellWidth
|
||||
height: cellHeight
|
||||
width: cellWidth - 1
|
||||
height: cellHeight - 1
|
||||
radius: Theme.cornerRadius
|
||||
color: currentIndex === index ? Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency) : mouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency) : "transparent"
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
@@ -29,12 +27,12 @@ Rectangle {
|
||||
|
||||
signal itemClicked(int index, var modelData)
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
signal keyboardNavigationReset()
|
||||
signal keyboardNavigationReset
|
||||
|
||||
width: listView.width
|
||||
height: itemHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: isCurrentItem ? Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency) : mouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency) : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: isCurrentItem ? Theme.widgetBaseHoverColor : mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
@@ -97,27 +95,27 @@ Rectangle {
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (root.hoverUpdatesSelection && !root.keyboardNavigationActive)
|
||||
root.listView.currentIndex = root.index
|
||||
root.listView.currentIndex = root.index;
|
||||
}
|
||||
onPositionChanged: {
|
||||
root.keyboardNavigationReset()
|
||||
root.keyboardNavigationReset();
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.itemClicked(root.index, root.model)
|
||||
root.itemClicked(root.index, root.model);
|
||||
}
|
||||
}
|
||||
onPressAndHold: mouse => {
|
||||
if (!root.isPlugin) {
|
||||
const globalPos = mapToItem(null, mouse.x, mouse.y)
|
||||
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y)
|
||||
const globalPos = mapToItem(null, mouse.x, mouse.y);
|
||||
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y);
|
||||
}
|
||||
}
|
||||
onPressed: mouse => {
|
||||
if (mouse.button === Qt.RightButton && !root.isPlugin) {
|
||||
const globalPos = mapToItem(null, mouse.x, mouse.y)
|
||||
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y)
|
||||
mouse.accepted = true
|
||||
const globalPos = mapToItem(null, mouse.x, mouse.y);
|
||||
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y);
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Flickable {
|
||||
@@ -24,7 +23,7 @@ Flickable {
|
||||
WheelHandler {
|
||||
id: wheelHandler
|
||||
|
||||
property real touchpadSpeed: 1.8
|
||||
property real touchpadSpeed: 2.8
|
||||
property real momentumRetention: 0.92
|
||||
property real lastWheelTime: 0
|
||||
property real momentum: 0
|
||||
@@ -32,102 +31,104 @@ Flickable {
|
||||
property bool sessionUsedMouseWheel: false
|
||||
|
||||
function startMomentum() {
|
||||
flickable.isMomentumActive = true
|
||||
momentumTimer.start()
|
||||
flickable.isMomentumActive = true;
|
||||
momentumTimer.start();
|
||||
}
|
||||
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
|
||||
onWheel: event => {
|
||||
vbar._scrollBarActive = true
|
||||
vbar.hideTimer.restart()
|
||||
vbar._scrollBarActive = true;
|
||||
vbar.hideTimer.restart();
|
||||
|
||||
const currentTime = Date.now()
|
||||
const timeDelta = currentTime - lastWheelTime
|
||||
lastWheelTime = currentTime
|
||||
const currentTime = Date.now();
|
||||
const timeDelta = currentTime - lastWheelTime;
|
||||
lastWheelTime = currentTime;
|
||||
|
||||
const deltaY = event.angleDelta.y
|
||||
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0
|
||||
const hasPixel = event.pixelDelta && event.pixelDelta.y !== 0;
|
||||
const hasAngle = event.angleDelta && event.angleDelta.y !== 0;
|
||||
const deltaY = event.angleDelta.y;
|
||||
const isMouseWheel = !hasPixel && hasAngle;
|
||||
|
||||
if (isMouseWheel) {
|
||||
sessionUsedMouseWheel = true
|
||||
momentumTimer.stop()
|
||||
flickable.isMomentumActive = false
|
||||
velocitySamples = []
|
||||
momentum = 0
|
||||
flickable.momentumVelocity = 0
|
||||
if (isMouseWheel) {
|
||||
sessionUsedMouseWheel = true;
|
||||
momentumTimer.stop();
|
||||
flickable.isMomentumActive = false;
|
||||
velocitySamples = [];
|
||||
momentum = 0;
|
||||
flickable.momentumVelocity = 0;
|
||||
|
||||
const lines = Math.floor(Math.abs(deltaY) / 120)
|
||||
const scrollAmount = (deltaY > 0 ? -lines : lines) * flickable.mouseWheelSpeed
|
||||
let newY = flickable.contentY + scrollAmount
|
||||
newY = Math.max(0, Math.min(flickable.contentHeight - flickable.height, newY))
|
||||
const lines = Math.floor(Math.abs(deltaY) / 120);
|
||||
const scrollAmount = (deltaY > 0 ? -lines : lines) * flickable.mouseWheelSpeed;
|
||||
let newY = flickable.contentY + scrollAmount;
|
||||
newY = Math.max(0, Math.min(flickable.contentHeight - flickable.height, newY));
|
||||
|
||||
if (flickable.flicking) {
|
||||
flickable.cancelFlick()
|
||||
}
|
||||
if (flickable.flicking) {
|
||||
flickable.cancelFlick();
|
||||
}
|
||||
|
||||
flickable.contentY = newY
|
||||
} else {
|
||||
sessionUsedMouseWheel = false
|
||||
momentumTimer.stop()
|
||||
flickable.isMomentumActive = false
|
||||
flickable.contentY = newY;
|
||||
} else {
|
||||
sessionUsedMouseWheel = false;
|
||||
momentumTimer.stop();
|
||||
flickable.isMomentumActive = false;
|
||||
|
||||
let delta = 0
|
||||
if (event.pixelDelta.y !== 0) {
|
||||
delta = event.pixelDelta.y * touchpadSpeed
|
||||
} else {
|
||||
delta = event.angleDelta.y / 8 * touchpadSpeed
|
||||
}
|
||||
let delta = 0;
|
||||
if (event.pixelDelta.y !== 0) {
|
||||
delta = event.pixelDelta.y * touchpadSpeed;
|
||||
} else {
|
||||
delta = event.angleDelta.y / 8 * touchpadSpeed;
|
||||
}
|
||||
|
||||
velocitySamples.push({
|
||||
"delta": delta,
|
||||
"time": currentTime
|
||||
})
|
||||
velocitySamples = velocitySamples.filter(s => currentTime - s.time < 100)
|
||||
velocitySamples.push({
|
||||
"delta": delta,
|
||||
"time": currentTime
|
||||
});
|
||||
velocitySamples = velocitySamples.filter(s => currentTime - s.time < 100);
|
||||
|
||||
if (velocitySamples.length > 1) {
|
||||
const totalDelta = velocitySamples.reduce((sum, s) => sum + s.delta, 0)
|
||||
const timeSpan = currentTime - velocitySamples[0].time
|
||||
if (timeSpan > 0) {
|
||||
flickable.momentumVelocity = Math.max(-flickable.maxMomentumVelocity, Math.min(flickable.maxMomentumVelocity, totalDelta / timeSpan * 1000))
|
||||
}
|
||||
}
|
||||
if (velocitySamples.length > 1) {
|
||||
const totalDelta = velocitySamples.reduce((sum, s) => sum + s.delta, 0);
|
||||
const timeSpan = currentTime - velocitySamples[0].time;
|
||||
if (timeSpan > 0) {
|
||||
flickable.momentumVelocity = Math.max(-flickable.maxMomentumVelocity, Math.min(flickable.maxMomentumVelocity, totalDelta / timeSpan * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
if (event.pixelDelta.y !== 0 && timeDelta < 50) {
|
||||
momentum = momentum * momentumRetention + delta * 0.15
|
||||
delta += momentum
|
||||
} else {
|
||||
momentum = 0
|
||||
}
|
||||
if (event.pixelDelta.y !== 0 && timeDelta < 50) {
|
||||
momentum = momentum * momentumRetention + delta * 0.15;
|
||||
delta += momentum;
|
||||
} else {
|
||||
momentum = 0;
|
||||
}
|
||||
|
||||
let newY = flickable.contentY - delta
|
||||
newY = Math.max(0, Math.min(flickable.contentHeight - flickable.height, newY))
|
||||
let newY = flickable.contentY - delta;
|
||||
newY = Math.max(0, Math.min(flickable.contentHeight - flickable.height, newY));
|
||||
|
||||
if (flickable.flicking) {
|
||||
flickable.cancelFlick()
|
||||
}
|
||||
if (flickable.flicking) {
|
||||
flickable.cancelFlick();
|
||||
}
|
||||
|
||||
flickable.contentY = newY
|
||||
}
|
||||
flickable.contentY = newY;
|
||||
}
|
||||
|
||||
event.accepted = true
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (!active) {
|
||||
if (!sessionUsedMouseWheel && Math.abs(flickable.momentumVelocity) >= flickable.minMomentumVelocity) {
|
||||
startMomentum()
|
||||
startMomentum();
|
||||
} else {
|
||||
velocitySamples = []
|
||||
flickable.momentumVelocity = 0
|
||||
velocitySamples = [];
|
||||
flickable.momentumVelocity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMovementStarted: {
|
||||
vbar._scrollBarActive = true
|
||||
vbar.hideTimer.stop()
|
||||
vbar._scrollBarActive = true;
|
||||
vbar.hideTimer.stop();
|
||||
}
|
||||
onMovementEnded: vbar.hideTimer.restart()
|
||||
|
||||
@@ -137,24 +138,24 @@ Flickable {
|
||||
repeat: true
|
||||
|
||||
onTriggered: {
|
||||
const newY = flickable.contentY - flickable.momentumVelocity * 0.016
|
||||
const maxY = Math.max(0, flickable.contentHeight - flickable.height)
|
||||
const newY = flickable.contentY - flickable.momentumVelocity * 0.016;
|
||||
const maxY = Math.max(0, flickable.contentHeight - flickable.height);
|
||||
|
||||
if (newY < 0 || newY > maxY) {
|
||||
flickable.contentY = newY < 0 ? 0 : maxY
|
||||
stop()
|
||||
flickable.isMomentumActive = false
|
||||
flickable.momentumVelocity = 0
|
||||
return
|
||||
flickable.contentY = newY < 0 ? 0 : maxY;
|
||||
stop();
|
||||
flickable.isMomentumActive = false;
|
||||
flickable.momentumVelocity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
flickable.contentY = newY
|
||||
flickable.momentumVelocity *= flickable.friction
|
||||
flickable.contentY = newY;
|
||||
flickable.momentumVelocity *= flickable.friction;
|
||||
|
||||
if (Math.abs(flickable.momentumVelocity) < 5) {
|
||||
stop()
|
||||
flickable.isMomentumActive = false
|
||||
flickable.momentumVelocity = 0
|
||||
stop();
|
||||
flickable.isMomentumActive = false;
|
||||
flickable.momentumVelocity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ GridView {
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
onMovementStarted: {
|
||||
vbar._scrollBarActive = true
|
||||
vbar.hideTimer.stop()
|
||||
vbar._scrollBarActive = true;
|
||||
vbar.hideTimer.stop();
|
||||
}
|
||||
onMovementEnded: vbar.hideTimer.restart()
|
||||
|
||||
@@ -28,7 +28,7 @@ GridView {
|
||||
id: wheelHandler
|
||||
|
||||
property real mouseWheelSpeed: 60
|
||||
property real touchpadSpeed: 1.8
|
||||
property real touchpadSpeed: 2.8
|
||||
property real momentumRetention: 0.92
|
||||
property real lastWheelTime: 0
|
||||
property real momentum: 0
|
||||
@@ -36,87 +36,89 @@ GridView {
|
||||
property bool sessionUsedMouseWheel: false
|
||||
|
||||
function startMomentum() {
|
||||
isMomentumActive = true
|
||||
momentumTimer.start()
|
||||
isMomentumActive = true;
|
||||
momentumTimer.start();
|
||||
}
|
||||
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
onWheel: event => {
|
||||
vbar._scrollBarActive = true
|
||||
vbar.hideTimer.restart()
|
||||
vbar._scrollBarActive = true;
|
||||
vbar.hideTimer.restart();
|
||||
|
||||
const currentTime = Date.now()
|
||||
const timeDelta = currentTime - lastWheelTime
|
||||
lastWheelTime = currentTime
|
||||
const currentTime = Date.now();
|
||||
const timeDelta = currentTime - lastWheelTime;
|
||||
lastWheelTime = currentTime;
|
||||
|
||||
const deltaY = event.angleDelta.y
|
||||
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0
|
||||
const hasPixel = event.pixelDelta && event.pixelDelta.y !== 0;
|
||||
const hasAngle = event.angleDelta && event.angleDelta.y !== 0;
|
||||
const deltaY = event.angleDelta.y;
|
||||
const isMouseWheel = !hasPixel && hasAngle;
|
||||
|
||||
if (isMouseWheel) {
|
||||
sessionUsedMouseWheel = true
|
||||
momentumTimer.stop()
|
||||
isMomentumActive = false
|
||||
velocitySamples = []
|
||||
momentum = 0
|
||||
momentumVelocity = 0
|
||||
if (isMouseWheel) {
|
||||
sessionUsedMouseWheel = true;
|
||||
momentumTimer.stop();
|
||||
isMomentumActive = false;
|
||||
velocitySamples = [];
|
||||
momentum = 0;
|
||||
momentumVelocity = 0;
|
||||
|
||||
const lines = Math.floor(Math.abs(deltaY) / 120)
|
||||
const scrollAmount = (deltaY > 0 ? -lines : lines) * cellHeight * 0.35
|
||||
let newY = contentY + scrollAmount
|
||||
newY = Math.max(0, Math.min(contentHeight - height, newY))
|
||||
const lines = Math.floor(Math.abs(deltaY) / 120);
|
||||
const scrollAmount = (deltaY > 0 ? -lines : lines) * cellHeight * 0.35;
|
||||
let newY = contentY + scrollAmount;
|
||||
newY = Math.max(0, Math.min(contentHeight - height, newY));
|
||||
|
||||
if (flicking) {
|
||||
cancelFlick()
|
||||
}
|
||||
if (flicking) {
|
||||
cancelFlick();
|
||||
}
|
||||
|
||||
contentY = newY
|
||||
} else {
|
||||
sessionUsedMouseWheel = false
|
||||
momentumTimer.stop()
|
||||
isMomentumActive = false
|
||||
contentY = newY;
|
||||
} else {
|
||||
sessionUsedMouseWheel = false;
|
||||
momentumTimer.stop();
|
||||
isMomentumActive = false;
|
||||
|
||||
let delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y * touchpadSpeed : event.angleDelta.y / 120 * cellHeight * 1.2
|
||||
let delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y * touchpadSpeed : event.angleDelta.y / 120 * cellHeight * 1.2;
|
||||
|
||||
velocitySamples.push({
|
||||
"delta": delta,
|
||||
"time": currentTime
|
||||
})
|
||||
velocitySamples = velocitySamples.filter(s => currentTime - s.time < 100)
|
||||
velocitySamples.push({
|
||||
"delta": delta,
|
||||
"time": currentTime
|
||||
});
|
||||
velocitySamples = velocitySamples.filter(s => currentTime - s.time < 100);
|
||||
|
||||
if (velocitySamples.length > 1) {
|
||||
const totalDelta = velocitySamples.reduce((sum, s) => sum + s.delta, 0)
|
||||
const timeSpan = currentTime - velocitySamples[0].time
|
||||
if (timeSpan > 0) {
|
||||
momentumVelocity = Math.max(-maxMomentumVelocity, Math.min(maxMomentumVelocity, totalDelta / timeSpan * 1000))
|
||||
}
|
||||
}
|
||||
if (velocitySamples.length > 1) {
|
||||
const totalDelta = velocitySamples.reduce((sum, s) => sum + s.delta, 0);
|
||||
const timeSpan = currentTime - velocitySamples[0].time;
|
||||
if (timeSpan > 0) {
|
||||
momentumVelocity = Math.max(-maxMomentumVelocity, Math.min(maxMomentumVelocity, totalDelta / timeSpan * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
if (event.pixelDelta.y !== 0 && timeDelta < 50) {
|
||||
momentum = momentum * momentumRetention + delta * 0.15
|
||||
delta += momentum
|
||||
} else {
|
||||
momentum = 0
|
||||
}
|
||||
if (event.pixelDelta.y !== 0 && timeDelta < 50) {
|
||||
momentum = momentum * momentumRetention + delta * 0.15;
|
||||
delta += momentum;
|
||||
} else {
|
||||
momentum = 0;
|
||||
}
|
||||
|
||||
let newY = contentY - delta
|
||||
newY = Math.max(0, Math.min(contentHeight - height, newY))
|
||||
let newY = contentY - delta;
|
||||
newY = Math.max(0, Math.min(contentHeight - height, newY));
|
||||
|
||||
if (flicking) {
|
||||
cancelFlick()
|
||||
}
|
||||
if (flicking) {
|
||||
cancelFlick();
|
||||
}
|
||||
|
||||
contentY = newY
|
||||
}
|
||||
contentY = newY;
|
||||
}
|
||||
|
||||
event.accepted = true
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
onActiveChanged: {
|
||||
if (!active) {
|
||||
if (!sessionUsedMouseWheel && Math.abs(momentumVelocity) >= minMomentumVelocity) {
|
||||
startMomentum()
|
||||
startMomentum();
|
||||
} else {
|
||||
velocitySamples = []
|
||||
momentumVelocity = 0
|
||||
velocitySamples = [];
|
||||
momentumVelocity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,24 +129,24 @@ GridView {
|
||||
interval: 16
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
const newY = contentY - momentumVelocity * 0.016
|
||||
const maxY = Math.max(0, contentHeight - height)
|
||||
const newY = contentY - momentumVelocity * 0.016;
|
||||
const maxY = Math.max(0, contentHeight - height);
|
||||
|
||||
if (newY < 0 || newY > maxY) {
|
||||
contentY = newY < 0 ? 0 : maxY
|
||||
stop()
|
||||
isMomentumActive = false
|
||||
momentumVelocity = 0
|
||||
return
|
||||
contentY = newY < 0 ? 0 : maxY;
|
||||
stop();
|
||||
isMomentumActive = false;
|
||||
momentumVelocity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
contentY = newY
|
||||
momentumVelocity *= friction
|
||||
contentY = newY;
|
||||
momentumVelocity *= friction;
|
||||
|
||||
if (Math.abs(momentumVelocity) < 5) {
|
||||
stop()
|
||||
isMomentumActive = false
|
||||
momentumVelocity = 0
|
||||
stop();
|
||||
isMomentumActive = false;
|
||||
momentumVelocity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,130 +23,132 @@ ListView {
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
onMovementStarted: {
|
||||
isUserScrolling = true
|
||||
vbar._scrollBarActive = true
|
||||
vbar.hideTimer.stop()
|
||||
isUserScrolling = true;
|
||||
vbar._scrollBarActive = true;
|
||||
vbar.hideTimer.stop();
|
||||
}
|
||||
onMovementEnded: {
|
||||
isUserScrolling = false
|
||||
vbar.hideTimer.restart()
|
||||
isUserScrolling = false;
|
||||
vbar.hideTimer.restart();
|
||||
}
|
||||
|
||||
onContentYChanged: {
|
||||
if (!justChanged && isUserScrolling) {
|
||||
savedY = contentY
|
||||
savedY = contentY;
|
||||
}
|
||||
justChanged = false
|
||||
justChanged = false;
|
||||
}
|
||||
|
||||
onModelChanged: {
|
||||
justChanged = true
|
||||
contentY = savedY
|
||||
justChanged = true;
|
||||
contentY = savedY;
|
||||
}
|
||||
|
||||
WheelHandler {
|
||||
id: wheelHandler
|
||||
property real touchpadSpeed: 1.8
|
||||
property real touchpadSpeed: 2.8
|
||||
property real lastWheelTime: 0
|
||||
property real momentum: 0
|
||||
property var velocitySamples: []
|
||||
property bool sessionUsedMouseWheel: false
|
||||
|
||||
function startMomentum() {
|
||||
isMomentumActive = true
|
||||
momentumTimer.start()
|
||||
isMomentumActive = true;
|
||||
momentumTimer.start();
|
||||
}
|
||||
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
|
||||
onWheel: event => {
|
||||
isUserScrolling = true
|
||||
vbar._scrollBarActive = true
|
||||
vbar.hideTimer.restart()
|
||||
isUserScrolling = true;
|
||||
vbar._scrollBarActive = true;
|
||||
vbar.hideTimer.restart();
|
||||
|
||||
const currentTime = Date.now()
|
||||
const timeDelta = currentTime - lastWheelTime
|
||||
lastWheelTime = currentTime
|
||||
const currentTime = Date.now();
|
||||
const timeDelta = currentTime - lastWheelTime;
|
||||
lastWheelTime = currentTime;
|
||||
|
||||
const deltaY = event.angleDelta.y
|
||||
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0
|
||||
const hasPixel = event.pixelDelta && event.pixelDelta.y !== 0;
|
||||
const hasAngle = event.angleDelta && event.angleDelta.y !== 0;
|
||||
const deltaY = event.angleDelta.y;
|
||||
const isMouseWheel = !hasPixel && hasAngle;
|
||||
|
||||
if (isMouseWheel) {
|
||||
sessionUsedMouseWheel = true
|
||||
momentumTimer.stop()
|
||||
isMomentumActive = false
|
||||
velocitySamples = []
|
||||
momentum = 0
|
||||
momentumVelocity = 0
|
||||
if (isMouseWheel) {
|
||||
sessionUsedMouseWheel = true;
|
||||
momentumTimer.stop();
|
||||
isMomentumActive = false;
|
||||
velocitySamples = [];
|
||||
momentum = 0;
|
||||
momentumVelocity = 0;
|
||||
|
||||
const lines = Math.floor(Math.abs(deltaY) / 120)
|
||||
const scrollAmount = (deltaY > 0 ? -lines : lines) * mouseWheelSpeed
|
||||
let newY = listView.contentY + scrollAmount
|
||||
const maxY = Math.max(0, listView.contentHeight - listView.height + listView.originY)
|
||||
newY = Math.max(listView.originY, Math.min(maxY, newY))
|
||||
const lines = Math.floor(Math.abs(deltaY) / 120);
|
||||
const scrollAmount = (deltaY > 0 ? -lines : lines) * mouseWheelSpeed;
|
||||
let newY = listView.contentY + scrollAmount;
|
||||
const maxY = Math.max(0, listView.contentHeight - listView.height + listView.originY);
|
||||
newY = Math.max(listView.originY, Math.min(maxY, newY));
|
||||
|
||||
if (listView.flicking) {
|
||||
listView.cancelFlick()
|
||||
}
|
||||
if (listView.flicking) {
|
||||
listView.cancelFlick();
|
||||
}
|
||||
|
||||
listView.contentY = newY
|
||||
savedY = newY
|
||||
} else {
|
||||
sessionUsedMouseWheel = false
|
||||
momentumTimer.stop()
|
||||
isMomentumActive = false
|
||||
listView.contentY = newY;
|
||||
savedY = newY;
|
||||
} else {
|
||||
sessionUsedMouseWheel = false;
|
||||
momentumTimer.stop();
|
||||
isMomentumActive = false;
|
||||
|
||||
let delta = 0
|
||||
if (event.pixelDelta.y !== 0) {
|
||||
delta = event.pixelDelta.y * touchpadSpeed
|
||||
} else {
|
||||
delta = event.angleDelta.y / 8 * touchpadSpeed
|
||||
}
|
||||
let delta = 0;
|
||||
if (event.pixelDelta.y !== 0) {
|
||||
delta = event.pixelDelta.y * touchpadSpeed;
|
||||
} else {
|
||||
delta = event.angleDelta.y / 8 * touchpadSpeed;
|
||||
}
|
||||
|
||||
velocitySamples.push({
|
||||
"delta": delta,
|
||||
"time": currentTime
|
||||
})
|
||||
velocitySamples = velocitySamples.filter(s => currentTime - s.time < 100)
|
||||
velocitySamples.push({
|
||||
"delta": delta,
|
||||
"time": currentTime
|
||||
});
|
||||
velocitySamples = velocitySamples.filter(s => currentTime - s.time < 100);
|
||||
|
||||
if (velocitySamples.length > 1) {
|
||||
const totalDelta = velocitySamples.reduce((sum, s) => sum + s.delta, 0)
|
||||
const timeSpan = currentTime - velocitySamples[0].time
|
||||
if (timeSpan > 0) {
|
||||
momentumVelocity = Math.max(-maxMomentumVelocity, Math.min(maxMomentumVelocity, totalDelta / timeSpan * 1000))
|
||||
}
|
||||
}
|
||||
if (velocitySamples.length > 1) {
|
||||
const totalDelta = velocitySamples.reduce((sum, s) => sum + s.delta, 0);
|
||||
const timeSpan = currentTime - velocitySamples[0].time;
|
||||
if (timeSpan > 0) {
|
||||
momentumVelocity = Math.max(-maxMomentumVelocity, Math.min(maxMomentumVelocity, totalDelta / timeSpan * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
if (event.pixelDelta.y !== 0 && timeDelta < 50) {
|
||||
momentum = momentum * 0.92 + delta * 0.15
|
||||
delta += momentum
|
||||
} else {
|
||||
momentum = 0
|
||||
}
|
||||
if (event.pixelDelta.y !== 0 && timeDelta < 50) {
|
||||
momentum = momentum * 0.92 + delta * 0.15;
|
||||
delta += momentum;
|
||||
} else {
|
||||
momentum = 0;
|
||||
}
|
||||
|
||||
let newY = listView.contentY - delta
|
||||
const maxY = Math.max(0, listView.contentHeight - listView.height + listView.originY)
|
||||
newY = Math.max(listView.originY, Math.min(maxY, newY))
|
||||
let newY = listView.contentY - delta;
|
||||
const maxY = Math.max(0, listView.contentHeight - listView.height + listView.originY);
|
||||
newY = Math.max(listView.originY, Math.min(maxY, newY));
|
||||
|
||||
if (listView.flicking) {
|
||||
listView.cancelFlick()
|
||||
}
|
||||
if (listView.flicking) {
|
||||
listView.cancelFlick();
|
||||
}
|
||||
|
||||
listView.contentY = newY
|
||||
savedY = newY
|
||||
}
|
||||
listView.contentY = newY;
|
||||
savedY = newY;
|
||||
}
|
||||
|
||||
event.accepted = true
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (!active) {
|
||||
isUserScrolling = false
|
||||
isUserScrolling = false;
|
||||
if (!sessionUsedMouseWheel && Math.abs(momentumVelocity) >= minMomentumVelocity) {
|
||||
startMomentum()
|
||||
startMomentum();
|
||||
} else {
|
||||
velocitySamples = []
|
||||
momentumVelocity = 0
|
||||
velocitySamples = [];
|
||||
momentumVelocity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,27 +160,27 @@ ListView {
|
||||
repeat: true
|
||||
|
||||
onTriggered: {
|
||||
const newY = contentY - momentumVelocity * 0.016
|
||||
const maxY = Math.max(0, contentHeight - height + originY)
|
||||
const minY = originY
|
||||
const newY = contentY - momentumVelocity * 0.016;
|
||||
const maxY = Math.max(0, contentHeight - height + originY);
|
||||
const minY = originY;
|
||||
|
||||
if (newY < minY || newY > maxY) {
|
||||
contentY = newY < minY ? minY : maxY
|
||||
savedY = contentY
|
||||
stop()
|
||||
isMomentumActive = false
|
||||
momentumVelocity = 0
|
||||
return
|
||||
contentY = newY < minY ? minY : maxY;
|
||||
savedY = contentY;
|
||||
stop();
|
||||
isMomentumActive = false;
|
||||
momentumVelocity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
contentY = newY
|
||||
savedY = newY
|
||||
momentumVelocity *= friction
|
||||
contentY = newY;
|
||||
savedY = newY;
|
||||
momentumVelocity *= friction;
|
||||
|
||||
if (Math.abs(momentumVelocity) < 5) {
|
||||
stop()
|
||||
isMomentumActive = false
|
||||
momentumVelocity = 0
|
||||
stop();
|
||||
isMomentumActive = false;
|
||||
momentumVelocity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ Item {
|
||||
return customKeyboardFocus;
|
||||
if (!shouldBeVisible)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.isHyprland)
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ Item {
|
||||
property bool addingNewKey: false
|
||||
property bool useCustomCompositor: false
|
||||
property var _shortcutInhibitor: null
|
||||
property bool _altShiftGhost: false
|
||||
|
||||
readonly property bool _shortcutInhibitorAvailable: {
|
||||
try {
|
||||
@@ -61,6 +62,11 @@ Item {
|
||||
|
||||
Component.onDestruction: _destroyShortcutInhibitor()
|
||||
|
||||
Component.onCompleted: {
|
||||
if (isNew && isExpanded)
|
||||
resetEdits();
|
||||
}
|
||||
|
||||
onIsExpandedChanged: {
|
||||
if (!isExpanded)
|
||||
return;
|
||||
@@ -86,7 +92,7 @@ Item {
|
||||
editDesc = bindData.desc || "";
|
||||
hasChanges = false;
|
||||
_actionType = Actions.getActionType(editAction);
|
||||
useCustomCompositor = _actionType === "compositor" && !Actions.isKnownCompositorAction(editAction);
|
||||
useCustomCompositor = _actionType === "compositor" && editAction && !Actions.isKnownCompositorAction(editAction);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -105,7 +111,7 @@ Item {
|
||||
editDesc = bindData.desc || "";
|
||||
hasChanges = false;
|
||||
_actionType = Actions.getActionType(editAction);
|
||||
useCustomCompositor = _actionType === "compositor" && !Actions.isKnownCompositorAction(editAction);
|
||||
useCustomCompositor = _actionType === "compositor" && editAction && !Actions.isKnownCompositorAction(editAction);
|
||||
}
|
||||
|
||||
function startAddingNewKey() {
|
||||
@@ -590,37 +596,93 @@ Item {
|
||||
Keys.onPressed: event => {
|
||||
if (!root.recording)
|
||||
return;
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
root.stopRecording();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
event.accepted = true;
|
||||
|
||||
switch (event.key) {
|
||||
case Qt.Key_Control:
|
||||
case Qt.Key_Shift:
|
||||
case Qt.Key_Alt:
|
||||
case Qt.Key_Meta:
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const mods = KeyUtils.modsFromEvent(event.modifiers);
|
||||
const key = KeyUtils.xkbKeyFromQtKey(event.key);
|
||||
if (key) {
|
||||
root.updateEdit({
|
||||
key: KeyUtils.formatToken(mods, key)
|
||||
});
|
||||
root.stopRecording();
|
||||
event.accepted = true;
|
||||
if (event.key === 0 && (event.modifiers & Qt.AltModifier)) {
|
||||
root._altShiftGhost = true;
|
||||
return;
|
||||
}
|
||||
|
||||
let mods = KeyUtils.modsFromEvent(event.modifiers);
|
||||
let qtKey = event.key;
|
||||
|
||||
if (root._altShiftGhost && (event.modifiers & Qt.AltModifier) && !mods.includes("Shift")) {
|
||||
mods.push("Shift");
|
||||
}
|
||||
root._altShiftGhost = false;
|
||||
|
||||
if (qtKey === Qt.Key_Backtab) {
|
||||
qtKey = Qt.Key_Tab;
|
||||
if (!mods.includes("Shift"))
|
||||
mods.push("Shift");
|
||||
}
|
||||
|
||||
const key = KeyUtils.xkbKeyFromQtKey(qtKey);
|
||||
if (!key) {
|
||||
console.warn("[KeybindItem] Unknown key:", event.key, "mods:", event.modifiers);
|
||||
return;
|
||||
}
|
||||
|
||||
root.updateEdit({
|
||||
key: KeyUtils.formatToken(mods, key)
|
||||
});
|
||||
root.stopRecording();
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: !root.recording
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.startRecording()
|
||||
hoverEnabled: true
|
||||
cursorShape: root.recording ? Qt.CrossCursor : Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
onClicked: {
|
||||
if (!root.recording)
|
||||
root.startRecording();
|
||||
}
|
||||
|
||||
onWheel: wheel => {
|
||||
if (!root.recording)
|
||||
return;
|
||||
|
||||
wheel.accepted = true;
|
||||
|
||||
const mods = [];
|
||||
if (wheel.modifiers & Qt.ControlModifier)
|
||||
mods.push("Ctrl");
|
||||
if (wheel.modifiers & Qt.ShiftModifier)
|
||||
mods.push("Shift");
|
||||
if (wheel.modifiers & Qt.AltModifier)
|
||||
mods.push("Alt");
|
||||
if (wheel.modifiers & Qt.MetaModifier)
|
||||
mods.push("Super");
|
||||
|
||||
let wheelKey = "";
|
||||
if (wheel.angleDelta.y > 0)
|
||||
wheelKey = "WheelScrollUp";
|
||||
else if (wheel.angleDelta.y < 0)
|
||||
wheelKey = "WheelScrollDown";
|
||||
else if (wheel.angleDelta.x > 0)
|
||||
wheelKey = "WheelScrollRight";
|
||||
else if (wheel.angleDelta.x < 0)
|
||||
wheelKey = "WheelScrollLeft";
|
||||
|
||||
if (!wheelKey)
|
||||
return;
|
||||
|
||||
root.updateEdit({
|
||||
key: KeyUtils.formatToken(mods, wheelKey)
|
||||
});
|
||||
root.stopRecording();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,6 +878,69 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingM
|
||||
|
||||
property var dmsArgConfig: {
|
||||
const action = root.editAction;
|
||||
if (!action)
|
||||
return null;
|
||||
if (action.indexOf("audio increment") !== -1 || action.indexOf("audio decrement") !== -1 || action.indexOf("brightness increment") !== -1 || action.indexOf("brightness decrement") !== -1) {
|
||||
const parts = action.split(" ");
|
||||
const lastPart = parts[parts.length - 1];
|
||||
const hasAmount = /^\d+$/.test(lastPart);
|
||||
return {
|
||||
hasAmount: hasAmount,
|
||||
amount: hasAmount ? lastPart : ""
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
visible: root._actionType === "dms" && dmsArgConfig !== null
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Amount")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
Layout.preferredWidth: 80
|
||||
Layout.preferredHeight: 40
|
||||
placeholderText: "5"
|
||||
text: parent.dmsArgConfig?.amount || ""
|
||||
onTextChanged: {
|
||||
if (!parent.dmsArgConfig)
|
||||
return;
|
||||
const action = root.editAction;
|
||||
const parts = action.split(" ");
|
||||
const lastPart = parts[parts.length - 1];
|
||||
const hasOldAmount = /^\d+$/.test(lastPart);
|
||||
if (hasOldAmount)
|
||||
parts.pop();
|
||||
if (text && /^\d+$/.test(text))
|
||||
parts.push(text);
|
||||
root.updateEdit({
|
||||
action: parts.join(" ")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingM
|
||||
@@ -898,6 +1023,154 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingM
|
||||
visible: root._actionType === "compositor" && !root.useCustomCompositor && Actions.getActionArgConfig(root.editAction)
|
||||
|
||||
property var argConfig: Actions.getActionArgConfig(root.editAction)
|
||||
property var parsedArgs: Actions.parseCompositorActionArgs(root.editAction)
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Options")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
Layout.preferredWidth: 60
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankTextField {
|
||||
id: argValueField
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
visible: {
|
||||
const cfg = parent.parent.argConfig;
|
||||
if (!cfg || !cfg.config || !cfg.config.args)
|
||||
return false;
|
||||
const firstArg = cfg.config.args[0];
|
||||
return firstArg && (firstArg.type === "text" || firstArg.type === "number");
|
||||
}
|
||||
placeholderText: {
|
||||
const cfg = parent.parent.argConfig;
|
||||
if (!cfg || !cfg.config || !cfg.config.args)
|
||||
return "";
|
||||
return cfg.config.args[0]?.placeholder || "";
|
||||
}
|
||||
text: parent.parent.parsedArgs?.args?.value || parent.parent.parsedArgs?.args?.index || ""
|
||||
onTextChanged: {
|
||||
const cfg = parent.parent.argConfig;
|
||||
if (!cfg)
|
||||
return;
|
||||
const base = parent.parent.parsedArgs?.base || root.editAction.split(" ")[0];
|
||||
const args = cfg.config.args[0]?.type === "number" ? {
|
||||
index: text
|
||||
} : {
|
||||
value: text
|
||||
};
|
||||
root.updateEdit({
|
||||
action: Actions.buildCompositorAction(base, args)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: {
|
||||
const cfg = parent.parent.argConfig;
|
||||
return cfg && cfg.base === "move-column-to-workspace";
|
||||
}
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankToggle {
|
||||
id: focusToggle
|
||||
checked: parent.parent.parent.parsedArgs?.args?.focus === true
|
||||
onCheckedChanged: {
|
||||
const cfg = parent.parent.parent.argConfig;
|
||||
if (!cfg)
|
||||
return;
|
||||
const parsed = parent.parent.parent.parsedArgs;
|
||||
const args = {
|
||||
index: parsed?.args?.index || "",
|
||||
focus: checked
|
||||
};
|
||||
root.updateEdit({
|
||||
action: Actions.buildCompositorAction("move-column-to-workspace", args)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Follow focus")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: {
|
||||
const cfg = parent.parent.argConfig;
|
||||
return cfg && cfg.base && cfg.base.startsWith("screenshot");
|
||||
}
|
||||
spacing: Theme.spacingM
|
||||
|
||||
RowLayout {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankToggle {
|
||||
id: writeToDiskToggle
|
||||
checked: parent.parent.parent.parent.parsedArgs?.args?.opts?.["write-to-disk"] === "true"
|
||||
onCheckedChanged: {
|
||||
const parsed = parent.parent.parent.parent.parsedArgs;
|
||||
const base = parsed?.base || "screenshot";
|
||||
const opts = parsed?.args?.opts || {};
|
||||
opts["write-to-disk"] = checked ? "true" : "";
|
||||
root.updateEdit({
|
||||
action: Actions.buildCompositorAction(base, {
|
||||
opts: opts
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Save")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankToggle {
|
||||
id: showPointerToggle
|
||||
checked: parent.parent.parent.parent.parsedArgs?.args?.opts?.["show-pointer"] === "true"
|
||||
onCheckedChanged: {
|
||||
const parsed = parent.parent.parent.parent.parsedArgs;
|
||||
const base = parsed?.base || "screenshot";
|
||||
const opts = parsed?.args?.opts || {};
|
||||
opts["show-pointer"] = checked ? "true" : "";
|
||||
root.updateEdit({
|
||||
action: Actions.buildCompositorAction(base, {
|
||||
opts: opts
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Pointer")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Reference in New Issue
Block a user