Fix transparent bar contrast timing
This commit is contained in:
Executable
+125
@@ -0,0 +1,125 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Choose a legible transparent bar text color
|
||||||
|
# omarchy:hidden=true
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
position=${1:-top}
|
||||||
|
bar_size=${2:-}
|
||||||
|
text_color=${3:-}
|
||||||
|
background_color=${4:-}
|
||||||
|
background_path=""
|
||||||
|
screen_size=""
|
||||||
|
|
||||||
|
shift 4 2>/dev/null || true
|
||||||
|
while (($# > 0)); do
|
||||||
|
case "$1" in
|
||||||
|
--background)
|
||||||
|
background_path=${2:-}
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--screen)
|
||||||
|
screen_size=${2:-}
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
valid_hex() {
|
||||||
|
[[ $1 =~ ^#[0-9A-Fa-f]{6}$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
fallback() {
|
||||||
|
printf '%s\n' "$text_color"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
contrast() {
|
||||||
|
local color="$1"
|
||||||
|
local sample="$2"
|
||||||
|
|
||||||
|
awk -v fg="$color" -v bg="$sample" '
|
||||||
|
function channel(hex, start) {
|
||||||
|
return strtonum("0x" substr(hex, start, 2)) / 255
|
||||||
|
}
|
||||||
|
function linear(c) {
|
||||||
|
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ^ 2.4
|
||||||
|
}
|
||||||
|
function luminance(hex, r, g, b) {
|
||||||
|
r = linear(channel(hex, 2))
|
||||||
|
g = linear(channel(hex, 4))
|
||||||
|
b = linear(channel(hex, 6))
|
||||||
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||||
|
}
|
||||||
|
BEGIN {
|
||||||
|
l1 = luminance(fg)
|
||||||
|
l2 = luminance(bg)
|
||||||
|
if (l1 < l2) {
|
||||||
|
t = l1
|
||||||
|
l1 = l2
|
||||||
|
l2 = t
|
||||||
|
}
|
||||||
|
printf "%.6f\n", (l1 + 0.05) / (l2 + 0.05)
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
valid_hex "$text_color" || fallback
|
||||||
|
valid_hex "$background_color" || fallback
|
||||||
|
[[ $position =~ ^(top|bottom|left|right)$ ]] || fallback
|
||||||
|
[[ $bar_size =~ ^[0-9]+$ ]] || fallback
|
||||||
|
omarchy-cmd-present magick || fallback
|
||||||
|
|
||||||
|
if [[ -z $background_path ]]; then
|
||||||
|
background_path=$(readlink -f "$HOME/.config/omarchy/current/background" 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
[[ -f $background_path ]] || fallback
|
||||||
|
|
||||||
|
if [[ -z $screen_size ]]; then
|
||||||
|
if omarchy-cmd-present hyprctl && omarchy-cmd-present jq; then
|
||||||
|
screen_size=$(hyprctl monitors -j 2>/dev/null | jq -r '.[0] | "\(.width)x\(.height)"' 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
[[ $screen_size =~ ^([0-9]+)x([0-9]+)$ ]] || fallback
|
||||||
|
|
||||||
|
screen_width=${BASH_REMATCH[1]}
|
||||||
|
screen_height=${BASH_REMATCH[2]}
|
||||||
|
((screen_width > 0 && screen_height > 0 && bar_size > 0)) || fallback
|
||||||
|
|
||||||
|
case "$position" in
|
||||||
|
top)
|
||||||
|
crop="${screen_width}x${bar_size}+0+0"
|
||||||
|
;;
|
||||||
|
bottom)
|
||||||
|
crop_y=$((screen_height - bar_size))
|
||||||
|
((crop_y >= 0)) || fallback
|
||||||
|
crop="${screen_width}x${bar_size}+0+${crop_y}"
|
||||||
|
;;
|
||||||
|
left)
|
||||||
|
crop="${bar_size}x${screen_height}+0+0"
|
||||||
|
;;
|
||||||
|
right)
|
||||||
|
crop_x=$((screen_width - bar_size))
|
||||||
|
((crop_x >= 0)) || fallback
|
||||||
|
crop="${bar_size}x${screen_height}+${crop_x}+0"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
pixel=$(magick "$background_path" -auto-orient \
|
||||||
|
-resize "${screen_width}x${screen_height}^" \
|
||||||
|
-gravity center -extent "${screen_width}x${screen_height}" \
|
||||||
|
-gravity NorthWest -crop "$crop" +repage \
|
||||||
|
-resize '1x1!' -format '%[fx:int(255*r)],%[fx:int(255*g)],%[fx:int(255*b)]' info:- 2>/dev/null || true)
|
||||||
|
[[ $pixel =~ ^([0-9]+),([0-9]+),([0-9]+)$ ]] || fallback
|
||||||
|
|
||||||
|
sample=$(printf '#%02x%02x%02x' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}")
|
||||||
|
text_contrast=$(contrast "$text_color" "$sample")
|
||||||
|
background_contrast=$(contrast "$background_color" "$sample")
|
||||||
|
|
||||||
|
awk -v text="$text_contrast" -v background="$background_contrast" 'BEGIN { exit !(background > text) }' \
|
||||||
|
&& printf '%s\n' "$background_color" \
|
||||||
|
|| printf '%s\n' "$text_color"
|
||||||
@@ -83,6 +83,7 @@ Item {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
|
enabled: !root.bar || root.bar.foregroundAnimationEnabled
|
||||||
ColorAnimation { duration: 160 }
|
ColorAnimation { duration: 160 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ Item {
|
|||||||
})
|
})
|
||||||
property var layoutConfig: fallbackBarConfig.layout
|
property var layoutConfig: fallbackBarConfig.layout
|
||||||
property string centerAnchor: ""
|
property string centerAnchor: ""
|
||||||
|
property bool requestedTransparent: false
|
||||||
|
property bool useTransparentForeground: false
|
||||||
property bool transparent: false
|
property bool transparent: false
|
||||||
property int barConfigSerial: 0
|
property int barConfigSerial: 0
|
||||||
property string position: "top"
|
property string position: "top"
|
||||||
@@ -46,11 +48,15 @@ Item {
|
|||||||
property string fontFamily: Style.font.family
|
property string fontFamily: Style.font.family
|
||||||
// Bound to the central Color singleton so the bar tracks shell.toml's
|
// Bound to the central Color singleton so the bar tracks shell.toml's
|
||||||
// [bar] section. Property names kept for the rest of this file's bindings.
|
// [bar] section. Property names kept for the rest of this file's bindings.
|
||||||
property color foreground: Color.bar.text
|
property color themeForeground: Color.bar.text
|
||||||
|
property color themeContrastForeground: Color.background
|
||||||
|
property color transparentForeground: Color.bar.text
|
||||||
|
property color foreground: useTransparentForeground ? transparentForeground : themeForeground
|
||||||
|
property bool foregroundAnimationEnabled: true
|
||||||
property color background: Color.bar.background
|
property color background: Color.bar.background
|
||||||
property color urgent: Color.bar.active
|
property color urgent: Color.bar.active
|
||||||
|
|
||||||
Behavior on foreground { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
Behavior on foreground { enabled: root.foregroundAnimationEnabled; ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
||||||
Behavior on background { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
Behavior on background { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
||||||
Behavior on urgent { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
Behavior on urgent { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
|
||||||
property var tooltipTarget: null
|
property var tooltipTarget: null
|
||||||
@@ -179,7 +185,7 @@ Item {
|
|||||||
var config = Util.isPlainObject(barConfig) ? barConfig : fallbackBarConfig
|
var config = Util.isPlainObject(barConfig) ? barConfig : fallbackBarConfig
|
||||||
|
|
||||||
position = normalizePosition(config.position)
|
position = normalizePosition(config.position)
|
||||||
transparent = config.transparent === true
|
setRequestedTransparency(config.transparent === true)
|
||||||
centerAnchor = Util.canonicalWidgetId(config.centerAnchor || "")
|
centerAnchor = Util.canonicalWidgetId(config.centerAnchor || "")
|
||||||
layoutConfig = normalizeLayout(config.layout)
|
layoutConfig = normalizeLayout(config.layout)
|
||||||
barConfigSerial++
|
barConfigSerial++
|
||||||
@@ -307,14 +313,14 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleTransparency() {
|
function toggleTransparency() {
|
||||||
var nextTransparent = !(root.transparent === true)
|
var nextTransparent = !(root.requestedTransparent === true)
|
||||||
if (root.shell && typeof root.shell.mutateShellConfig === "function") {
|
if (root.shell && typeof root.shell.mutateShellConfig === "function") {
|
||||||
root.shell.mutateShellConfig(function(config) {
|
root.shell.mutateShellConfig(function(config) {
|
||||||
if (!Util.isPlainObject(config.bar)) config.bar = {}
|
if (!Util.isPlainObject(config.bar)) config.bar = {}
|
||||||
config.bar.transparent = nextTransparent
|
config.bar.transparent = nextTransparent
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
root.transparent = nextTransparent
|
root.setRequestedTransparency(nextTransparent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,6 +466,94 @@ Item {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function colorHex(colorValue) {
|
||||||
|
var c = colorValue
|
||||||
|
if (typeof c === "string") c = Qt.color(c)
|
||||||
|
function hexChannel(value) {
|
||||||
|
var s = Math.round(Util.clamp(value, 0, 1) * 255).toString(16)
|
||||||
|
return s.length < 2 ? "0" + s : s
|
||||||
|
}
|
||||||
|
return "#" + hexChannel(c.r) + hexChannel(c.g) + hexChannel(c.b)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRequestedTransparency(value) {
|
||||||
|
var nextTransparent = value === true
|
||||||
|
requestedTransparent = nextTransparent
|
||||||
|
if (!nextTransparent) {
|
||||||
|
foregroundAnimationEnabled = false
|
||||||
|
useTransparentForeground = false
|
||||||
|
transparent = false
|
||||||
|
transparentForeground = themeForeground
|
||||||
|
restoreForegroundAnimation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scheduleTransparentForegroundRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreForegroundAnimation() {
|
||||||
|
Qt.callLater(function() {
|
||||||
|
Qt.callLater(function() { root.foregroundAnimationEnabled = true })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleTransparentForegroundRefresh() {
|
||||||
|
if (!requestedTransparent) {
|
||||||
|
transparentForeground = themeForeground
|
||||||
|
return
|
||||||
|
}
|
||||||
|
transparentForegroundTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshTransparentForeground() {
|
||||||
|
if (!requestedTransparent || transparentForegroundProc.running) return
|
||||||
|
|
||||||
|
transparentForegroundProc.command = [
|
||||||
|
"omarchy-shell-bar-text-color",
|
||||||
|
root.position,
|
||||||
|
String(root.barSize),
|
||||||
|
colorHex(root.themeForeground),
|
||||||
|
colorHex(root.themeContrastForeground)
|
||||||
|
]
|
||||||
|
transparentForegroundProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
onRequestedTransparentChanged: scheduleTransparentForegroundRefresh()
|
||||||
|
onPositionChanged: scheduleTransparentForegroundRefresh()
|
||||||
|
onThemeForegroundChanged: scheduleTransparentForegroundRefresh()
|
||||||
|
onThemeContrastForegroundChanged: scheduleTransparentForegroundRefresh()
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: transparentForegroundTimer
|
||||||
|
interval: 120
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root.refreshTransparentForeground()
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: transparentForegroundProc
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: function(line) {
|
||||||
|
var value = String(line || "").trim()
|
||||||
|
if (!/^#[0-9A-Fa-f]{6}$/.test(value)) return
|
||||||
|
|
||||||
|
root.foregroundAnimationEnabled = false
|
||||||
|
root.transparentForeground = value
|
||||||
|
if (root.requestedTransparent) {
|
||||||
|
root.useTransparentForeground = true
|
||||||
|
root.transparent = true
|
||||||
|
}
|
||||||
|
root.restoreForegroundAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: root.home + "/.config/omarchy/current"
|
||||||
|
watchChanges: true
|
||||||
|
printErrors: false
|
||||||
|
onFileChanged: root.scheduleTransparentForegroundRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
function runProcess(process) {
|
function runProcess(process) {
|
||||||
if (!process.running)
|
if (!process.running)
|
||||||
process.running = true
|
process.running = true
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ BarWidget {
|
|||||||
bar: root.bar
|
bar: root.bar
|
||||||
text: panelLoader.item ? panelLoader.item.label : ""
|
text: panelLoader.item ? panelLoader.item.label : ""
|
||||||
active: panelLoader.item && panelLoader.item.klass === "active"
|
active: panelLoader.item && panelLoader.item.klass === "active"
|
||||||
horizontalMargin: 1
|
horizontalMargin: 5
|
||||||
// Tooltip suppressed because the panel is the detail view.
|
// Tooltip suppressed because the panel is the detail view.
|
||||||
tooltipText: ""
|
tooltipText: ""
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ BarWidget {
|
|||||||
color: activePlayer && activePlayer.isPlaying ? root.bar.foreground : Qt.darker(root.bar.foreground, 1.5)
|
color: activePlayer && activePlayer.isPlaying ? root.bar.foreground : Qt.darker(root.bar.foreground, 1.5)
|
||||||
font.family: root.bar.fontFamily
|
font.family: root.bar.fontFamily
|
||||||
font.pixelSize: Style.font.body
|
font.pixelSize: Style.font.body
|
||||||
Behavior on color { ColorAnimation { duration: 160 } }
|
Behavior on color {
|
||||||
|
enabled: !root.bar || root.bar.foregroundAnimationEnabled
|
||||||
|
ColorAnimation { duration: 160 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
|
require_command magick
|
||||||
|
|
||||||
|
TMPDIR=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMPDIR"' EXIT
|
||||||
|
|
||||||
|
export PATH="$ROOT/bin:$PATH"
|
||||||
|
|
||||||
|
light_top="$TMPDIR/light-top.png"
|
||||||
|
dark_top="$TMPDIR/dark-top.png"
|
||||||
|
|
||||||
|
magick -size 100x100 xc:'#202020' -fill '#f5f5f5' -draw 'rectangle 0,0 99,19' "$light_top"
|
||||||
|
magick -size 100x100 xc:'#f5f5f5' -fill '#202020' -draw 'rectangle 0,0 99,19' "$dark_top"
|
||||||
|
|
||||||
|
result=$(HOME="$TMPDIR" omarchy-shell-bar-text-color top 20 '#ffffff' '#101010' --background "$light_top" --screen 100x100)
|
||||||
|
[[ $result == "#101010" ]] || fail "transparent bar text switches to background color on light wallpaper" "expected #101010, got $result"
|
||||||
|
pass "transparent bar text switches to background color on light wallpaper"
|
||||||
|
|
||||||
|
result=$(HOME="$TMPDIR" omarchy-shell-bar-text-color top 20 '#ffffff' '#101010' --background "$dark_top" --screen 100x100)
|
||||||
|
[[ $result == "#ffffff" ]] || fail "transparent bar text keeps text color on dark wallpaper" "expected #ffffff, got $result"
|
||||||
|
pass "transparent bar text keeps text color on dark wallpaper"
|
||||||
|
|
||||||
|
result=$(HOME="$TMPDIR" omarchy-shell-bar-text-color top 20 '#ffffff' '#101010' --background "$TMPDIR/missing.png" --screen 100x100)
|
||||||
|
[[ $result == "#ffffff" ]] || fail "transparent bar text falls back to text color when sampling fails" "expected #ffffff, got $result"
|
||||||
|
pass "transparent bar text falls back to text color when sampling fails"
|
||||||
Reference in New Issue
Block a user