Refine shell panel and lock theming
This commit is contained in:
@@ -290,6 +290,83 @@ add_gradient_function_values() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strip_shell_section_header() {
|
||||||
|
local section="$1"
|
||||||
|
local file="$2"
|
||||||
|
|
||||||
|
awk -v section="$section" '
|
||||||
|
BEGIN { skipping_header = 0 }
|
||||||
|
/^[[:space:]]*\[[^]]+\][[:space:]]*($|#)/ {
|
||||||
|
if ($0 ~ "^[[:space:]]*\\[" section "\\][[:space:]]*($|#)") {
|
||||||
|
skipping_header = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_shell_section_override() {
|
||||||
|
local override="$1"
|
||||||
|
local section tmp body
|
||||||
|
|
||||||
|
[[ -f $NEXT_THEME_DIR/shell.toml ]] || return
|
||||||
|
[[ -f $override ]] || return
|
||||||
|
|
||||||
|
section=$(basename "$override")
|
||||||
|
section="${section#shell.}"
|
||||||
|
section="${section%.toml}"
|
||||||
|
[[ $section =~ ^[A-Za-z0-9_-]+$ ]] || return
|
||||||
|
|
||||||
|
tmp=$(mktemp)
|
||||||
|
body=$(mktemp)
|
||||||
|
|
||||||
|
strip_shell_section_header "$section" "$override" >"$body"
|
||||||
|
|
||||||
|
awk -v section="$section" -v body="$body" '
|
||||||
|
function emit_override() {
|
||||||
|
if (emitted) return
|
||||||
|
print "[" section "]"
|
||||||
|
while ((getline line < body) > 0) print line
|
||||||
|
close(body)
|
||||||
|
emitted = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/^[[:space:]]*\[[^]]+\][[:space:]]*($|#)/ {
|
||||||
|
if (in_section) {
|
||||||
|
emit_override()
|
||||||
|
in_section = 0
|
||||||
|
}
|
||||||
|
if ($0 ~ "^[[:space:]]*\\[" section "\\][[:space:]]*($|#)") {
|
||||||
|
in_section = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
!in_section { print }
|
||||||
|
|
||||||
|
END {
|
||||||
|
if (in_section || !emitted) {
|
||||||
|
if (NR > 0) print ""
|
||||||
|
emit_override()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$NEXT_THEME_DIR/shell.toml" >"$tmp"
|
||||||
|
|
||||||
|
mv "$tmp" "$NEXT_THEME_DIR/shell.toml"
|
||||||
|
rm "$body"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_shell_section_overrides() {
|
||||||
|
local override
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
for override in "$NEXT_THEME_DIR"/shell.*.toml; do
|
||||||
|
[[ $(basename "$override") != "shell.toml" ]] || continue
|
||||||
|
apply_shell_section_override "$override"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
alias_theme_color() {
|
alias_theme_color() {
|
||||||
local key="$1"
|
local key="$1"
|
||||||
local fallback="$2"
|
local fallback="$2"
|
||||||
@@ -426,3 +503,5 @@ if [[ -f $COLORS_FILE ]]; then
|
|||||||
|
|
||||||
rm "$sed_script"
|
rm "$sed_script"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
apply_shell_section_overrides
|
||||||
|
|||||||
@@ -101,6 +101,19 @@ The helper does not choose the first color unless you use `gradient_start`.
|
|||||||
and bar sizing. The default generated file comes from
|
and bar sizing. The default generated file comes from
|
||||||
`default/themed/shell.toml.tpl`.
|
`default/themed/shell.toml.tpl`.
|
||||||
|
|
||||||
|
Themes can override the entire generated file by shipping `shell.toml`, or just
|
||||||
|
one section by shipping `shell.<section>.toml`. For example,
|
||||||
|
`shell.lock.toml` replaces only the `[lock]` section after the default
|
||||||
|
`shell.toml` has been generated:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
text = "#ffffff"
|
||||||
|
placeholder = "#ffffff"
|
||||||
|
border = "#ffffff"
|
||||||
|
```
|
||||||
|
|
||||||
|
The filename decides the target section, so the `[lock]` header is optional.
|
||||||
|
|
||||||
The running shell reads `shell.toml` into two QML singletons:
|
The running shell reads `shell.toml` into two QML singletons:
|
||||||
|
|
||||||
- `Color` for palette and surface roles like `Color.menu.border`.
|
- `Color` for palette and surface roles like `Color.menu.border`.
|
||||||
|
|||||||
@@ -14,11 +14,9 @@ import qs.Commons
|
|||||||
// their parent surface — so keyboard-summoned popups fell flat without it.
|
// their parent surface — so keyboard-summoned popups fell flat without it.
|
||||||
//
|
//
|
||||||
// API is a subset of Common.PopupCard: anchorItem, owner, bar, open,
|
// API is a subset of Common.PopupCard: anchorItem, owner, bar, open,
|
||||||
// padding, margin, contentWidth/Height, default contentItem. Missing on
|
// padding, margin, contentWidth/Height, centerOnBar, default contentItem.
|
||||||
// purpose (for now): centerOnBar, triggerMode ("hover"), containsMouse.
|
// Missing on purpose (for now): triggerMode ("hover"), containsMouse.
|
||||||
// Hover-mode popups (system-stats, weather) and centered popups
|
// Hover-mode popups (system-stats) need extra plumbing before migrating.
|
||||||
// (calendar week-view) need extra plumbing before migrating; converting
|
|
||||||
// them is a follow-up.
|
|
||||||
//
|
//
|
||||||
// Positioning: full-screen layer-shell with the card placed inside at
|
// Positioning: full-screen layer-shell with the card placed inside at
|
||||||
// `cardOrigin`. We use the bar window's height/width for the perpendicular
|
// `cardOrigin`. We use the bar window's height/width for the perpendicular
|
||||||
@@ -42,6 +40,7 @@ PanelWindow {
|
|||||||
property int padding: Style.spacing.popupPadding
|
property int padding: Style.spacing.popupPadding
|
||||||
property int contentWidth: Style.space(280)
|
property int contentWidth: Style.space(280)
|
||||||
property int contentHeight: Style.space(200)
|
property int contentHeight: Style.space(200)
|
||||||
|
property bool centerOnBar: false
|
||||||
property bool open: false
|
property bool open: false
|
||||||
property int gap: Style.gapsOut // distance between bar edge and panel
|
property int gap: Style.gapsOut // distance between bar edge and panel
|
||||||
property bool popoutSwitching: false
|
property bool popoutSwitching: false
|
||||||
@@ -173,7 +172,13 @@ PanelWindow {
|
|||||||
readonly property point cardOrigin: {
|
readonly property point cardOrigin: {
|
||||||
if (!anchorItem || !bar) return Qt.point(margin, margin)
|
if (!anchorItem || !bar) return Qt.point(margin, margin)
|
||||||
var x = 0, y = 0
|
var x = 0, y = 0
|
||||||
if (barPos === "bottom") {
|
if (centerOnBar && (barPos === "top" || barPos === "bottom")) {
|
||||||
|
x = screenW / 2 - contentWidth / 2
|
||||||
|
y = barPos === "bottom" ? screenH - barH - contentHeight - gap : barH + gap
|
||||||
|
} else if (centerOnBar) {
|
||||||
|
x = barPos === "left" ? barW + gap : screenW - barW - contentWidth - gap
|
||||||
|
y = screenH / 2 - contentHeight / 2
|
||||||
|
} else if (barPos === "bottom") {
|
||||||
x = anchorScreenPos.x + anchorW / 2 - contentWidth / 2
|
x = anchorScreenPos.x + anchorW / 2 - contentWidth / 2
|
||||||
y = screenH - barH - contentHeight - gap
|
y = screenH - barH - contentHeight - gap
|
||||||
} else if (barPos === "left") {
|
} else if (barPos === "left") {
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ Item {
|
|||||||
property bool useTransparentForeground: false
|
property bool useTransparentForeground: false
|
||||||
property bool transparent: false
|
property bool transparent: false
|
||||||
property bool centerSectionHovered: false
|
property bool centerSectionHovered: false
|
||||||
|
property bool centerSectionRevealHeld: false
|
||||||
|
property bool centerHoverRevealSuppressed: false
|
||||||
property int barConfigSerial: 0
|
property int barConfigSerial: 0
|
||||||
property string position: "top"
|
property string position: "top"
|
||||||
// Resolves through fontconfig at paint time (Style.font.family defaults
|
// Resolves through fontconfig at paint time (Style.font.family defaults
|
||||||
@@ -374,6 +376,22 @@ Item {
|
|||||||
|
|
||||||
Component.onCompleted: applyBarConfig()
|
Component.onCompleted: applyBarConfig()
|
||||||
|
|
||||||
|
function setCenterSectionHovered(hovered) {
|
||||||
|
centerSectionHovered = hovered
|
||||||
|
if (hovered) {
|
||||||
|
centerSectionRevealTimer.stop()
|
||||||
|
centerSectionRevealHeld = true
|
||||||
|
} else {
|
||||||
|
centerSectionRevealTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: centerSectionRevealTimer
|
||||||
|
interval: 120
|
||||||
|
onTriggered: root.centerSectionRevealHeld = root.centerSectionHovered
|
||||||
|
}
|
||||||
|
|
||||||
function run(command) {
|
function run(command) {
|
||||||
if (!command) return
|
if (!command) return
|
||||||
|
|
||||||
@@ -970,7 +988,7 @@ Item {
|
|||||||
CenterGestureArea { anchors.fill: parent }
|
CenterGestureArea { anchors.fill: parent }
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
onHoveredChanged: root.centerSectionHovered = hovered
|
onHoveredChanged: root.setCenterSectionHovered(hovered)
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleList {
|
ModuleList {
|
||||||
@@ -1001,7 +1019,7 @@ Item {
|
|||||||
|
|
||||||
visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock"
|
visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock"
|
||||||
clockHovered: centerAnchorModule.hovered
|
clockHovered: centerAnchorModule.hovered
|
||||||
centerHovered: root.centerSectionHovered
|
centerHovered: root.centerSectionRevealHeld && !root.centerHoverRevealSuppressed
|
||||||
anchors.right: centerAnchorModule.left
|
anchors.right: centerAnchorModule.left
|
||||||
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
anchors.verticalCenter: centerAnchorModule.verticalCenter
|
||||||
}
|
}
|
||||||
@@ -1025,7 +1043,7 @@ Item {
|
|||||||
CenterGestureArea { anchors.fill: parent }
|
CenterGestureArea { anchors.fill: parent }
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
onHoveredChanged: root.centerSectionHovered = hovered
|
onHoveredChanged: root.setCenterSectionHovered(hovered)
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleList {
|
ModuleList {
|
||||||
@@ -1056,7 +1074,7 @@ Item {
|
|||||||
|
|
||||||
visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock"
|
visible: centerRoot.hasAnchor && centerAnchorModule.moduleName === "omarchy.clock"
|
||||||
clockHovered: centerAnchorModule.hovered
|
clockHovered: centerAnchorModule.hovered
|
||||||
centerHovered: root.centerSectionHovered
|
centerHovered: root.centerSectionRevealHeld && !root.centerHoverRevealSuppressed
|
||||||
anchors.bottom: centerAnchorModule.top
|
anchors.bottom: centerAnchorModule.top
|
||||||
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
anchors.horizontalCenter: centerAnchorModule.horizontalCenter
|
||||||
}
|
}
|
||||||
@@ -1098,13 +1116,8 @@ Item {
|
|||||||
implicitHeight: button.implicitHeight
|
implicitHeight: button.implicitHeight
|
||||||
width: implicitWidth
|
width: implicitWidth
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
opacity: revealed ? 1.0 : 0
|
|
||||||
z: 500
|
z: 500
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation { duration: 120; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
|
|
||||||
HoverHandler { id: controlHover }
|
HoverHandler { id: controlHover }
|
||||||
|
|
||||||
Component.onCompleted: root.registerConfigControl(configControl)
|
Component.onCompleted: root.registerConfigControl(configControl)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ BarWidget {
|
|||||||
property bool indicatorAreaHovered: false
|
property bool indicatorAreaHovered: false
|
||||||
property bool indicatorItemHovered: false
|
property bool indicatorItemHovered: false
|
||||||
readonly property bool alwaysShowIndicators: setting("alwaysShow", false) === true
|
readonly property bool alwaysShowIndicators: setting("alwaysShow", false) === true
|
||||||
readonly property bool revealInactiveIndicators: alwaysShowIndicators || indicatorAreaHovered || indicatorItemHovered || (bar && bar.centerSectionHovered === true)
|
readonly property bool revealInactiveIndicators: alwaysShowIndicators || indicatorAreaHovered || indicatorItemHovered || (bar && bar.centerSectionRevealHeld === true && bar.centerHoverRevealSuppressed !== true)
|
||||||
|
|
||||||
signal refreshRequested()
|
signal refreshRequested()
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Item {
|
|||||||
readonly property bool errorState: failureMessage.length > 0
|
readonly property bool errorState: failureMessage.length > 0
|
||||||
readonly property var inputBorderSpec: errorState
|
readonly property var inputBorderSpec: errorState
|
||||||
? Border.surfaceSpec("lock", "border-error", Color.lock.borderError, root.outlineThickness, "border-alpha")
|
? Border.surfaceSpec("lock", "border-error", Color.lock.borderError, root.outlineThickness, "border-alpha")
|
||||||
: Border.hyprlandActiveSpec(Color.accent, root.outlineThickness)
|
: Border.surfaceSpec("lock", "border-active", Color.lock.borderActive, root.outlineThickness, "border-alpha")
|
||||||
|
|
||||||
signal submitPassword(string password)
|
signal submitPassword(string password)
|
||||||
signal passwordTextEdited(string password)
|
signal passwordTextEdited(string password)
|
||||||
|
|||||||
@@ -9,17 +9,38 @@ Panel {
|
|||||||
id: root
|
id: root
|
||||||
moduleName: "omarchy.weather"
|
moduleName: "omarchy.weather"
|
||||||
ipcTarget: "omarchy.weather"
|
ipcTarget: "omarchy.weather"
|
||||||
|
manageIpc: false
|
||||||
|
|
||||||
property var anchorItem: null
|
property var anchorItem: null
|
||||||
|
property bool openedFromHotkey: false
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
|
openedFromHotkey = false
|
||||||
|
setCenterHoverRevealSuppressed(false)
|
||||||
root.controller.show()
|
root.controller.show()
|
||||||
root.refresh()
|
root.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openFromHotkey() {
|
||||||
|
openedFromHotkey = true
|
||||||
|
setCenterHoverRevealSuppressed(true)
|
||||||
|
root.controller.show()
|
||||||
|
root.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
setCenterHoverRevealSuppressed(false)
|
||||||
|
root.controller.hide()
|
||||||
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
if (root.opened) root.close()
|
if (root.opened) root.close()
|
||||||
else root.open()
|
else root.openFromHotkey()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCenterHoverRevealSuppressed(value) {
|
||||||
|
if (root.bar && "centerHoverRevealSuppressed" in root.bar)
|
||||||
|
root.bar.centerHoverRevealSuppressed = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parsed wttr.in j1 response. Kept on failure so stale data stays visible.
|
// Parsed wttr.in j1 response. Kept on failure so stale data stays visible.
|
||||||
@@ -190,29 +211,45 @@ Panel {
|
|||||||
onTriggered: root.refresh()
|
onTriggered: root.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupCard {
|
IpcHandler {
|
||||||
id: popup
|
target: root.ipcTarget
|
||||||
|
|
||||||
|
function open(): void { root.openFromHotkey() }
|
||||||
|
function close(): void { root.close() }
|
||||||
|
function show(): void { root.openFromHotkey() }
|
||||||
|
function hide(): void { root.close() }
|
||||||
|
function toggle(): void { root.toggle() }
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardPanel {
|
||||||
|
id: panel
|
||||||
anchorItem: root.anchorItem
|
anchorItem: root.anchorItem
|
||||||
owner: root
|
owner: root
|
||||||
bar: root.bar
|
bar: root.bar
|
||||||
open: root.opened
|
open: root.opened
|
||||||
centerOnBar: true
|
centerOnBar: true
|
||||||
triggerMode: "click"
|
focusTarget: keyCatcher
|
||||||
contentWidth: popup.fittedContentWidth(Style.space(480))
|
contentWidth: panel.fittedContentWidth(Style.space(480))
|
||||||
contentHeight: popup.fittedContentHeight(weatherColumn.implicitHeight)
|
contentHeight: panel.fittedContentHeight(weatherColumn.implicitHeight)
|
||||||
|
|
||||||
Flickable {
|
PanelKeyCatcher {
|
||||||
id: weatherScroll
|
id: keyCatcher
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
contentWidth: width
|
onCloseRequested: root.close()
|
||||||
contentHeight: weatherColumn.implicitHeight
|
onTabRequested: function(direction) { root.switchPanel(direction) }
|
||||||
clip: true
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
Column {
|
Flickable {
|
||||||
id: weatherColumn
|
id: weatherScroll
|
||||||
width: weatherScroll.width
|
anchors.fill: parent
|
||||||
spacing: Style.space(14)
|
contentWidth: width
|
||||||
|
contentHeight: weatherColumn.implicitHeight
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: weatherColumn
|
||||||
|
width: weatherScroll.width
|
||||||
|
spacing: Style.space(14)
|
||||||
|
|
||||||
// ---- Hero row: big icon + temp on the left; location and stats stacked on the right.
|
// ---- Hero row: big icon + temp on the left; location and stats stacked on the right.
|
||||||
Item {
|
Item {
|
||||||
@@ -431,6 +468,7 @@ Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Poll the weather pill text/class every minute. Local to this widget.
|
// Poll the weather pill text/class every minute. Local to this widget.
|
||||||
Process {
|
Process {
|
||||||
|
|||||||
@@ -264,6 +264,12 @@ bright-magenta={{ bright_magenta }}
|
|||||||
legacy-bright-purple={{ bright_purple }}
|
legacy-bright-purple={{ bright_purple }}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
cat >"$NEXT_THEME/shell.lock.toml" <<'EOF'
|
||||||
|
text = "#ffffff"
|
||||||
|
placeholder = "#ffffff"
|
||||||
|
border = "#ffffff"
|
||||||
|
EOF
|
||||||
|
|
||||||
OMARCHY_PATH="$ROOT" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-templates"
|
OMARCHY_PATH="$ROOT" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-templates"
|
||||||
grep -qx 'mix=#808080' "$NEXT_THEME/mix-test.txt" || fail "theme template mix helper works"
|
grep -qx 'mix=#808080' "$NEXT_THEME/mix-test.txt" || fail "theme template mix helper works"
|
||||||
grep -qx 'strip=808080' "$NEXT_THEME/mix-test.txt" || fail "theme template mix_strip helper works"
|
grep -qx 'strip=808080' "$NEXT_THEME/mix-test.txt" || fail "theme template mix_strip helper works"
|
||||||
@@ -291,6 +297,16 @@ grep -qx 'fallback-shell=#336699' "$NEXT_THEME/gradient-test.txt" || fail "theme
|
|||||||
grep -qx 'fallback-shell-gradient=#336699' "$NEXT_THEME/gradient-test.txt" || fail "theme template shell_gradient fallback works"
|
grep -qx 'fallback-shell-gradient=#336699' "$NEXT_THEME/gradient-test.txt" || fail "theme template shell_gradient fallback works"
|
||||||
pass "theme template gradient helpers work"
|
pass "theme template gradient helpers work"
|
||||||
|
|
||||||
|
awk '
|
||||||
|
/^\[lock\]$/ { in_lock = 1; next }
|
||||||
|
/^\[/ { in_lock = 0 }
|
||||||
|
in_lock && /^text = "#ffffff"$/ { text = 1 }
|
||||||
|
in_lock && /^placeholder = "#ffffff"$/ { placeholder = 1 }
|
||||||
|
in_lock && /^border = "#ffffff"$/ { border = 1 }
|
||||||
|
END { exit(text && placeholder && border ? 0 : 1) }
|
||||||
|
' "$NEXT_THEME/shell.toml" || fail "theme shell section override is merged"
|
||||||
|
pass "theme shell section override is merged"
|
||||||
|
|
||||||
jq -e '.name == "omarchy-system" and .vars.panel == "#0f0f0f" and .vars.border == "#4d4d4d" and .colors.accent == "accent"' "$NEXT_THEME/pi.json" >/dev/null
|
jq -e '.name == "omarchy-system" and .vars.panel == "#0f0f0f" and .vars.border == "#4d4d4d" and .colors.accent == "accent"' "$NEXT_THEME/pi.json" >/dev/null
|
||||||
pass "pi theme template is generated from standard themed templates"
|
pass "pi theme template is generated from standard themed templates"
|
||||||
cp "$NEXT_THEME/pi.json" "$CURRENT_THEME/pi.json"
|
cp "$NEXT_THEME/pi.json" "$CURRENT_THEME/pi.json"
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
text = "#a9b1d6"
|
||||||
|
placeholder = "#a9b1d6"
|
||||||
|
text-error = "#a9b1d6"
|
||||||
|
border = "#a9b1d6"
|
||||||
|
border-active = "#a9b1d6"
|
||||||
|
border-error = "#a9b1d6"
|
||||||
Reference in New Issue
Block a user