feat(quickshell): cap the rail's open chamfers and trace between them
A chamfer with no neighbour behind it now carries the corner it removed, put back OUTSIDE the panel as a detached accent triangle. capGap is the perpendicular distance from the cut, hence the per-axis shift of capGap over root 2: the cap moves along the cut's normal, not along an axis. The trace joining two caps belongs to the RAIL, not the panel — the run it draws is the gap BETWEEN two panels, which no panel can see. HyprChromeBar filters the row down to panels (a slack Item has no `rightChamfer`, so spacers drop out, and dropping them is exactly what makes a trace span them), then joins each right cap to the next left cap: straight, one 45° step at the midpoint of the gap, straight. It meets the middle of each cap's outward face rather than its tip. TestPanel is a staging slot between two spacers. It counts seconds since load, which is the cheapest proof the panel is live, and standing alone it exercises a cap and a trace at both ends — something a rail of butted panels never does. Restructures the module in the same commit, since the moves and the edits above land in the same files: folders are PascalCase, the bar and its widgets moved under Widgets/Bar, and DebugWindow takes the HyprChrome Theme instead of the legacy one. The two singletons are identical today, so that is not a visual fix — it is a palette edit reaching the debug stage in future. Rename detection needs -M40% to follow HyprChromeBar, which grew past the default similarity threshold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U19X5LGTxtq4pb4jdNVivv
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import qs.HyprChrome.Theme
|
||||
|
||||
// Debug stage: a bare, chrome-less staging area in the middle of ONE monitor
|
||||
// (the secondary by default), used to look at a widget in isolation before it
|
||||
// has a home in the bar or a launcher.
|
||||
//
|
||||
// DebugWindow {
|
||||
// VitalBar { width: 220; value: 0.4 }
|
||||
// }
|
||||
//
|
||||
// Children are reparented into the centred slot, which sizes itself to them —
|
||||
// so they must carry their own size (implicit or explicit). Do NOT anchor a
|
||||
// child to the slot (`anchors.fill: parent`): the slot measures its children,
|
||||
// so that is a binding loop.
|
||||
//
|
||||
// Nothing is drawn around them — no panel, no background, no dim: whatever is
|
||||
// staged is exactly what appears. Only the staged widgets take pointer input
|
||||
// (`mask`), so the rest of the monitor stays clickable, and the window never
|
||||
// takes keyboard focus. Toggle: SUPER CTRL D.
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
// Monitor to stage on. Falls back to the LAST connected screen when the
|
||||
// name matches nothing, so a single-monitor session still gets a stage.
|
||||
property string screenName: "HDMI-A-1"
|
||||
property bool active: true
|
||||
|
||||
default property alias content: slot.data
|
||||
|
||||
// Quickshell.screens is a QML list, not a JS array — no .find() on it.
|
||||
readonly property var targetScreen: {
|
||||
const screens = Quickshell.screens;
|
||||
if (screens.length === 0)
|
||||
return null;
|
||||
for (let i = 0; i < screens.length; i++) {
|
||||
if (screens[i].name === root.screenName)
|
||||
return screens[i];
|
||||
}
|
||||
return screens[screens.length - 1];
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
root.active = !root.active;
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "debug"
|
||||
description: "Toggle the debug widget stage"
|
||||
onPressed: root.toggle()
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: win
|
||||
|
||||
screen: root.targetScreen
|
||||
visible: root.active && root.targetScreen !== null
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
// A HUD, not a modal: never steal the keyboard from the focused window.
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
// Ignore the dense bar's exclusive zone so "centred" means the centre of
|
||||
// the monitor, not the centre of what is left below the bar.
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
// Everything outside the staged widgets is click-through.
|
||||
mask: Region {
|
||||
item: slot
|
||||
}
|
||||
|
||||
Item {
|
||||
id: slot
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: Math.max(childrenRect.width, placeholder.visible ? placeholder.implicitWidth : 0)
|
||||
height: Math.max(childrenRect.height, placeholder.visible ? placeholder.implicitHeight : 0)
|
||||
}
|
||||
|
||||
// Sits beside the slot, not inside it, so it never counts itself. Without
|
||||
// it an unsized child looks identical to a broken window.
|
||||
Text {
|
||||
id: placeholder
|
||||
|
||||
visible: slot.children.length === 0
|
||||
anchors.centerIn: parent
|
||||
text: "NO WIDGETS STAGED"
|
||||
color: Theme.muted
|
||||
font.family: Theme.microFont
|
||||
font.pixelSize: 8
|
||||
font.letterSpacing: 1.2
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user