buninu-browser 0.2.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES/jsmdcui.txt +22 -0
- package/README.md +102 -2
- package/frontends/wayland/LICENSE-land +21 -0
- package/frontends/wayland/README.md +123 -0
- package/frontends/wayland/casty/LICENSE +21 -0
- package/frontends/wayland/casty/bookmarks.js +32 -0
- package/frontends/wayland/casty/click.js +96 -0
- package/frontends/wayland/casty/commands.js +61 -0
- package/frontends/wayland/casty/config.js +45 -0
- package/frontends/wayland/casty/download-directory.js +28 -0
- package/frontends/wayland/casty/download.js +49 -0
- package/frontends/wayland/casty/hints.js +334 -0
- package/frontends/wayland/casty/keys.js +56 -0
- package/frontends/wayland/casty/page-commands.js +80 -0
- package/frontends/wayland/casty/prompt-width.js +148 -0
- package/frontends/wayland/casty/urlbar.js +606 -0
- package/frontends/wayland/client.js +88 -0
- package/frontends/wayland/clipboard.js +80 -0
- package/frontends/wayland/index.js +116 -0
- package/frontends/wayland/input.js +56 -0
- package/frontends/wayland/protocol.js +55 -0
- package/frontends/wayland/protocols/wayland.xml +3248 -0
- package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
- package/frontends/wayland/transport.js +120 -0
- package/frontends/wayland/urlbar.js +158 -0
- package/index.js +28 -1
- package/lib/clipboard-command.js +10 -0
- package/lib/clipboard-powershell.js +22 -0
- package/lib/clipboard.js +57 -0
- package/lib/document-types.js +11 -0
- package/lib/headless-shell.js +78 -13
- package/lib/layout/text-layout.js +17 -9
- package/lib/paint/display-list.js +5 -1
- package/lib/paint/frame.js +35 -3
- package/lib/paint/raw-frames.js +47 -0
- package/lib/qml/README.md +971 -0
- package/lib/qml/builtins.js +700 -0
- package/lib/qml/canvas-dom.js +115 -0
- package/lib/qml/document.js +1176 -0
- package/lib/qml/format.js +9 -0
- package/lib/qml/host.js +324 -0
- package/lib/qml/images.js +199 -0
- package/lib/qml/launcher.js +132 -0
- package/lib/qml/loader.js +517 -0
- package/lib/qml/media-renderer.js +49 -0
- package/lib/qml/media.js +86 -0
- package/lib/qml/models.js +622 -0
- package/lib/qml/parser.js +381 -0
- package/lib/qml/qs +5 -0
- package/lib/qml/qscasty +3 -0
- package/lib/qml/qsheadless +3 -0
- package/lib/qml/qswayland +3 -0
- package/lib/qml/reactive.js +159 -0
- package/lib/qml/runtime.js +3381 -0
- package/lib/qml/shapes.js +243 -0
- package/lib/qml/text.js +109 -0
- package/lib/qml/textures.js +123 -0
- package/lib/qml/wayland-clipboard.js +32 -0
- package/lib/qml/worker-thread.js +70 -0
- package/lib/render-tree/index.js +16 -1
- package/lib/renderer/bindings.js +11 -0
- package/lib/renderer/canvas-2d.js +13 -0
- package/lib/renderer/compositor-channel.js +103 -0
- package/lib/renderer/compositor.js +25 -33
- package/lib/renderer/flags.js +1 -1
- package/lib/renderer/host.js +128 -92
- package/lib/renderer/main.js +38 -4
- package/lib/renderer/page-realm.js +32 -1
- package/lib/renderer/page-renderer.js +250 -21
- package/lib/renderer/text-input-paint.js +54 -0
- package/lib/renderer/text-input.js +57 -0
- package/lib/style/computed-style.js +2 -2
- package/package.json +1 -1
- package/test/clipboard.test.js +54 -0
- package/test/fixtures/browser-raw-demo.html +36 -0
- package/test/fixtures/flea-helpers/Dockerfile +36 -0
- package/test/fixtures/flea-helpers/bat.mjs +43 -0
- package/test/fixtures/flea-helpers/bin/bat +3 -0
- package/test/fixtures/flea-helpers/bin/bwrap +10 -0
- package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
- package/test/fixtures/flea-helpers/bin/glow +3 -0
- package/test/fixtures/flea-helpers/bin/qs +3 -0
- package/test/fixtures/flea-helpers/bin/unsupported +3 -0
- package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
- package/test/fixtures/flea-helpers/check-media.mjs +29 -0
- package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
- package/test/fixtures/flea-helpers/check.mjs +36 -0
- package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
- package/test/fixtures/flea-helpers/source-mime.xml +28 -0
- package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
- package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
- package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
- package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea/LICENSE +21 -0
- package/test/fixtures/qml/flea/README.md +25 -0
- package/test/fixtures/qml/flea/Theme.qml +8 -0
- package/test/fixtures/qml/flea/demo.qml +24 -0
- package/test/fixtures/qml/flea/qmldir +3 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
- package/test/fixtures/qml/flea-theme/README.md +70 -0
- package/test/fixtures/qml/flea-theme/button.qml +17 -0
- package/test/fixtures/qml/flea-theme/demo.qml +27 -0
- package/test/fixtures/qml/flea-theme/field.qml +21 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
- package/test/fixtures/qml/flea-theme/segment.qml +17 -0
- package/test/fixtures/qml/flea-theme/settings.qml +58 -0
- package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
- package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
- package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
- package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
- package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
- package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
- package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
- package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
- package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
- package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
- package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
- package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
- package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
- package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
- package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
- package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
- package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
- package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
- package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
- package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
- package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
- package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
- package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
- package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
- package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
- package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
- package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
- package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
- package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
- package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
- package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
- package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
- package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
- package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
- package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
- package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
- package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
- package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
- package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
- package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
- package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
- package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
- package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
- package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
- package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
- package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
- package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
- package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
- package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
- package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
- package/test/fixtures/qml/hello.qml +26 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
- package/test/fixtures/qml/imports/components/Badge.qml +15 -0
- package/test/fixtures/qml/imports/logic/base.js +2 -0
- package/test/fixtures/qml/imports/logic/counter.js +4 -0
- package/test/fixtures/qml/imports/logic/math.js +3 -0
- package/test/fixtures/qml/imports/logic/shared.js +3 -0
- package/test/fixtures/qml/imports/main.qml +21 -0
- package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
- package/test/fixtures/qml/imports/theme/qmldir +2 -0
- package/test/fixtures/qml/multiply.qml +61 -0
- package/test/fixtures/qml-canvas-dom.js +90 -0
- package/test/fixtures/qml-media-audio.js +39 -0
- package/test/fixtures/raw-capture-benchmark.json +76 -0
- package/test/helpers/flea-sandbox.js +48 -0
- package/test/helpers/quickshell-fixture.js +125 -0
- package/test/html-clipboard-wayland.test.js +47 -0
- package/test/index.test.js +13 -0
- package/test/manual-casty-without-qml.js +87 -0
- package/test/manual-flea-application.js +49 -0
- package/test/manual-flea-binary.js +62 -0
- package/test/manual-flea-qml.js +9 -0
- package/test/manual-flea-settings.js +40 -0
- package/test/manual-flea-theme.js +40 -0
- package/test/manual-qml-removal.js +46 -0
- package/test/manual-without-qml.js +45 -0
- package/test/qml-canvas-dom.test.js +37 -0
- package/test/qml-clipboard-bridge.test.js +25 -0
- package/test/qml-clipboard-wayland.test.js +40 -0
- package/test/qml-connections-timing.test.js +73 -0
- package/test/qml-dynamic.test.js +401 -0
- package/test/qml-flea-app.test.js +146 -0
- package/test/qml-flea-backend.test.js +78 -0
- package/test/qml-flea-columns-backend.test.js +38 -0
- package/test/qml-flea-compile.test.js +78 -0
- package/test/qml-flea-launcher.test.js +88 -0
- package/test/qml-flea-markdown-backend.test.js +31 -0
- package/test/qml-flea-media.test.js +28 -0
- package/test/qml-flea-navigation-backend.test.js +35 -0
- package/test/qml-flea-navigation-timing.test.js +147 -0
- package/test/qml-flea-preview-backend.test.js +53 -0
- package/test/qml-flea-previews.test.js +163 -0
- package/test/qml-flea-rows.test.js +101 -0
- package/test/qml-flea-textures.test.js +59 -0
- package/test/qml-flea-view-switch.test.js +33 -0
- package/test/qml-flea-wayland.test.js +68 -0
- package/test/qml-flea.test.js +34 -0
- package/test/qml-host.test.js +238 -0
- package/test/qml-imports.test.js +102 -0
- package/test/qml-inherited-handlers.test.js +51 -0
- package/test/qml-input-selection.test.js +66 -0
- package/test/qml-interactions.test.js +309 -0
- package/test/qml-language.test.js +167 -0
- package/test/qml-launcher.test.js +54 -0
- package/test/qml-layout-text.test.js +205 -0
- package/test/qml-media.test.js +63 -0
- package/test/qml-persisted-delegates.test.js +45 -0
- package/test/qml-quickshell-controls.test.js +65 -0
- package/test/qml-quickshell-settings.test.js +62 -0
- package/test/qml-repeater.test.js +72 -0
- package/test/qml-style-updates.test.js +124 -0
- package/test/qml-textures.test.js +66 -0
- package/test/qml-views.test.js +332 -0
- package/test/qml-worker.test.js +69 -0
- package/test/qml.test.js +83 -0
- package/test/raw-capture.test.js +152 -0
- package/test/renderer-host.test.js +11 -0
- package/test/text-input-pointer.test.js +31 -0
- package/test/text-input-scroll.test.js +29 -0
- package/test/text-input.test.js +47 -0
- package/test/wayland-frontend.test.js +83 -0
- package/test/wayland-urlbar.test.js +38 -0
- package/test/window-resize.test.js +36 -0
- package/tools/bench-raw-capture.js +32 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
pragma Singleton
|
|
2
|
+
import Quickshell
|
|
3
|
+
import QtQuick
|
|
4
|
+
|
|
5
|
+
// Shared utility helpers used across plugins. Pure functions only — no
|
|
6
|
+
// state. Anything stateful belongs on Color, Style, or a service.
|
|
7
|
+
QtObject {
|
|
8
|
+
id: root
|
|
9
|
+
|
|
10
|
+
// Manual click forwarding must honor the same clipping as Qt's hit test.
|
|
11
|
+
// Collapsed drawers keep their children laid out beyond the visible edge.
|
|
12
|
+
function itemContainsPoint(target, source, x, y) {
|
|
13
|
+
if (!target || !target.contains(target.mapFromItem(source, x, y))) return false
|
|
14
|
+
for (var item = target; item; item = item.parent) {
|
|
15
|
+
if (!item.visible || item.opacity === 0 || !item.enabled) return false
|
|
16
|
+
if (item.clip && !item.contains(item.mapFromItem(source, x, y))) return false
|
|
17
|
+
}
|
|
18
|
+
return true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function clamp(value, min, max) {
|
|
22
|
+
var n = Number(value)
|
|
23
|
+
if (!isFinite(n)) return min
|
|
24
|
+
return Math.max(min, Math.min(max, n))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function clampAlpha(value) {
|
|
28
|
+
return clamp(value, 0, 1)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function wheelSteps(accumulator, delta) {
|
|
32
|
+
// Some mouse/compositor combinations scale a single notch well beyond
|
|
33
|
+
// Qt's conventional 120 units. Keep one event to one step while still
|
|
34
|
+
// accumulating the smaller deltas emitted by touchpads.
|
|
35
|
+
delta = Math.max(-120, Math.min(120, delta))
|
|
36
|
+
if (accumulator * delta < 0) accumulator = 0
|
|
37
|
+
var total = accumulator + delta
|
|
38
|
+
var steps = total < 0 ? Math.ceil(total / 120) : Math.floor(total / 120)
|
|
39
|
+
return { steps: steps, remainder: total - steps * 120 }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Compose a base color with an opacity. Accepts a color object or a hex
|
|
43
|
+
// string; null/undefined yields transparent black at the requested alpha.
|
|
44
|
+
function alpha(c, opacity) {
|
|
45
|
+
var a = clampAlpha(opacity)
|
|
46
|
+
if (!c) return Qt.rgba(0, 0, 0, a)
|
|
47
|
+
if (typeof c === "string") c = Qt.color(c)
|
|
48
|
+
return Qt.rgba(c.r, c.g, c.b, a)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// file:// URL with each path segment percent-encoded so spaces and
|
|
52
|
+
// special chars in user paths don't break Image.source.
|
|
53
|
+
function fileUrl(path) {
|
|
54
|
+
if (!path) return ""
|
|
55
|
+
return "file://" + String(path).split("/").map(encodeURIComponent).join("/")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isVideoPath(path) {
|
|
59
|
+
return /\.(mp4|m4v|mov|webm|mkv|avi)$/i.test(String(path || ""))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Single-quote a string for bash. The replace handles embedded single
|
|
63
|
+
// quotes by closing, escaping, and re-opening the literal.
|
|
64
|
+
function shellQuote(value) {
|
|
65
|
+
return "'" + String(value || "").replace(/'/g, "'\\''") + "'"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function execDetached(command) {
|
|
69
|
+
Quickshell.execDetached(["bash", "-lc", command])
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Run an argv vector without a shell interpreting it: the constant `exec "$@"`
|
|
73
|
+
// means the args only ever land in positional parameters, which bash expands
|
|
74
|
+
// without re-tokenizing — so untrusted data ($(id), a filename) stays literal.
|
|
75
|
+
// The login shell (-l) keeps the PATH/session env GUI targets (omasnap, mpv,
|
|
76
|
+
// xdg-open) need. Prefer this over execDetached for anything built from input.
|
|
77
|
+
function execArgv(argv) {
|
|
78
|
+
Quickshell.execDetached(["bash", "-lc", 'exec "$@"', "bash"].concat(argv))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isPlainObject(value) {
|
|
82
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function canonicalWidgetId(id) {
|
|
86
|
+
return String(id || "")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Best-effort base64 decode. Returns "" on parse failure rather than
|
|
90
|
+
// surfacing garbage downstream.
|
|
91
|
+
function decodeBase64(value) {
|
|
92
|
+
var s = String(value || "")
|
|
93
|
+
if (!s) return ""
|
|
94
|
+
try { return Qt.atob(s) } catch (e) { return "" }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function cloneJson(value) {
|
|
98
|
+
return JSON.parse(JSON.stringify(value === undefined ? null : value))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Parse the last line of a custom-module / indicator process output as
|
|
102
|
+
// waybar-style JSON ({text, class, tooltip, ...}). Falls back to {text: raw}
|
|
103
|
+
// when the output isn't JSON, and {} for empty output.
|
|
104
|
+
function parseModuleJson(raw) {
|
|
105
|
+
var text = String(raw || "").trim()
|
|
106
|
+
if (!text) return {}
|
|
107
|
+
var lines = text.split("\n")
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(lines[lines.length - 1])
|
|
110
|
+
} catch (e) {
|
|
111
|
+
return { text: text }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Standard Qt text-editing keys shared by every searchable panel's filter:
|
|
116
|
+
// Backspace delete previous character
|
|
117
|
+
// Ctrl+Backspace delete previous word (Qt DeleteStartOfWord)
|
|
118
|
+
// Ctrl+U clear the whole field
|
|
119
|
+
// True only when the event would actually change the text, so an empty
|
|
120
|
+
// filter never swallows the key — panels keep their own empty-filter
|
|
121
|
+
// fallbacks (e.g. menu back-navigation) in later branches.
|
|
122
|
+
// The arrow a horizontal key press means along the reading direction: in a
|
|
123
|
+
// right-to-left layout, mirrored, Left moves forward and Right back. With
|
|
124
|
+
// vim set, h and l count as Left and Right too.
|
|
125
|
+
function readingKey(event, vim) {
|
|
126
|
+
var key = event.key
|
|
127
|
+
if (vim && (key === Qt.Key_H || event.text === "h")) key = Qt.Key_Left
|
|
128
|
+
else if (vim && (key === Qt.Key_L || event.text === "l")) key = Qt.Key_Right
|
|
129
|
+
if (I18n.rightToLeft) {
|
|
130
|
+
if (key === Qt.Key_Left) return Qt.Key_Right
|
|
131
|
+
if (key === Qt.Key_Right) return Qt.Key_Left
|
|
132
|
+
}
|
|
133
|
+
return key
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function editsFilter(event, text) {
|
|
137
|
+
if (!text) return false
|
|
138
|
+
// Alt/Meta-modified sequences belong to other shortcuts — never edit here.
|
|
139
|
+
if (event.modifiers & (Qt.AltModifier | Qt.MetaModifier)) return false
|
|
140
|
+
if (event.key === Qt.Key_U) // Ctrl+U only (not Ctrl+Shift+U → Unicode input)
|
|
141
|
+
return event.modifiers === Qt.ControlModifier
|
|
142
|
+
return event.key === Qt.Key_Backspace // plain, Shift, or Ctrl Backspace
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// New filter text after applying an edit key. Assumes editsFilter(event, text).
|
|
146
|
+
function editedFilter(event, text) {
|
|
147
|
+
if (event.key === Qt.Key_U) return "" // Ctrl+U: clear
|
|
148
|
+
if (event.modifiers & Qt.ControlModifier) // Ctrl+Backspace: word
|
|
149
|
+
return text.replace(/\s+$/, "").replace(/\S+$/, "")
|
|
150
|
+
return text.slice(0, -1) // Backspace: char
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Layout normalization shared by bar config consumers
|
|
154
|
+
// so the two never drift. Entries are deep-cloned to decouple from the
|
|
155
|
+
// input config; consumers can mutate without leaking back to shell.json.
|
|
156
|
+
function normalizeLayoutEntry(entry) {
|
|
157
|
+
if (typeof entry === "string") return { id: canonicalWidgetId(entry) }
|
|
158
|
+
if (isPlainObject(entry) && entry.id) {
|
|
159
|
+
var copy = cloneJson(entry)
|
|
160
|
+
copy.id = canonicalWidgetId(copy.id)
|
|
161
|
+
return copy
|
|
162
|
+
}
|
|
163
|
+
return null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeLayoutSection(list) {
|
|
167
|
+
if (!Array.isArray(list)) return []
|
|
168
|
+
var out = []
|
|
169
|
+
for (var i = 0; i < list.length; i++) {
|
|
170
|
+
var e = normalizeLayoutEntry(list[i])
|
|
171
|
+
if (e) out.push(e)
|
|
172
|
+
}
|
|
173
|
+
return out
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// The bar's sections in the order they run along it, and tray, drawn
|
|
177
|
+
// inside the tray's drawer. The center box sits in the middle of the bar;
|
|
178
|
+
// centerBegin and centerEnd hang off its two edges.
|
|
179
|
+
readonly property var barSections: ["left", "centerBegin", "center", "centerEnd", "right", "tray"]
|
|
180
|
+
|
|
181
|
+
function normalizeLayout(layout) {
|
|
182
|
+
var src = isPlainObject(layout) ? layout : {}
|
|
183
|
+
var out = {}
|
|
184
|
+
for (var i = 0; i < barSections.length; i++)
|
|
185
|
+
out[barSections[i]] = normalizeLayoutSection(src[barSections[i]])
|
|
186
|
+
return out
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module qs.Commons
|
|
2
|
+
singleton AudioNodes 1.0 AudioNodes.qml
|
|
3
|
+
singleton Border 1.0 Border.qml
|
|
4
|
+
singleton Color 1.0 Color.qml
|
|
5
|
+
singleton I18n 1.0 I18n.qml
|
|
6
|
+
singleton InputDevices 1.0 InputDevices.qml
|
|
7
|
+
singleton IpcRegistry 1.0 IpcRegistry.qml
|
|
8
|
+
singleton InputMethodState 1.0 InputMethodState.qml
|
|
9
|
+
singleton Style 1.0 Style.qml
|
|
10
|
+
singleton UntypedInput 1.0 UntypedInput.qml
|
|
11
|
+
singleton Util 1.0 Util.qml
|
|
12
|
+
ShellIpc 1.0 ShellIpc.qml
|
|
13
|
+
Translations 1.0 Translations.qml
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import Quickshell
|
|
3
|
+
import "./ui" as Flea
|
|
4
|
+
FloatingWindow {
|
|
5
|
+
id: demo
|
|
6
|
+
width: 800; height: 420
|
|
7
|
+
title: "Flea — settings segments"
|
|
8
|
+
color: Flea.Theme.color.background
|
|
9
|
+
property string choice: "List"
|
|
10
|
+
Column {
|
|
11
|
+
x: 24; y: 24; spacing: 24
|
|
12
|
+
Text {text:"Real Flea segmented choices";font.pixelSize:24;color:Flea.Theme.color.foreground}
|
|
13
|
+
Flea.SettingsSegment {options:["List","Grid","Columns"];value:demo.choice;onPicked:function(i){demo.choice=options[i]}}
|
|
14
|
+
Flea.SettingsSegment {options:["List","Grid","Columns"];glyphs:["list","grid","columns"];value:demo.choice;onPicked:function(i){demo.choice=options[i]}}
|
|
15
|
+
Text {id: status;text:"Selected: "+demo.choice;font.pixelSize:18;color:Flea.Theme.color.foreground}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import QtQuick.Controls
|
|
3
|
+
import Quickshell
|
|
4
|
+
import "./ui" as Flea
|
|
5
|
+
import "./ui/js/TextSize.js" as TextSize
|
|
6
|
+
FloatingWindow {
|
|
7
|
+
id: demo
|
|
8
|
+
width: 800; height: 420
|
|
9
|
+
title: "Flea — text size settings | " + Flea.Theme.baseSize + " | " + demo.clicks
|
|
10
|
+
color: Flea.Theme.color.background
|
|
11
|
+
property bool editing: true
|
|
12
|
+
property int clicks: 0
|
|
13
|
+
Text {
|
|
14
|
+
x: 24; y: 24
|
|
15
|
+
text: "Flea text size"
|
|
16
|
+
font.pixelSize: 24
|
|
17
|
+
color: Flea.Theme.color.foreground
|
|
18
|
+
}
|
|
19
|
+
Flea.SettingsRuler {
|
|
20
|
+
id: ruler
|
|
21
|
+
x: 24; y: 85; width: 740
|
|
22
|
+
stops: TextSize.STOPS
|
|
23
|
+
index: TextSize.STOPS.indexOf(Flea.Theme.baseSize)
|
|
24
|
+
active: demo.editing
|
|
25
|
+
onPicked: function (size) {
|
|
26
|
+
demo.clicks++
|
|
27
|
+
// Change the real shared ViewState in memory; this demo never writes settings.
|
|
28
|
+
Flea.ViewState.state = {display: {textSize: {mode: size}}}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
Text {
|
|
32
|
+
id: sample
|
|
33
|
+
x: 24; y: 165
|
|
34
|
+
text: "The quick brown fox — shared Theme"
|
|
35
|
+
color: Flea.Theme.color.foreground
|
|
36
|
+
font.family: Flea.Theme.font.family
|
|
37
|
+
font.pixelSize: Flea.Theme.font.body
|
|
38
|
+
}
|
|
39
|
+
Text {
|
|
40
|
+
id: status
|
|
41
|
+
x: 24; y: 220
|
|
42
|
+
text: "Size: " + Flea.Theme.baseSize + " / clicks: " + demo.clicks
|
|
43
|
+
color: Flea.Theme.color.muted
|
|
44
|
+
font.pixelSize: 16
|
|
45
|
+
}
|
|
46
|
+
Button {
|
|
47
|
+
id: toggle
|
|
48
|
+
x: 24; y: 280; width: 220; height: 44
|
|
49
|
+
text: demo.editing ? "Disable ruler" : "Enable ruler"
|
|
50
|
+
onClicked: demo.editing = !demo.editing
|
|
51
|
+
}
|
|
52
|
+
Button {
|
|
53
|
+
id: reset
|
|
54
|
+
x: 270; y: 280; width: 230; height: 44
|
|
55
|
+
text: "Follow Omarchy"
|
|
56
|
+
onClicked: Flea.ViewState.state = {display: {textSize: {mode: "system"}}}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import Quickshell
|
|
3
|
+
FloatingWindow {
|
|
4
|
+
width: 400; height: 240
|
|
5
|
+
title: "Text metrics | " + empty.implicitWidth + ":" + empty.implicitHeight + ":" + empty.lineCount + ":" + single.lineCount + ":" + multi.lineCount + ":" + clipped.lineCount + ":" + clipped.truncated
|
|
6
|
+
Text {id:empty;text:""}
|
|
7
|
+
Text {id:single;y:40;text:"one"}
|
|
8
|
+
Text {id:multi;y:80;text:"one\ntwo"}
|
|
9
|
+
Text {id:clipped;y:140;text:"one\ntwo\nthree";maximumLineCount:2;elide:Text.ElideRight}
|
|
10
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import qs.Commons
|
|
3
|
+
import "js/Buttons.js" as Buttons
|
|
4
|
+
|
|
5
|
+
// The one control every dialog, card, picker button and strip action draws.
|
|
6
|
+
Item {
|
|
7
|
+
id: root
|
|
8
|
+
|
|
9
|
+
property string label: ""
|
|
10
|
+
property bool primary: false
|
|
11
|
+
// A destructive action rests as the error label in a muted frame, never primary.
|
|
12
|
+
property bool destructive: false
|
|
13
|
+
property real horizontalPadding: Theme.spacing.gap
|
|
14
|
+
// An unavailable action shows muted ink and its press does nothing.
|
|
15
|
+
property bool available: true
|
|
16
|
+
// The keyboard's own signal. Callers whose focus sits on a wrapper pass it down.
|
|
17
|
+
property bool focused: root.activeFocus
|
|
18
|
+
// True where the form owns the Tab order and the button only reports through tabbed.
|
|
19
|
+
property bool tabHandle: false
|
|
20
|
+
// A stop in its dialog's own Tab walk; while unavailable it is disabled, so it leaves the walk and keeps its look.
|
|
21
|
+
property bool tabStop: false
|
|
22
|
+
// A member of a set (the protocols): its frame and label are foreground while it is the current one and muted otherwise.
|
|
23
|
+
property bool setMember: false
|
|
24
|
+
property bool current: false
|
|
25
|
+
// Hosted in the chrome strip: a Theme.chromeHeight press area around a Theme.chromeControlHeight frame at caption size, and Tab reaches it.
|
|
26
|
+
property bool inStrip: false
|
|
27
|
+
|
|
28
|
+
// The pointer's own state, read by the native capture harness before it shoots a hover or a press.
|
|
29
|
+
readonly property bool hovered: hover.hovered
|
|
30
|
+
readonly property bool pressed: tap.pressed
|
|
31
|
+
// A release activates only inside the press area, so a held point outside draws the resting control.
|
|
32
|
+
readonly property bool pressShown: root.pressed && root.available && tap.point.position.x >= 0 && tap.point.position.x < root.width
|
|
33
|
+
&& tap.point.position.y >= 0 && tap.point.position.y < root.height
|
|
34
|
+
|
|
35
|
+
signal activated()
|
|
36
|
+
// The form owns the order; a button only reports that Tab happened inside it.
|
|
37
|
+
signal tabbed(var from, bool back)
|
|
38
|
+
|
|
39
|
+
// Focus is the control's own frame: foreground, or accent for a set member where it has hue. Where a hairline would match the rest (a primary whose rest frame is the foreground, a set member without hue) the frame draws RING wide.
|
|
40
|
+
readonly property string focusFrame: Buttons.focusFrame(Theme.color.accentHasHue, root.setMember, root.primary && Qt.colorEqual(Theme.color.accentFrame, Theme.color.foreground))
|
|
41
|
+
readonly property bool focusShown: root.focused && root.available
|
|
42
|
+
// A secondary frame takes mutedFrame, ThemeRoles' border role, under a live label; a set member takes foreground while current and mutedFrame otherwise.
|
|
43
|
+
readonly property color frame: root.focusShown && root.focusFrame === "accent" ? Theme.color.accent
|
|
44
|
+
: root.focusShown ? Theme.color.foreground
|
|
45
|
+
: root.setMember ? (root.current ? Theme.color.foreground : Theme.color.mutedFrame)
|
|
46
|
+
: root.primary && root.available ? Theme.color.accentFrame : Theme.color.mutedFrame
|
|
47
|
+
readonly property color ink: !root.available || root.setMember && !root.current ? Theme.color.muted
|
|
48
|
+
: root.destructive ? Theme.color.error : Theme.color.foreground
|
|
49
|
+
// The frame and this wash say which action is being asked for; the label stays foreground.
|
|
50
|
+
readonly property color wash: root.primary && root.available ? Qt.alpha(Theme.color.accent, Buttons.WASH_PRIMARY) : "transparent"
|
|
51
|
+
// A border draws inside its rectangle, so the thick frame grows inward and nothing moves.
|
|
52
|
+
readonly property int frameWidth: root.focusShown && root.focusFrame === "thick" ? Buttons.RING : Theme.spacing.hairline
|
|
53
|
+
|
|
54
|
+
// A chrome strip's press area fills the strip and centres the control height above its rule.
|
|
55
|
+
readonly property real frameHeight: root.inStrip ? Theme.chromeControlHeight : root.height
|
|
56
|
+
readonly property real frameY: root.inStrip ? Math.round((root.height - Theme.spacing.hairline - root.frameHeight) / 2) : 0
|
|
57
|
+
|
|
58
|
+
implicitWidth: Math.max(Theme.hitMin, text.implicitWidth + 2 * root.horizontalPadding + 2 * Theme.spacing.hairline)
|
|
59
|
+
implicitHeight: root.inStrip ? Theme.chromeHeight : Theme.rowHeight - Theme.spacing.rowPaddingY
|
|
60
|
+
// A strip control or a tab stop stays in the Tab chain and drops out as a disabled item; flipping activeFocusOnTab under a focused control only warns.
|
|
61
|
+
activeFocusOnTab: root.inStrip || root.tabStop
|
|
62
|
+
enabled: root.available || !root.inStrip && !root.tabStop
|
|
63
|
+
opacity: root.available ? 1 : Buttons.DISABLED_OPACITY
|
|
64
|
+
|
|
65
|
+
Accessible.role: Accessible.Button
|
|
66
|
+
Accessible.name: root.label
|
|
67
|
+
Accessible.onPressAction: if (root.available) root.activated()
|
|
68
|
+
|
|
69
|
+
Item {
|
|
70
|
+
id: box
|
|
71
|
+
y: root.frameY
|
|
72
|
+
width: parent.width
|
|
73
|
+
height: root.frameHeight
|
|
74
|
+
|
|
75
|
+
// Only the frame and wash shrink: native text loses thin stems under a non-integer scale.
|
|
76
|
+
Item {
|
|
77
|
+
id: chrome
|
|
78
|
+
property int insetX: root.pressShown && !Theme.reducedMotion ? Math.round(box.width * (1 - Buttons.PRESS_SCALE) / 2) : 0
|
|
79
|
+
property int insetY: root.pressShown && !Theme.reducedMotion ? Math.round(box.height * (1 - Buttons.PRESS_SCALE) / 2) : 0
|
|
80
|
+
x: chrome.insetX
|
|
81
|
+
y: chrome.insetY
|
|
82
|
+
width: Math.round(box.width) - 2 * chrome.insetX
|
|
83
|
+
height: Math.round(box.height) - 2 * chrome.insetY
|
|
84
|
+
Behavior on insetX {
|
|
85
|
+
enabled: !Theme.reducedMotion
|
|
86
|
+
NumberAnimation { duration: Buttons.PRESS_MS; easing.type: Easing.OutCubic }
|
|
87
|
+
}
|
|
88
|
+
Behavior on insetY {
|
|
89
|
+
enabled: !Theme.reducedMotion
|
|
90
|
+
NumberAnimation { duration: Buttons.PRESS_MS; easing.type: Easing.OutCubic }
|
|
91
|
+
}
|
|
92
|
+
Rectangle {
|
|
93
|
+
objectName: "buttonFrame"
|
|
94
|
+
anchors.fill: parent
|
|
95
|
+
color: root.wash
|
|
96
|
+
border.width: root.frameWidth
|
|
97
|
+
border.color: root.frame
|
|
98
|
+
}
|
|
99
|
+
// Hover and press lay the control's own ink inside the frame at its width.
|
|
100
|
+
Rectangle {
|
|
101
|
+
objectName: "buttonWash"
|
|
102
|
+
anchors.fill: parent
|
|
103
|
+
anchors.margins: root.frameWidth
|
|
104
|
+
color: root.pressShown ? Qt.alpha(root.ink, Buttons.WASH_PRESS)
|
|
105
|
+
: !root.pressed && hover.hovered && root.available ? Qt.alpha(root.ink, Buttons.WASH_HOVER) : "transparent"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
Text {
|
|
110
|
+
id: text
|
|
111
|
+
objectName: "buttonLabel"
|
|
112
|
+
anchors.centerIn: parent
|
|
113
|
+
text: root.label
|
|
114
|
+
color: root.ink
|
|
115
|
+
font.family: Theme.font.family
|
|
116
|
+
font.pixelSize: root.inStrip ? Theme.font.caption : Theme.font.body
|
|
117
|
+
textFormat: Text.PlainText
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
HoverHandler {
|
|
122
|
+
id: hover
|
|
123
|
+
cursorShape: root.available ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// A held key's repeats never activate again, so a key held past a focus move cannot press the next button.
|
|
127
|
+
function keyActivate(event) { if (!root.available) event.accepted = false; else if (!event.isAutoRepeat) root.activated() }
|
|
128
|
+
|
|
129
|
+
// A focused button answers Enter exactly as a press does; a handled Tab is accepted here so Qt never moves focus past the form's own stepFocus order.
|
|
130
|
+
Keys.onReturnPressed: function(event) { root.keyActivate(event) }
|
|
131
|
+
Keys.onEnterPressed: function(event) { root.keyActivate(event) }
|
|
132
|
+
Keys.onSpacePressed: function(event) { root.keyActivate(event) }
|
|
133
|
+
Keys.onTabPressed: function(event) {
|
|
134
|
+
if (!root.tabHandle) { event.accepted = false; return }
|
|
135
|
+
root.tabbed(root, (event.modifiers & Qt.ShiftModifier) !== 0)
|
|
136
|
+
event.accepted = true
|
|
137
|
+
}
|
|
138
|
+
Keys.onBacktabPressed: function(event) {
|
|
139
|
+
if (!root.tabHandle) { event.accepted = false; return }
|
|
140
|
+
root.tabbed(root, true)
|
|
141
|
+
event.accepted = true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
TapHandler {
|
|
145
|
+
id: tap
|
|
146
|
+
acceptedButtons: Qt.LeftButton
|
|
147
|
+
gesturePolicy: TapHandler.ReleaseWithinBounds
|
|
148
|
+
onTapped: if (root.available) root.activated()
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import qs.Commons
|
|
3
|
+
import "." as Flea
|
|
4
|
+
|
|
5
|
+
// One labelled field, as the canvas's Network board draws it: a small uppercase label above a
|
|
6
|
+
// bordered box that takes the accent while it has the caret.
|
|
7
|
+
Item {
|
|
8
|
+
id: root
|
|
9
|
+
|
|
10
|
+
property string label: ""
|
|
11
|
+
// An alias, not a plain property assigned from onTextChanged: writing to a property that carries
|
|
12
|
+
// a binding destroys that binding, so a later programmatic set (switching protocol resets the
|
|
13
|
+
// port) would silently never reach the field.
|
|
14
|
+
property alias text: field.text
|
|
15
|
+
property string placeholder: ""
|
|
16
|
+
// A password box hides what it holds, and the eye beside it reveals while held.
|
|
17
|
+
property bool secret: false
|
|
18
|
+
// The field that holds a refused answer frames itself in the error role; off everywhere else.
|
|
19
|
+
property bool error: false
|
|
20
|
+
// Bullets that stand for a refused answer the field no longer holds; the first key replaces them.
|
|
21
|
+
property int heldMask: 0
|
|
22
|
+
|
|
23
|
+
signal accepted()
|
|
24
|
+
// Keys changed the text, so a card can drop the sentence the error frame stands for.
|
|
25
|
+
signal edited()
|
|
26
|
+
// The form owns the order; a field only reports that Tab happened inside it.
|
|
27
|
+
signal tabbed(var from, bool back)
|
|
28
|
+
|
|
29
|
+
readonly property alias input: field
|
|
30
|
+
readonly property bool focused: field.activeFocus
|
|
31
|
+
// Read by the capture harness: whether the eye is held, and its centre in window coordinates.
|
|
32
|
+
readonly property bool revealed: reveal.pressed
|
|
33
|
+
function eyeCentre() {
|
|
34
|
+
var at = eye.mapToItem(null, eye.width / 2, eye.height / 2)
|
|
35
|
+
return Math.round(at.x) + " " + Math.round(at.y)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function takeFocus() {
|
|
39
|
+
field.forceActiveFocus()
|
|
40
|
+
}
|
|
41
|
+
readonly property real labelSpacing: 0.1
|
|
42
|
+
|
|
43
|
+
implicitHeight: caption.implicitHeight + Theme.spacing.hairline * 4 + box.height
|
|
44
|
+
|
|
45
|
+
Text {
|
|
46
|
+
id: caption
|
|
47
|
+
anchors.top: parent.top
|
|
48
|
+
anchors.left: parent.left
|
|
49
|
+
text: root.label.toUpperCase()
|
|
50
|
+
color: Theme.color.muted
|
|
51
|
+
font.family: Theme.font.family
|
|
52
|
+
font.pixelSize: Theme.font.caption
|
|
53
|
+
font.letterSpacing: Theme.font.caption * root.labelSpacing
|
|
54
|
+
textFormat: Text.PlainText
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Rectangle {
|
|
58
|
+
id: box
|
|
59
|
+
anchors.top: caption.bottom
|
|
60
|
+
anchors.topMargin: Theme.spacing.hairline * 4
|
|
61
|
+
anchors.left: parent.left
|
|
62
|
+
anchors.right: parent.right
|
|
63
|
+
height: Theme.rowHeight - Theme.spacing.rowPaddingY
|
|
64
|
+
color: Theme.color.background
|
|
65
|
+
border.width: Theme.spacing.hairline
|
|
66
|
+
border.color: root.error ? Theme.color.error : (field.activeFocus ? Theme.color.accent : Theme.color.mutedFrame)
|
|
67
|
+
|
|
68
|
+
TextInput {
|
|
69
|
+
id: field
|
|
70
|
+
anchors.left: parent.left
|
|
71
|
+
anchors.right: eye.left
|
|
72
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
73
|
+
anchors.leftMargin: Theme.spacing.gap
|
|
74
|
+
anchors.rightMargin: Theme.spacing.gap
|
|
75
|
+
color: Theme.color.foreground
|
|
76
|
+
selectionColor: Theme.color.accent
|
|
77
|
+
selectedTextColor: Theme.color.background
|
|
78
|
+
font.family: Theme.font.family
|
|
79
|
+
font.pixelSize: Theme.font.body
|
|
80
|
+
clip: true
|
|
81
|
+
leftPadding: heldMarks.visible ? heldMarks.width : 0
|
|
82
|
+
// Revealed only while the eye is held, so a shoulder never reads it from a stuck toggle.
|
|
83
|
+
echoMode: root.secret && !reveal.pressed ? TextInput.Password : TextInput.Normal
|
|
84
|
+
// The boards draw a masked value as U+2022 bullets, never Qt's larger default circle.
|
|
85
|
+
passwordCharacter: "\u2022"
|
|
86
|
+
onAccepted: root.accepted()
|
|
87
|
+
onTextEdited: { root.heldMask = 0; root.edited() }
|
|
88
|
+
Keys.onPressed: function(event) {
|
|
89
|
+
if ((event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) && field.text.length === 0 && root.heldMask > 0) { root.heldMask = 0; root.edited() }
|
|
90
|
+
}
|
|
91
|
+
Keys.onTabPressed: function(event) { root.tabbed(root, (event.modifiers & Qt.ShiftModifier) !== 0) }
|
|
92
|
+
Keys.onBacktabPressed: root.tabbed(root, true)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Text {
|
|
96
|
+
id: heldMarks
|
|
97
|
+
anchors.left: field.left
|
|
98
|
+
anchors.verticalCenter: field.verticalCenter
|
|
99
|
+
visible: root.heldMask > 0 && field.text.length === 0 && !reveal.pressed
|
|
100
|
+
text: field.passwordCharacter.repeat(Math.max(0, root.heldMask))
|
|
101
|
+
color: Theme.color.foreground
|
|
102
|
+
font: field.font
|
|
103
|
+
textFormat: Text.PlainText
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Text {
|
|
107
|
+
anchors.left: field.left
|
|
108
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
109
|
+
visible: field.text.length === 0 && !field.activeFocus && !heldMarks.visible
|
|
110
|
+
text: root.placeholder
|
|
111
|
+
color: Theme.color.muted
|
|
112
|
+
font.family: Theme.font.family
|
|
113
|
+
font.pixelSize: Theme.font.body
|
|
114
|
+
textFormat: Text.PlainText
|
|
115
|
+
elide: Text.ElideRight
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
Item {
|
|
119
|
+
id: eye
|
|
120
|
+
anchors.right: parent.right
|
|
121
|
+
anchors.rightMargin: Theme.spacing.gap
|
|
122
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
123
|
+
width: root.secret ? Math.max(Theme.hitMin, Theme.font.bodySmall) : 0
|
|
124
|
+
height: root.secret ? Math.max(Theme.hitMin, Theme.font.bodySmall) : 0
|
|
125
|
+
|
|
126
|
+
Accessible.role: Accessible.Button
|
|
127
|
+
Accessible.name: "Show password"
|
|
128
|
+
Accessible.ignored: !root.secret
|
|
129
|
+
|
|
130
|
+
Flea.Glyph {
|
|
131
|
+
anchors.centerIn: parent
|
|
132
|
+
width: Theme.font.bodySmall
|
|
133
|
+
height: Theme.font.bodySmall
|
|
134
|
+
visible: root.secret
|
|
135
|
+
name: "eye"
|
|
136
|
+
color: reveal.pressed ? Theme.color.accent : Theme.color.mutedFrame
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
MouseArea {
|
|
140
|
+
id: reveal
|
|
141
|
+
anchors.fill: parent
|
|
142
|
+
enabled: root.secret
|
|
143
|
+
cursorShape: Qt.PointingHandCursor
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
|
|
3
|
+
// The word that closes a surface, in the corner every surface puts it in: Dialogs rule 7, and the
|
|
4
|
+
// spelling ui/SettingsPanel.qml already uses, so no two dialogs can name the way out differently.
|
|
5
|
+
Text {
|
|
6
|
+
text: "esc"
|
|
7
|
+
color: Theme.color.muted
|
|
8
|
+
font.family: Theme.font.family
|
|
9
|
+
font.pixelSize: Theme.font.caption
|
|
10
|
+
textFormat: Text.PlainText
|
|
11
|
+
}
|