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.
Files changed (249) hide show
  1. package/LICENSES/jsmdcui.txt +22 -0
  2. package/README.md +102 -2
  3. package/frontends/wayland/LICENSE-land +21 -0
  4. package/frontends/wayland/README.md +123 -0
  5. package/frontends/wayland/casty/LICENSE +21 -0
  6. package/frontends/wayland/casty/bookmarks.js +32 -0
  7. package/frontends/wayland/casty/click.js +96 -0
  8. package/frontends/wayland/casty/commands.js +61 -0
  9. package/frontends/wayland/casty/config.js +45 -0
  10. package/frontends/wayland/casty/download-directory.js +28 -0
  11. package/frontends/wayland/casty/download.js +49 -0
  12. package/frontends/wayland/casty/hints.js +334 -0
  13. package/frontends/wayland/casty/keys.js +56 -0
  14. package/frontends/wayland/casty/page-commands.js +80 -0
  15. package/frontends/wayland/casty/prompt-width.js +148 -0
  16. package/frontends/wayland/casty/urlbar.js +606 -0
  17. package/frontends/wayland/client.js +88 -0
  18. package/frontends/wayland/clipboard.js +80 -0
  19. package/frontends/wayland/index.js +116 -0
  20. package/frontends/wayland/input.js +56 -0
  21. package/frontends/wayland/protocol.js +55 -0
  22. package/frontends/wayland/protocols/wayland.xml +3248 -0
  23. package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
  24. package/frontends/wayland/transport.js +120 -0
  25. package/frontends/wayland/urlbar.js +158 -0
  26. package/index.js +28 -1
  27. package/lib/clipboard-command.js +10 -0
  28. package/lib/clipboard-powershell.js +22 -0
  29. package/lib/clipboard.js +57 -0
  30. package/lib/document-types.js +11 -0
  31. package/lib/headless-shell.js +78 -13
  32. package/lib/layout/text-layout.js +17 -9
  33. package/lib/paint/display-list.js +5 -1
  34. package/lib/paint/frame.js +35 -3
  35. package/lib/paint/raw-frames.js +47 -0
  36. package/lib/qml/README.md +971 -0
  37. package/lib/qml/builtins.js +700 -0
  38. package/lib/qml/canvas-dom.js +115 -0
  39. package/lib/qml/document.js +1176 -0
  40. package/lib/qml/format.js +9 -0
  41. package/lib/qml/host.js +324 -0
  42. package/lib/qml/images.js +199 -0
  43. package/lib/qml/launcher.js +132 -0
  44. package/lib/qml/loader.js +517 -0
  45. package/lib/qml/media-renderer.js +49 -0
  46. package/lib/qml/media.js +86 -0
  47. package/lib/qml/models.js +622 -0
  48. package/lib/qml/parser.js +381 -0
  49. package/lib/qml/qs +5 -0
  50. package/lib/qml/qscasty +3 -0
  51. package/lib/qml/qsheadless +3 -0
  52. package/lib/qml/qswayland +3 -0
  53. package/lib/qml/reactive.js +159 -0
  54. package/lib/qml/runtime.js +3381 -0
  55. package/lib/qml/shapes.js +243 -0
  56. package/lib/qml/text.js +109 -0
  57. package/lib/qml/textures.js +123 -0
  58. package/lib/qml/wayland-clipboard.js +32 -0
  59. package/lib/qml/worker-thread.js +70 -0
  60. package/lib/render-tree/index.js +16 -1
  61. package/lib/renderer/bindings.js +11 -0
  62. package/lib/renderer/canvas-2d.js +13 -0
  63. package/lib/renderer/compositor-channel.js +103 -0
  64. package/lib/renderer/compositor.js +25 -33
  65. package/lib/renderer/flags.js +1 -1
  66. package/lib/renderer/host.js +128 -92
  67. package/lib/renderer/main.js +38 -4
  68. package/lib/renderer/page-realm.js +32 -1
  69. package/lib/renderer/page-renderer.js +250 -21
  70. package/lib/renderer/text-input-paint.js +54 -0
  71. package/lib/renderer/text-input.js +57 -0
  72. package/lib/style/computed-style.js +2 -2
  73. package/package.json +1 -1
  74. package/test/clipboard.test.js +54 -0
  75. package/test/fixtures/browser-raw-demo.html +36 -0
  76. package/test/fixtures/flea-helpers/Dockerfile +36 -0
  77. package/test/fixtures/flea-helpers/bat.mjs +43 -0
  78. package/test/fixtures/flea-helpers/bin/bat +3 -0
  79. package/test/fixtures/flea-helpers/bin/bwrap +10 -0
  80. package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
  81. package/test/fixtures/flea-helpers/bin/glow +3 -0
  82. package/test/fixtures/flea-helpers/bin/qs +3 -0
  83. package/test/fixtures/flea-helpers/bin/unsupported +3 -0
  84. package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
  85. package/test/fixtures/flea-helpers/check-media.mjs +29 -0
  86. package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
  87. package/test/fixtures/flea-helpers/check.mjs +36 -0
  88. package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
  89. package/test/fixtures/flea-helpers/source-mime.xml +28 -0
  90. package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
  91. package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
  92. package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
  93. package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
  94. package/test/fixtures/qml/flea/LICENSE +21 -0
  95. package/test/fixtures/qml/flea/README.md +25 -0
  96. package/test/fixtures/qml/flea/Theme.qml +8 -0
  97. package/test/fixtures/qml/flea/demo.qml +24 -0
  98. package/test/fixtures/qml/flea/qmldir +3 -0
  99. package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
  100. package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
  101. package/test/fixtures/qml/flea-theme/README.md +70 -0
  102. package/test/fixtures/qml/flea-theme/button.qml +17 -0
  103. package/test/fixtures/qml/flea-theme/demo.qml +27 -0
  104. package/test/fixtures/qml/flea-theme/field.qml +21 -0
  105. package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
  106. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
  107. package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
  108. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
  109. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
  110. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
  111. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
  112. package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
  113. package/test/fixtures/qml/flea-theme/segment.qml +17 -0
  114. package/test/fixtures/qml/flea-theme/settings.qml +58 -0
  115. package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
  116. package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
  117. package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
  118. package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
  119. package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
  120. package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
  121. package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
  122. package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
  123. package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
  124. package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
  125. package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
  126. package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
  127. package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
  128. package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
  129. package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
  130. package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
  131. package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
  132. package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
  133. package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
  134. package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
  135. package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
  136. package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
  137. package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
  138. package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
  139. package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
  140. package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
  141. package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
  142. package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
  143. package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
  144. package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
  145. package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
  146. package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
  147. package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
  148. package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
  149. package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
  150. package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
  151. package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
  152. package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
  153. package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
  154. package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
  155. package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
  156. package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
  157. package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
  158. package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
  159. package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
  160. package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
  161. package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
  162. package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
  163. package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
  164. package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
  165. package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
  166. package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
  167. package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
  168. package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
  169. package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
  170. package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
  171. package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
  172. package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
  173. package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
  174. package/test/fixtures/qml/hello.qml +26 -0
  175. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
  176. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
  177. package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
  178. package/test/fixtures/qml/imports/components/Badge.qml +15 -0
  179. package/test/fixtures/qml/imports/logic/base.js +2 -0
  180. package/test/fixtures/qml/imports/logic/counter.js +4 -0
  181. package/test/fixtures/qml/imports/logic/math.js +3 -0
  182. package/test/fixtures/qml/imports/logic/shared.js +3 -0
  183. package/test/fixtures/qml/imports/main.qml +21 -0
  184. package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
  185. package/test/fixtures/qml/imports/theme/qmldir +2 -0
  186. package/test/fixtures/qml/multiply.qml +61 -0
  187. package/test/fixtures/qml-canvas-dom.js +90 -0
  188. package/test/fixtures/qml-media-audio.js +39 -0
  189. package/test/fixtures/raw-capture-benchmark.json +76 -0
  190. package/test/helpers/flea-sandbox.js +48 -0
  191. package/test/helpers/quickshell-fixture.js +125 -0
  192. package/test/html-clipboard-wayland.test.js +47 -0
  193. package/test/index.test.js +13 -0
  194. package/test/manual-casty-without-qml.js +87 -0
  195. package/test/manual-flea-application.js +49 -0
  196. package/test/manual-flea-binary.js +62 -0
  197. package/test/manual-flea-qml.js +9 -0
  198. package/test/manual-flea-settings.js +40 -0
  199. package/test/manual-flea-theme.js +40 -0
  200. package/test/manual-qml-removal.js +46 -0
  201. package/test/manual-without-qml.js +45 -0
  202. package/test/qml-canvas-dom.test.js +37 -0
  203. package/test/qml-clipboard-bridge.test.js +25 -0
  204. package/test/qml-clipboard-wayland.test.js +40 -0
  205. package/test/qml-connections-timing.test.js +73 -0
  206. package/test/qml-dynamic.test.js +401 -0
  207. package/test/qml-flea-app.test.js +146 -0
  208. package/test/qml-flea-backend.test.js +78 -0
  209. package/test/qml-flea-columns-backend.test.js +38 -0
  210. package/test/qml-flea-compile.test.js +78 -0
  211. package/test/qml-flea-launcher.test.js +88 -0
  212. package/test/qml-flea-markdown-backend.test.js +31 -0
  213. package/test/qml-flea-media.test.js +28 -0
  214. package/test/qml-flea-navigation-backend.test.js +35 -0
  215. package/test/qml-flea-navigation-timing.test.js +147 -0
  216. package/test/qml-flea-preview-backend.test.js +53 -0
  217. package/test/qml-flea-previews.test.js +163 -0
  218. package/test/qml-flea-rows.test.js +101 -0
  219. package/test/qml-flea-textures.test.js +59 -0
  220. package/test/qml-flea-view-switch.test.js +33 -0
  221. package/test/qml-flea-wayland.test.js +68 -0
  222. package/test/qml-flea.test.js +34 -0
  223. package/test/qml-host.test.js +238 -0
  224. package/test/qml-imports.test.js +102 -0
  225. package/test/qml-inherited-handlers.test.js +51 -0
  226. package/test/qml-input-selection.test.js +66 -0
  227. package/test/qml-interactions.test.js +309 -0
  228. package/test/qml-language.test.js +167 -0
  229. package/test/qml-launcher.test.js +54 -0
  230. package/test/qml-layout-text.test.js +205 -0
  231. package/test/qml-media.test.js +63 -0
  232. package/test/qml-persisted-delegates.test.js +45 -0
  233. package/test/qml-quickshell-controls.test.js +65 -0
  234. package/test/qml-quickshell-settings.test.js +62 -0
  235. package/test/qml-repeater.test.js +72 -0
  236. package/test/qml-style-updates.test.js +124 -0
  237. package/test/qml-textures.test.js +66 -0
  238. package/test/qml-views.test.js +332 -0
  239. package/test/qml-worker.test.js +69 -0
  240. package/test/qml.test.js +83 -0
  241. package/test/raw-capture.test.js +152 -0
  242. package/test/renderer-host.test.js +11 -0
  243. package/test/text-input-pointer.test.js +31 -0
  244. package/test/text-input-scroll.test.js +29 -0
  245. package/test/text-input.test.js +47 -0
  246. package/test/wayland-frontend.test.js +83 -0
  247. package/test/wayland-urlbar.test.js +38 -0
  248. package/test/window-resize.test.js +36 -0
  249. package/tools/bench-raw-capture.js +32 -0
@@ -0,0 +1,460 @@
1
+ pragma Singleton
2
+
3
+ import Quickshell
4
+ import Quickshell.Hyprland
5
+ import Quickshell.Io
6
+ import QtQuick
7
+ import qs.Commons
8
+ import qs.Commons as Commons
9
+ import "js/Buttons.js" as Buttons
10
+ import "js/Columns.js" as Columns
11
+ import "js/Contrast.js" as Contrast
12
+ import "js/Density.js" as Density
13
+ import "js/Palette.js" as Palette
14
+ import "js/TextSize.js" as TextSize
15
+
16
+ // Flea is its own process, so it plays the role shell.qml plays for the bar: it feeds Color and Style.
17
+ Singleton {
18
+ id: root
19
+
20
+ readonly property string stateDir: Quickshell.env("HOME") + "/.local/state/omarchy/current"
21
+
22
+ // The parsed colors.toml, for the code inks CodeLines lifts.
23
+ property var palette: ({})
24
+ // True only once colors.toml parsed to a palette, so a test can tell one from a fallback.
25
+ property bool ready: false
26
+
27
+ // The size Flea draws at: Omarchy's own unless the Display section pinned an override stop.
28
+ readonly property int baseSize: TextSize.effective(ViewState.textSize, Style.font.baseSize)
29
+ readonly property bool overridden: !TextSize.following(ViewState.textSize)
30
+ // One while following, so every OEM token below is Omarchy's own until an override moves it,
31
+ // and then moves by the ratio between the pinned stop and Omarchy's size.
32
+ readonly property real sizeRatio: Style.font.baseSize > 0 ? root.baseSize / Style.font.baseSize : 1
33
+
34
+ // The compositor's own monitor scale, which the Display section shows and never steps; 0 until
35
+ // hyprctl has answered, and the panel says so rather than claiming a number it does not have.
36
+ property real monitorScale: 0
37
+
38
+ // A property and not a Motion.js var: a plain library var notifies nothing, so every Behavior
39
+ // reading it would keep whatever it was built with when the compositor's answer arrives.
40
+ property bool reducedMotion: Quickshell.env("FLEA_REDUCED_MOTION") === "1"
41
+
42
+ // The only literal colours in the UI. Color models five roles; surface, symlink and executable
43
+ // have no counterpart, and the other two are read here before Color.loadColors has run.
44
+ readonly property var fallbackColor: ({
45
+ background: "#101315",
46
+ surface: "#181825",
47
+ symlink: "#94e2d5",
48
+ executable: "#a6e3a1"
49
+ })
50
+
51
+ // A palette role at or under this HSV saturation carries no colour (urgent falls back to the foreground, a grey accent cannot mark focus).
52
+ readonly property real hueFloor: 0.2
53
+ // The least contrast text needs on its ground (WCAG AA), and the least a frame or glyph needs (a graphical object).
54
+ readonly property real textContrast: 4.5
55
+ readonly property real frameContrast: 3
56
+ // Tag swatch colours lifted on the listing and the menu ground plus the digit fallback, filled once per theme load by Contrast.tagTheme.
57
+ property var tag: ({ colors: {}, menuColors: {}, fallback: false })
58
+ // The board's fixed tag slot width, once a tagged row has been seen in the folder.
59
+ readonly property int tagSlotWidth: 88
60
+
61
+ readonly property QtObject color: ThemeColors { textContrast: root.textContrast; hueFloor: root.hueFloor }
62
+
63
+ readonly property QtObject font: QtObject {
64
+ readonly property string family: Style.font.family
65
+ // Following takes Omarchy's resolved token, so a theme's own font override still wins. An
66
+ // override runs Style's own fontPx ratios at the pinned stop, which is the same ladder.
67
+ // Running text draws at Omarchy's regular body (decided 2026-09-07); bodySmall stays the geometry token every row and mark is sized from.
68
+ readonly property int body: root.overridden ? TextSize.body(root.baseSize) : Style.font.body
69
+ readonly property int bodySmall: root.overridden ? TextSize.bodySmall(root.baseSize) : Style.font.bodySmall
70
+ readonly property int caption: root.overridden ? TextSize.caption(root.baseSize) : Style.font.caption
71
+ }
72
+
73
+ // Tight drops the padding through Density040, Compact stays the default.
74
+ readonly property real densityRatio: Density.ratioFor(ViewState.density)
75
+
76
+ readonly property QtObject spacing: QtObject {
77
+ readonly property int hairline: Style.spacing.hairline
78
+ readonly property int rowPaddingX: Math.round(Style.spacing.rowPaddingX * root.sizeRatio)
79
+ readonly property int rowPaddingY: Math.round(Style.spacing.controlPaddingY * root.sizeRatio)
80
+ readonly property int gap: Math.round(Style.spacing.rowGap * root.sizeRatio)
81
+ // Properties040's Tags pair: 6 px from a swatch to its name and 16 px between tags at text size 14.
82
+ readonly property int tagNameGap: Math.round(Style.space(5) * root.sizeRatio)
83
+ readonly property int tagPairGap: Math.round(Style.space(14) * root.sizeRatio)
84
+ }
85
+
86
+ // One glyph's advance in a monospace face is every glyph's advance, so this sizes every fixed column; row names budget off bodyAdvance, grid captions off bodySmallAdvance.
87
+ readonly property real glyphAdvance: glyphMetrics.advanceWidth
88
+ // The body-face advance the row names draw at; Row and ColumnRow budget off this, never the caption one.
89
+ readonly property real bodyAdvance: bodyGlyphMetrics.advanceWidth
90
+ TextMetrics {
91
+ id: glyphMetrics
92
+ font.family: Style.font.family
93
+ font.pixelSize: root.font.bodySmall
94
+ text: "0"
95
+ }
96
+
97
+ // Trash draws its Deleted cell at body, and body/bodySmall is not one ratio across the size stops.
98
+ TextMetrics { id: bodyGlyphMetrics; font.family: Style.font.family; font.pixelSize: root.font.body; text: "0" }
99
+ readonly property real bodySmallAdvance: root.glyphAdvance // Same inputs as glyphMetrics, so one object serves both; grid tile budgets off this, never the body one.
100
+
101
+ // The header and every row read the stored widths so the two cannot drift apart; a drag previews in the header alone, ListColumns040 callout 1.
102
+ // Sample input: {"mode": "wide"} answers the measured width, {"size": -40} the same.
103
+ function storedWidth(key, fallback) {
104
+ var raw = ViewState.state.columnWidths ? ViewState.state.columnWidths[key] : undefined
105
+ var n = Columns.storedNumber(raw)
106
+ if (!(n >= 0))
107
+ return fallback
108
+ return Columns.clampListWidth(n)
109
+ }
110
+ readonly property QtObject column: QtObject {
111
+ // mode is a permanent column by design; Row and Header both read this width.
112
+ readonly property int mode: root.storedWidth("mode", Math.round(root.modeChars * glyphMetrics.advanceWidth))
113
+ readonly property int size: root.storedWidth("size", Math.round(root.sizeChars * glyphMetrics.advanceWidth))
114
+ readonly property int date: root.storedWidth("date", Math.round(root.dateChars * glyphMetrics.advanceWidth))
115
+ // Trash's Deleted column: the same sixteen characters in the size that draws them, ceil because a rounded width elides at base 14.
116
+ readonly property int trashDate: Math.ceil(root.dateChars * bodyGlyphMetrics.advanceWidth)
117
+ // The send picker's own, anchored the way kind below it is rather than counted in characters.
118
+ readonly property int pickerDate: Math.round(root.pickerDateBaseWidth * root.font.bodySmall / root.pickerDateBaseBodySmall)
119
+ // Kind text varies too much for a character count, so its base is a pixel width scaled by the same ratio bodySmall already is.
120
+ readonly property int kind: root.storedWidth("kind", Math.round(root.kindBaseWidth * root.font.bodySmall / 12))
121
+ readonly property int location: 150 // Sidebar040's fixed Recent column, shared by Header and Row.
122
+ // Not a column: the floor under the name, which the four above drop one by one to protect.
123
+ readonly property int nameMin: Math.round(root.nameMinChars * glyphMetrics.advanceWidth)
124
+ }
125
+
126
+ // Row height follows the font so it scales with omarchy display text size.
127
+ readonly property int rowHeight: Math.round(font.bodySmall * lineBoxRatio) + 2 * spacing.rowPaddingY
128
+ readonly property int fileRowHeight: Density.rowHeight(Math.round(font.bodySmall * lineBoxRatio), spacing.rowPaddingY, ViewState.density)
129
+ // The icon slot is the row's text line box, so an icon can never change the row height.
130
+ readonly property int iconSize: root.rowHeight - 2 * root.spacing.rowPaddingY
131
+ // A mark is sized from the type scale, never from its slot: 19, the canvas's own M.mark, is the row and menu one.
132
+ readonly property real markSize: root.font.bodySmall * 1.45
133
+ // The clipboard mark's own size, read by every view and the list's shared budgets.
134
+ readonly property int clipMarkSize: 12
135
+ // A mark standing alone takes its own step of the same scale: States.dc.html draws Locked and Error at 40.
136
+ readonly property int stateMarkSize: Math.round(root.font.bodySmall * 3.1)
137
+ // The brand moment, the empty hero and the loading crawl: 48, which is 6/5 of stateMarkSize on the same board.
138
+ readonly property int heroMarkSize: Math.round(root.font.bodySmall * 3.7)
139
+ // A chrome strip's mark is the OEM's own icon token, the one Ui/Button.qml and the Tailscale and
140
+ // Dropbox bar icons size from: 16 at base-size 14, which is the canvas's chrome mark on every board.
141
+ readonly property int chromeMarkSize: Math.round(Style.font.icon * root.sizeRatio)
142
+ // The boards use one two-unit stroke on the shared 24-unit glyph grid.
143
+ readonly property real strokeWidth: 2
144
+ // WCAG 2.5.8 floor. Marks stay at their type-scale size; the hit box grows to this.
145
+ readonly property int hitMin: 24
146
+ // The wheel, see ui/FastScrollHandler.qml: a notch is the platform's lines times notchPx times the
147
+ // multiplier (PR 16's pair, 4x on Omarchy Spotify); touchpad pixels move times TOUCH_GAIN (decided 2026-10-01).
148
+ readonly property QtObject scroll: QtObject {
149
+ readonly property int notchPx: 24
150
+ readonly property real multiplier: 4
151
+ }
152
+ // Wide enough for "Send with Taildrop" at bodySmall, 257 at base-size 14; ui/ContextMenu.qml draws it.
153
+ readonly property int menuWidth: Math.round(Style.space(220) * root.sizeRatio)
154
+
155
+ // Leading a row gives its text, above and below, before the padding is added.
156
+ readonly property real lineBoxRatio: 1.8
157
+
158
+ // Operator's density pass: a sidebar rail reads denser than the list it sits beside in every
159
+ // reference (qui's rail rows sit around 0.85 of its list row, Finder's around 0.75), so the
160
+ // rail gets its own row height and icon slot instead of borrowing the list's directly.
161
+ readonly property real railRowRatio: 0.78
162
+ readonly property int railRowHeight: Math.round(root.rowHeight * root.railRowRatio)
163
+ readonly property int railIconSize: root.railRowHeight - 2 * root.spacing.rowPaddingY
164
+
165
+ // The header and status bar are thin chrome strips, not data rows (Finder's own header sits
166
+ // well under its row height); this keeps both denser than a list row without a pixel constant.
167
+ readonly property real chromeRowRatio: 0.72
168
+ readonly property int chromeHeight: Math.round(root.rowHeight * root.chromeRowRatio)
169
+ // A chrome control's frame is inset from its strip on every side, and the strip carries its own
170
+ // rule along the bottom edge, so the inset has to clear that rule too: two lines that touch read
171
+ // as one line whatever colour they are. A quarter of the caption keeps the margin visible at
172
+ // every text size instead of pinning it to a pixel, and never below twice the rule's own width.
173
+ readonly property int chromeControlInset: Math.max(2 * root.spacing.hairline, Math.round(root.font.caption / 4))
174
+ // What is left of the strip once its rule and both insets are taken off. The hit box stays the
175
+ // whole strip, so a press still clears hitMin however small the frame gets.
176
+ readonly property int chromeControlHeight: root.chromeHeight - root.spacing.hairline - 2 * root.chromeControlInset
177
+ // The room a dialog body's clip keeps past its controls, RING plus a hairline: a focused filled check box draws its keyline two hairlines outside its box, and CardScroll.reveal() keeps this much past a control it scrolls to.
178
+ readonly property int focusClearance: Buttons.RING + root.spacing.hairline
179
+ // The two steps of that fill, in whichever role the control carries: the pointer's and the
180
+ // keyboard's. The second is the weight the rail's own active row and the segmented chooser's
181
+ // active segment already take, so a control under the keyboard reads at the same strength.
182
+ readonly property real washHover: Buttons.WASH_HOVER
183
+ readonly property real washActive: Buttons.WASH_PRESS
184
+ readonly property real disabledOpacity: Buttons.DISABLED_OPACITY
185
+ // A search result's base name takes at most this share of the row, so its folder always shows; Row and ColumnRow read it.
186
+ readonly property real searchNameShare: 0.66
187
+ // An accent edge is 2px along a tall side and 3px flush along a short one, measured in Quickshell.
188
+ readonly property int accentEdge: 3
189
+ // "rwxrwxrwx", Format.permissions is always exactly this wide.
190
+ readonly property int modeChars: 9
191
+ // "1000.0 kB": the SI ladder's tier-boundary rounding is one char wider than "999.9 kB".
192
+ readonly property int sizeChars: 9
193
+ // "2026-09-12 15:29", the one form Format.date prints.
194
+ readonly property int dateChars: 16
195
+ // SendPicker.html draws the chooser's date in an 80px slot, on a board whose base size is 14 and whose bodySmall is therefore 13.
196
+ readonly property int pickerDateBaseWidth: 80
197
+ readonly property int pickerDateBaseBodySmall: 13
198
+ // 120px at the OEM's base font size of 12, the Kind column's own anchor, see column.kind above.
199
+ readonly property int kindBaseWidth: 120
200
+ // The name's floor, in the character unit the fixed columns are already written in. Twenty
201
+ // characters holds 84.5% of a 25,473-name sample of /usr/bin, /usr/include,
202
+ // /usr/share/applications and this repo whole, and every further two buys under five points.
203
+ readonly property int nameMinChars: 20
204
+
205
+ // DualPane specifies these slots at bodySmall 13; mark, gap and padding remain shared tokens.
206
+ readonly property QtObject dualColumn: QtObject {
207
+ readonly property real size: 70 * root.font.bodySmall / 13
208
+ readonly property real date: 125 * root.font.bodySmall / 13
209
+ readonly property real nameMin: 180 * root.font.bodySmall / 13
210
+ }
211
+
212
+ // The narrowest Dual listing: the name floor Columns.dualSet protects, with the row padding, mark and gap before it.
213
+ readonly property real dualMinWidth: 2 * root.spacing.rowPaddingX + root.markSize + root.spacing.gap + root.dualColumn.nameMin
214
+
215
+ function dualColumns(width, hidden) {
216
+ return Columns.dualSet(width, {rowPaddingX: root.spacing.rowPaddingX, gap: root.spacing.gap,
217
+ iconSize: root.markSize, nameMin: root.dualColumn.nameMin,
218
+ size: root.dualColumn.size, date: root.dualColumn.date}, hidden)
219
+ }
220
+
221
+ // The grid view's own two numbers. The canvas calls it a "48 px slot"; twice the list's own mark
222
+ // slot is 46 at base-size 14, and the token wins over the mock, see the icon-language spec.
223
+ readonly property QtObject grid: QtObject {
224
+ readonly property int iconSize: root.iconSize * 2
225
+ // GridView.html reserves two caption lines, each 15px at the 11px caption token.
226
+ readonly property real captionLineHeight: root.font.caption * 15 / 11
227
+ readonly property real captionHeight: 2 * captionLineHeight
228
+ // GridView.dc.html's own five-column reference viewport: 880 body less 2x40 board padding,
229
+ // 2x1 window hairline, 2x18 grid padding and 4x8 gap, over five tiles, is 146 at base-size 14.
230
+ readonly property int minCellWidth: root.space(125)
231
+ }
232
+
233
+ // The September 8 sizing override gives dialog cards more room without enlarging context menus.
234
+ readonly property real dialogWidthRatio: 9 / 8
235
+ // A card's whole size inside its room and a whole centred origin, so no hairline of its frame lands on a half pixel.
236
+ function cardSpan(want, room) { return Math.max(0, Math.min(Math.ceil(want), Math.floor(room))) }
237
+ function cardOrigin(room, span) { return Math.round((room - span) / 2) }
238
+
239
+ // The Settings board's anatomy, resolved at base-size 14: a border-box panel 560 wide whose two
240
+ // outer hairlines leave 558 inside, split into a 150 rail and a 408 pane. 480 and 350 are those
241
+ // two at the OEM's own 12 anchor, so the pair scales once and the rail is what is left over.
242
+ readonly property QtObject settings: QtObject {
243
+ readonly property int panelWidth: Math.round(root.space(480) * root.dialogWidthRatio)
244
+ readonly property int paneWidth: Math.round(root.space(350) * root.dialogWidthRatio)
245
+ readonly property int railWidth: root.settings.panelWidth - root.settings.paneWidth
246
+ - 2 * root.spacing.hairline
247
+ // Settings.dc.html insets the rail column by 10 above its first row and below its last, in resolved pixels at base-size 14, whose bodySmall is 13, so space() would scale it twice.
248
+ readonly property int railPaddingY: Math.round(10 * root.font.bodySmall / 13)
249
+ }
250
+
251
+ readonly property QtObject preview: QtObject {
252
+ // Quick-Look sized inset over the window, not a second window.
253
+ readonly property real fraction: 0.82
254
+ // TallPreviews specimen C leaves 24 px of the window above and below a tall Quick Look, in resolved pixels at base 14.
255
+ readonly property int edge: 24
256
+ }
257
+
258
+ // The columns a list of this width can draw, less the ones ViewState hides; Header and Row call
259
+ // it with their own anchored-equal width, so they cannot disagree, and dateWidth is the picker's.
260
+ readonly property var columnTokens: ({
261
+ rowPaddingX: root.spacing.rowPaddingX, gap: root.spacing.gap, iconSize: root.iconSize,
262
+ nameMin: root.column.nameMin, mode: root.column.mode,
263
+ size: root.column.size, date: root.column.date, kind: root.column.kind, location: root.column.location
264
+ })
265
+ function columnSet(dateWidth, dual) {
266
+ if (dual)
267
+ return Object.assign({}, root.columnTokens, {iconSize: root.markSize,
268
+ nameMin: root.dualColumn.nameMin, size: root.dualColumn.size,
269
+ date: dateWidth === undefined ? root.dualColumn.date : dateWidth});
270
+ return dateWidth === undefined ? root.columnTokens : Object.assign({}, root.columnTokens, {date: dateWidth});
271
+ }
272
+ function columns(width, hidden, dateWidth, recent, dual) { return recent ? Columns.recentSet(width, root.columnSet(dateWidth, dual), hidden, dual) : Columns.set(width, root.columnSet(dateWidth), hidden); }
273
+
274
+ // Five callers plus the grid and settings tokens above: ConvertDialog, KeymapSheet, NetworkDialog, NetworkForm, TransferCard; every other spacing token is direct.
275
+ function space(px) {
276
+ return Math.round(Style.space(px) * root.sizeRatio);
277
+ }
278
+
279
+ // The metrics contract as the app resolves it, one key=value per line in the Blueprint board's
280
+ // order; ui/shell.qml serves it as tokens() and tools/flea-metrics-gate diffs it. family is the
281
+ // resolved face, never the "monospace" alias, so the gate cannot pass on a box without the font.
282
+ function tokens() {
283
+ var t = {
284
+ family: Style.font.resolvedFamily,
285
+ baseSize: root.baseSize,
286
+ body: root.font.body,
287
+ bodySmall: root.font.bodySmall,
288
+ caption: root.font.caption,
289
+ lineBoxRatio: root.lineBoxRatio,
290
+ rowPaddingX: root.spacing.rowPaddingX,
291
+ rowPaddingY: root.spacing.rowPaddingY,
292
+ gap: root.spacing.gap,
293
+ hairline: root.spacing.hairline,
294
+ rowHeight: root.rowHeight,
295
+ iconSize: root.iconSize,
296
+ markSize: root.markSize,
297
+ stateMarkSize: root.stateMarkSize,
298
+ heroMarkSize: root.heroMarkSize,
299
+ strokeWidth: root.strokeWidth,
300
+ railRowHeight: root.railRowHeight,
301
+ railIconSize: root.railIconSize,
302
+ chromeHeight: root.chromeHeight,
303
+ chromeMarkSize: root.chromeMarkSize,
304
+ columnMode: root.column.mode,
305
+ columnSize: root.column.size,
306
+ columnDate: root.column.date,
307
+ columnPickerDate: root.column.pickerDate,
308
+ columnKind: root.column.kind,
309
+ menuWidth: root.menuWidth,
310
+ cornerRadius: Style.cornerRadius,
311
+ previewFraction: root.preview.fraction,
312
+ gridIconSize: root.grid.iconSize,
313
+ gridMinCellWidth: root.grid.minCellWidth,
314
+ settingsPanelWidth: root.settings.panelWidth,
315
+ settingsRailWidth: root.settings.railWidth,
316
+ settingsPaneWidth: root.settings.paneWidth,
317
+ settingsRailPaddingY: root.settings.railPaddingY
318
+ };
319
+ var lines = [];
320
+ for (var key in t)
321
+ lines.push(key + "=" + t[key]);
322
+ return lines.join("\n");
323
+ }
324
+
325
+ // Color owns the other five; only the three it does not model are assigned here.
326
+ function applyColors(body) {
327
+ var found = Palette.parse(body);
328
+ var bg = Palette.pick(found, ["background"], root.fallbackColor.background);
329
+ var surface = Palette.pick(found, Palette.SURFACE_KEYS, root.fallbackColor.surface);
330
+ root.color.surface = surface;
331
+ Commons.Color.loadColors(body);
332
+ // Frames and glyphs draw the palette's muted lifted to frameContrast; text lifts that to textContrast on the background, then the surface.
333
+ root.color.opaqueMutedFrame = Contrast.ensureRatio(
334
+ Palette.pick(found, ["muted"], Qt.darker(Commons.Color.foreground, 1.4)), bg, root.frameContrast);
335
+ root.color.opaqueMuted = Contrast.ensureRatio(Contrast.ensureRatio(root.color.opaqueMutedFrame, bg, root.textContrast), surface, root.textContrast);
336
+ // Metadata ink is the foreground darkened one step, lifted the same two ways; the unlifted step stays for glyphs that read it.
337
+ root.color.opaqueMetadataFrame = Qt.darker(Commons.Color.foreground, 1.4);
338
+ root.color.opaqueMetadata = Contrast.ensureRatio(Contrast.ensureRatio(root.color.opaqueMetadataFrame, bg, root.textContrast), surface, root.textContrast);
339
+ // Headings take the palette's bright foreground (the ANSI ring's color15 without one) when it has more contrast than the foreground.
340
+ var bright = Palette.pick(found, ["bright_foreground", "color15"], String(Commons.Color.foreground));
341
+ root.color.opaqueForegroundBright = Contrast.ratio(bright, bg) > Contrast.ratio(String(Commons.Color.foreground), bg) ? bright : Commons.Color.foreground;
342
+ root.color.opaqueAccentFrame = Contrast.ensureRatio(Commons.Color.accent, surface, 3);
343
+ root.color.accentHasHue = Commons.Color.accent.hsvSaturation > root.hueFloor;
344
+ root.color.opaqueSymlink = Contrast.ensureRatio(
345
+ Palette.pick(found, ["cyan", "color6"], root.fallbackColor.symlink), bg, 4.5);
346
+ root.color.opaqueExecutable = Contrast.ensureRatio(
347
+ Palette.pick(found, ["green", "color2"], root.fallbackColor.executable), bg, 4.5);
348
+ root.palette = found;
349
+ // Urgent is the palette's own red: seven of the 23 installed themes leave it under 4.5:1 on their own ground, so it is lifted the way symlink and executable are, and the three whose red carries no chroma at all (solitude, white, vantablack) fall back to the foreground, because a destructive row drawn in the same grey as an unavailable one reads as switched off rather than as dangerous.
350
+ var hued = Commons.Color.urgent.hsvSaturation > root.hueFloor;
351
+ root.color.opaqueError = hued ? Contrast.ensureRatio(Commons.Color.urgent, bg, Math.max(4.5, Contrast.ratio(String(root.color.opaqueMuted), bg))) : String(Commons.Color.foreground);
352
+ // The status bar draws that same ink on the surface, where four themes land under 4.5; it is never quieter than the caption there.
353
+ root.color.opaqueErrorOnSurface = hued ? Contrast.ensureRatio(Commons.Color.urgent, surface, Math.max(4.5, Contrast.ratio(String(root.color.opaqueMuted), surface))) : String(Commons.Color.foreground);
354
+ root.tag = Contrast.tagTheme(found, bg, surface);
355
+ // A body that parsed to nothing left every role on its fallback, so the flag says so rather
356
+ // than reporting that the read happened: text() returns "" for a file that is not there.
357
+ root.ready = Palette.isPalette(found);
358
+ }
359
+
360
+ // Sample input: [{"id":0,"name":"DP-1","width":2560,"height":1440,"scale":1.00,"focused":true}]
361
+ function applyMonitorScale(body) {
362
+ try {
363
+ var monitors = JSON.parse(body);
364
+ for (var i = 0; i < monitors.length; i++) {
365
+ if (monitors[i].focused === true)
366
+ root.monitorScale = monitors[i].scale;
367
+ }
368
+ } catch (e) {
369
+ // hyprctl unreachable, or a shape this build does not know: the row keeps saying so.
370
+ }
371
+ }
372
+
373
+ // Sample input: {"option": "animations:enabled", "bool": false, "set": true }
374
+ function applyReducedMotion(body) {
375
+ root.reducedMotion = String(body).indexOf('"bool": false') >= 0;
376
+ }
377
+
378
+ // blockLoading only gates calls to text()/data(), so a window could paint one frame against the
379
+ // un-loaded fallback; Component.onCompleted calls text() itself, which blocks this Singleton's
380
+ // construction before any window, and the later onLoaded is a harmless idempotent second apply.
381
+ FileView {
382
+ id: colorsFile
383
+ path: root.stateDir + "/theme/colors.toml"
384
+ blockLoading: true
385
+ printErrors: false
386
+ onLoaded: root.applyColors(text())
387
+ onLoadFailed: root.ready = false
388
+ Component.onCompleted: root.applyColors(colorsFile.text())
389
+ }
390
+
391
+ // Color.loadShell refreshes Style's whole token scale, so the type ladder flips with the theme.
392
+ FileView {
393
+ id: shellFile
394
+ path: root.stateDir + "/theme/shell.toml"
395
+ blockLoading: true
396
+ printErrors: false
397
+ onLoaded: Commons.Color.loadShell(text())
398
+ onLoadFailed: Commons.Color.loadShell("")
399
+ }
400
+
401
+ // omarchy-theme-set rm -rf's and mv's the theme directory, so an inotify watch on a file inside
402
+ // it dies with the old inode and never fires again. theme.name is rewritten in place after the
403
+ // swap, which makes it the one event that survives, measured across three consecutive switches.
404
+ FileView {
405
+ id: themeNameFile
406
+ path: root.stateDir + "/theme.name"
407
+ blockLoading: true
408
+ watchChanges: true
409
+ printErrors: false
410
+ onFileChanged: {
411
+ reload();
412
+ colorsFile.reload();
413
+ shellFile.reload();
414
+ // The OEM shell's applyTheme runs this beside the two reloads above, and without it a theme that moves decoration:rounding leaves every corner here on the old value.
415
+ Style.scheduleRefresh();
416
+ }
417
+ }
418
+
419
+ // Hyprland reloads its config for a Window Corners, Gaps or Transparency change with no theme switch, so Style's rounding and Glass's mode are read again then.
420
+ signal configReloaded()
421
+ signal fleaWindowOpened()
422
+ // Sample input, openwindow's data: "55bacb0a1f80,1,com.thisisgm.flea,Flea", the class third.
423
+ function hyprEvent(name, data) {
424
+ if (name === "openwindow" && String(data).split(",")[2] === "com.thisisgm.flea") root.fleaWindowOpened();
425
+ if (name !== "configreloaded") return;
426
+ Style.scheduleRefresh();
427
+ root.configReloaded();
428
+ }
429
+ // The Hyprland singleton is only touched inside Hyprland, so a headless run has no socket to join.
430
+ Connections {
431
+ target: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE") !== null ? Hyprland : null
432
+ function onRawEvent(event) { root.hyprEvent(event.name, event.data) }
433
+ }
434
+
435
+ // Read once, not watched: the Display section reports the compositor's scale and Flea owns no
436
+ // control that could change it, so there is nothing here for a poll to keep in step with.
437
+ Process {
438
+ running: true
439
+ command: ["hyprctl", "monitors", "-j"]
440
+ stdout: StdioCollector {
441
+ waitForEnd: true
442
+ // text is a property on this type and not a function, which every other collector in
443
+ // this tree already reads that way; calling it throws and the row stays unanswered.
444
+ onStreamFinished: root.applyMonitorScale(text)
445
+ }
446
+ }
447
+
448
+ // Flea agrees with the compositor rather than carrying a switch, and FLEA_REDUCED_MOTION is the
449
+ // test override; Quickshell.env answers null for an unset variable, so the guard is a truthiness
450
+ // test, StdioCollector text is a property whose call throws, and the query is Style.qml's own.
451
+ Process {
452
+ id: motionQuery
453
+ running: !Quickshell.env("FLEA_REDUCED_MOTION")
454
+ command: ["hyprctl", "-j", "getoption", "animations:enabled"]
455
+ stdout: StdioCollector {
456
+ waitForEnd: true
457
+ onStreamFinished: root.applyReducedMotion(text)
458
+ }
459
+ }
460
+ }
@@ -0,0 +1,43 @@
1
+ import QtQuick
2
+ import qs.Commons as Commons
3
+ import "js/Contrast.js" as Contrast
4
+ import "js/Material.js" as Material
5
+
6
+ // Palette roles stay opaque; the default mode's text lift is computed once per palette, alpha or focus change.
7
+ QtObject {
8
+ id: root
9
+ required property real textContrast
10
+ required property real hueFloor
11
+ readonly property color background: Commons.Color.background
12
+ readonly property color foreground: root.material.foreground
13
+ property color opaqueForegroundBright: Commons.Color.foreground
14
+ property color foregroundBright: Contrast.ratio(String(root.opaqueForegroundBright), String(root.background)) > Contrast.ratio(String(root.foreground), String(root.background)) ? root.opaqueForegroundBright : root.foreground
15
+ property color opaqueMuted: Qt.darker(Commons.Color.foreground, 1.4)
16
+ property color muted: root.material.muted
17
+ property color opaqueMutedFrame: Qt.darker(Commons.Color.foreground, 1.4)
18
+ property color mutedFrame: root.modeInk(root.opaqueMutedFrame, 3)
19
+ property color opaqueMetadata: Qt.darker(Commons.Color.foreground, 1.4)
20
+ property color metadata: root.material.metadata
21
+ property color opaqueMetadataFrame: Qt.darker(Commons.Color.foreground, 1.4)
22
+ property color metadataFrame: root.modeInk(root.opaqueMetadataFrame, 3)
23
+ readonly property color accent: root.modeInk(Commons.Color.accent, 3)
24
+ property color opaqueError: Commons.Color.urgent
25
+ property color error: root.modeInk(root.opaqueError, root.textContrast, root.muted)
26
+ property color opaqueErrorOnSurface: Commons.Color.urgent
27
+ property color errorOnSurface: root.modeInk(root.opaqueErrorOnSurface, root.textContrast, root.muted)
28
+ property color surface: Theme.fallbackColor.surface
29
+ property color opaqueSymlink: Theme.fallbackColor.symlink
30
+ property color symlink: root.modeInk(root.opaqueSymlink, root.textContrast)
31
+ property color opaqueExecutable: Theme.fallbackColor.executable
32
+ property color executable: root.modeInk(root.opaqueExecutable, root.textContrast)
33
+ property color opaqueAccentFrame: Commons.Color.accent
34
+ property color accentFrame: root.modeInk(root.opaqueAccentFrame, 3)
35
+ property bool accentHasHue: Commons.Color.accent.hsvSaturation > root.hueFloor
36
+ readonly property var opaquePalette: ({background: root.background, surface: root.surface,
37
+ foreground: Commons.Color.foreground, muted: root.opaqueMuted, metadata: root.opaqueMetadata})
38
+ readonly property var modePalette: Object.assign({}, root.opaquePalette, {blurBrightness: Glass.mode === "glassy" ? 0.75 : 1})
39
+ readonly property var material: Glass.on && Glass.usesDefault ? Material.inks(root.modePalette, Math.round(Glass.alpha * 255), root.textContrast) : root.opaquePalette
40
+ function modeInk(ink, need, quiet) {
41
+ return Glass.on && Glass.usesDefault ? Material.liftInk(ink, root.modePalette, Math.round(Glass.alpha * 255), need, quiet) : ink
42
+ }
43
+ }