Bring on gradient borders

This commit is contained in:
Ryan Hughes
2026-06-02 11:17:13 -04:00
parent 30bafe7bb0
commit 98ba4a9697
48 changed files with 1323 additions and 328 deletions
+55
View File
@@ -0,0 +1,55 @@
import QtQuick
import QtQuick.Shapes
import qs.Commons
import "../Commons/BorderGeometry.js" as Geometry
// Visual-only border renderer. It draws a filled rounded ring so borders can
// have gradients and independent top/right/bottom/left widths. Flat uniform
// borders should stay on Rectangle.border; this component is the fallback for
// cases Rectangle cannot represent.
Item {
id: root
property var borderSpec: Border.none()
property real radius: 0
readonly property var _widths: borderSpec && borderSpec.widths ? borderSpec.widths : Geometry.parseWidthSpec(0, 0)
readonly property bool hasBorder: Geometry.maxWidth(_widths) > 0
readonly property var _gradient: borderSpec && borderSpec.gradient ? borderSpec.gradient : ({ colors: [], angle: 0, enabled: false })
readonly property var _colors: _gradient.enabled ? _gradient.colors : [Border.color(borderSpec), Border.color(borderSpec)]
readonly property var _endpoints: Geometry.gradientEndpoints(width, height, _gradient.angle || 0)
readonly property string _path: Geometry.ringPath(width, height, radius, _widths)
visible: hasBorder && width > 0 && height > 0
anchors.fill: parent
z: 100000
Shape {
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillRule: ShapePath.OddEvenFill
strokeWidth: 0
fillGradient: LinearGradient {
x1: root._endpoints.x1
y1: root._endpoints.y1
x2: root._endpoints.x2
y2: root._endpoints.y2
GradientStop { position: Geometry.stopPosition(root._colors, 0); color: Geometry.stopColor(root._colors, 0) }
GradientStop { position: Geometry.stopPosition(root._colors, 1); color: Geometry.stopColor(root._colors, 1) }
GradientStop { position: Geometry.stopPosition(root._colors, 2); color: Geometry.stopColor(root._colors, 2) }
GradientStop { position: Geometry.stopPosition(root._colors, 3); color: Geometry.stopColor(root._colors, 3) }
GradientStop { position: Geometry.stopPosition(root._colors, 4); color: Geometry.stopColor(root._colors, 4) }
GradientStop { position: Geometry.stopPosition(root._colors, 5); color: Geometry.stopColor(root._colors, 5) }
GradientStop { position: Geometry.stopPosition(root._colors, 6); color: Geometry.stopColor(root._colors, 6) }
GradientStop { position: Geometry.stopPosition(root._colors, 7); color: Geometry.stopColor(root._colors, 7) }
GradientStop { position: Geometry.stopPosition(root._colors, 8); color: Geometry.stopColor(root._colors, 8) }
GradientStop { position: Geometry.stopPosition(root._colors, 9); color: Geometry.stopColor(root._colors, 9) }
}
PathSvg { path: root._path }
}
}
}
+40
View File
@@ -0,0 +1,40 @@
import QtQuick
import qs.Commons
// Rectangle-compatible surface with Omarchy border specs. Uses native
// Rectangle.border for cheap flat/uniform borders and BorderOverlay for
// gradients or per-side widths.
Rectangle {
id: root
property var borderSpec: Border.none()
property real padding: 0
property real topPadding: padding
property real rightPadding: padding
property real bottomPadding: padding
property real leftPadding: padding
readonly property real borderTop: Border.top(borderSpec)
readonly property real borderRight: Border.right(borderSpec)
readonly property real borderBottom: Border.bottom(borderSpec)
readonly property real borderLeft: Border.left(borderSpec)
readonly property real contentTopInset: borderTop + topPadding
readonly property real contentRightInset: borderRight + rightPadding
readonly property real contentBottomInset: borderBottom + bottomPadding
readonly property real contentLeftInset: borderLeft + leftPadding
readonly property bool usesOverlayBorder: Border.needsOverlay(borderSpec)
border.color: Border.canUseNative(borderSpec) ? Border.color(borderSpec) : "transparent"
border.width: Border.canUseNative(borderSpec) ? Border.uniformWidth(borderSpec) : 0
Loader {
anchors.fill: parent
active: root.usesOverlayBorder
sourceComponent: BorderOverlay {
anchors.fill: parent
radius: root.radius
borderSpec: root.borderSpec
}
}
}
+24 -22
View File
@@ -17,7 +17,7 @@ import qs.Commons
//
// Emits `hovered(bool)` so panels with their own keyboard cursor model
// can update state on mouse enter/leave.
Rectangle {
BorderSurface {
id: root
property string text: ""
@@ -46,6 +46,11 @@ Rectangle {
property real verticalPadding: Style.spacing.controlPaddingY
property bool leftAlign: false
leftPadding: horizontalPadding
rightPadding: horizontalPadding
topPadding: verticalPadding
bottomPadding: verticalPadding
// Tooltip palette. Auto-rendered if tooltipText is set. Defaults pull
// from [tooltip] in shell.toml; override per-instance only when a button
// intentionally wants a tooltip that diverges from the theme.
@@ -62,13 +67,21 @@ Rectangle {
Keys.onEnterPressed: if (focusable) root.clicked()
Keys.onSpacePressed: if (focusable) root.clicked()
implicitWidth: row.implicitWidth + horizontalPadding * 2
implicitHeight: row.implicitHeight + verticalPadding * 2
implicitWidth: row.implicitWidth + horizontalPadding * 2 + borderLeft + borderRight
implicitHeight: row.implicitHeight + verticalPadding * 2 + borderTop + borderBottom
radius: Style.cornerRadius
readonly property bool hot: mouseArea.containsMouse || hasCursor
readonly property bool _showFocusRing: focusable && activeFocus
readonly property color _selectedColor: Style.selectedStateColor(root.foreground, root.accent)
readonly property var _tooltipBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", root.tooltipBorder, Color.tooltip.border, Math.max(1, Style.normalBorderWidth))
readonly property var _selectedBorderSpec: Border.controlSpec("selected", root.foreground, root.accent)
readonly property var _normalBorderSpec: Border.controlSpec("normal", root.foreground, root.accent)
readonly property var _borderSpec: _showFocusRing ? Border.controlSpec("focus", root.foreground, root.accent)
: hot ? Border.controlSpec("hover-cursor", root.foreground, root.accent)
: selected ? (Border.controlHasWidth("selected") ? _selectedBorderSpec : (bordered ? _normalBorderSpec : Border.none()))
: bordered ? _normalBorderSpec
: Border.none()
color: mouseArea.pressed ? Style.pressedFillFor(root.foreground, root.accent)
: _showFocusRing ? Style.focusFillFor(root.foreground, root.accent)
@@ -84,17 +97,7 @@ Rectangle {
// default for plain buttons; explicitly bordered buttons keep their
// normal border when selected unless selected-border-width opts in to a
// dedicated selected border.
border.color: _showFocusRing ? Style.focusBorderFor(root.foreground, root.accent)
: hot ? Style.hoverBorderFor(root.foreground, root.accent)
: selected ? (Style.selectedBorderWidth > 0 ? Style.selectedBorderFor(root.foreground, root.accent) : Style.normalBorderFor(root.foreground, root.accent))
: bordered ? Style.normalBorderFor(root.foreground, root.accent)
: "transparent"
border.width: _showFocusRing ? Style.focusBorderWidth
: hot ? Style.hoverBorderWidth
: selected ? (Style.selectedBorderWidth > 0 ? Style.selectedBorderWidth : (bordered ? Style.normalBorderWidth : 0))
: bordered ? Style.normalBorderWidth
: 0
borderSpec: _borderSpec
Behavior on color { ColorAnimation { duration: 120 } }
@@ -103,10 +106,9 @@ Rectangle {
text: root.tooltipText
delay: 400
padding: 0
background: Rectangle {
background: BorderSurface {
color: root.tooltipBackground
border.color: root.tooltipBorder
border.width: Math.max(1, Style.normalBorderWidth)
borderSpec: root._tooltipBorderSpec
radius: 0
}
contentItem: Text {
@@ -114,10 +116,10 @@ Rectangle {
color: root.tooltipForeground
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
leftPadding: Style.spacing.controlPaddingX
rightPadding: Style.spacing.controlPaddingX
topPadding: Style.spacing.controlPaddingY
bottomPadding: Style.spacing.controlPaddingY
leftPadding: Border.left(root._tooltipBorderSpec) + Style.spacing.controlPaddingX
rightPadding: Border.right(root._tooltipBorderSpec) + Style.spacing.controlPaddingX
topPadding: Border.top(root._tooltipBorderSpec) + Style.spacing.controlPaddingY
bottomPadding: Border.bottom(root._tooltipBorderSpec) + Style.spacing.controlPaddingY
}
}
@@ -125,7 +127,7 @@ Rectangle {
id: row
anchors.verticalCenter: parent.verticalCenter
anchors.left: root.leftAlign ? parent.left : undefined
anchors.leftMargin: root.leftAlign ? root.horizontalPadding : 0
anchors.leftMargin: root.leftAlign ? root.contentLeftInset : 0
anchors.horizontalCenter: root.leftAlign ? undefined : parent.horizontalCenter
spacing: Style.spacing.controlGap
+11 -8
View File
@@ -46,20 +46,24 @@ Item {
MouseArea { anchors.fill: parent; onClicked: root.canceled() }
Rectangle {
BorderSurface {
id: card
width: Math.min(parent.width - Style.space(96), Style.space(370))
height: Style.space(132)
anchors.centerIn: parent
color: root.background
border.color: root.selectedText
border.width: Style.normalBorderWidth
borderSpec: Border.flat(root.selectedText, Style.normalBorderWidth)
padding: Style.space(18)
radius: root.cornerRadius
MouseArea { anchors.fill: parent; onClicked: {} }
Item {
anchors.fill: parent
anchors.margins: Style.space(18)
anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
Text {
anchors.left: parent.left
@@ -80,7 +84,7 @@ Item {
Repeater {
model: [root.cancelText, root.confirmText]
Rectangle {
BorderSurface {
required property int index
required property string modelData
@@ -92,10 +96,9 @@ Item {
color: selected
? (destructive ? Util.alpha(Color.urgent, 0.22) : root.selectedBackground)
: "transparent"
border.color: destructive
borderSpec: Border.flat(destructive
? (selected ? Color.urgent : Util.alpha(Color.urgent, 0.56))
: (selected ? root.selectedText : Util.alpha(root.foreground, 0.38))
border.width: Style.normalBorderWidth
: (selected ? root.selectedText : Util.alpha(root.foreground, 0.38)), Style.normalBorderWidth)
radius: 0
Text {
+6 -12
View File
@@ -11,7 +11,7 @@ import qs.Commons
// hover-cursor border. `outline` remains as a compatibility flag for
// callers that used to request border-only rows, but slider rows still
// receive the same hover-cursor background as every other row.
Rectangle {
BorderSurface {
id: root
property bool hasCursor: false
@@ -27,19 +27,13 @@ Rectangle {
radius: Style.cornerRadius
color: hasCursor ? fill : (current ? currentFill : "transparent")
border.color: root.hasCursor
? Style.hoverBorderFor(root.foreground, root.accent)
borderSpec: root.hasCursor
? Border.controlSpec("hover-cursor", root.foreground, root.accent)
: (root.current
? Style.selectedBorderFor(root.foreground, root.accent)
? Border.controlSpec("selected", root.foreground, root.accent)
: (root.bordered
? Style.normalBorderFor(root.foreground, root.accent)
: "transparent"))
border.width: root.hasCursor
? Style.hoverBorderWidth
: (root.current
? Style.selectedBorderWidth
: (root.bordered ? Style.normalBorderWidth : 0))
? Border.controlSpec("normal", root.foreground, root.accent)
: Border.none()))
Behavior on color {
ColorAnimation { duration: 60 }
+13 -9
View File
@@ -26,6 +26,7 @@ Item {
property color background: Color.popups.background
property color popupBorder: Color.popups.border
property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight
@@ -78,7 +79,7 @@ Item {
font.bold: true
}
Rectangle {
BorderSurface {
id: trigger
width: parent.width
height: root.rowHeight
@@ -86,10 +87,10 @@ Item {
readonly property bool _focused: trigger.activeFocus
readonly property bool _hot: triggerHover.hovered || root.hasCursor
readonly property var _borderSpec: Border.controlSpec(trigger._focused ? "focus" : (trigger._hot ? "hover-cursor" : "normal"), root.foreground, root.accent)
color: Style.controlFill(trigger._focused, trigger._hot, root.foreground, root.accent)
border.color: Style.controlBorder(trigger._focused, trigger._hot, root.foreground, root.accent)
border.width: Style.controlBorderWidth(trigger._focused, trigger._hot)
borderSpec: _borderSpec
activeFocusOnTab: true
@@ -112,8 +113,8 @@ Item {
anchors.left: parent.left
anchors.right: chevron.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.spacing.controlPaddingX
anchors.rightMargin: Style.spacing.md
anchors.leftMargin: trigger.borderLeft + Style.spacing.controlPaddingX
anchors.rightMargin: trigger.borderRight + Style.spacing.md
text: root.currentLabel()
color: root.foreground
font.family: root.fontFamily
@@ -125,7 +126,7 @@ Item {
id: chevron
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Style.spacing.controlGap
anchors.rightMargin: trigger.borderRight + Style.spacing.controlGap
text: "󰅀"
color: Qt.darker(root.foreground, 1.2)
font.family: root.fontFamily
@@ -149,12 +150,15 @@ Item {
implicitHeight: Math.min(root.options.length * root.popupRowHeight + Math.max(0, root.options.length - 1) * Style.spacing.labelGap + Style.spacing.xxs,
root.popupRowHeight * 8 + 7 * Style.spacing.labelGap + Style.spacing.xxs)
padding: Style.spacing.hairline
leftPadding: Border.left(root.popupBorderSpec) + Style.spacing.hairline
rightPadding: Border.right(root.popupBorderSpec) + Style.spacing.hairline
topPadding: Border.top(root.popupBorderSpec) + Style.spacing.hairline
bottomPadding: Border.bottom(root.popupBorderSpec) + Style.spacing.hairline
focus: true
background: Rectangle {
background: BorderSurface {
color: root.background
border.color: root.popupBorder
border.width: Style.normalBorderWidth
borderSpec: root.popupBorderSpec
radius: Style.cornerRadius
}
+7 -4
View File
@@ -285,15 +285,15 @@ PanelWindow {
// --- card ----------------------------------------------------------------
Rectangle {
BorderSurface {
id: card
x: root.cardOrigin.x
y: root.cardOrigin.y
width: root.contentWidth
height: root.contentHeight
color: Color.popups.background
border.color: Color.popups.border
border.width: Math.max(1, Style.space(2))
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
padding: root.padding
radius: Style.cornerRadius
opacity: root.open || root.popoutSwitching ? 1.0 : 0
@@ -309,7 +309,10 @@ PanelWindow {
Item {
id: contentHolder
anchors.fill: parent
anchors.margins: root.padding
anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
opacity: root.popoutSwitching ? (root.open ? 1.0 : 0) : 1.0
Behavior on opacity {
+21 -19
View File
@@ -37,6 +37,7 @@ Item {
property color background: Color.popups.background
property color popupBorder: Color.popups.border
property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight
@@ -266,7 +267,7 @@ Item {
font.bold: true
}
Rectangle {
BorderSurface {
id: trigger
width: parent.width
height: root.rowHeight
@@ -274,10 +275,10 @@ Item {
readonly property bool _focused: trigger.activeFocus
readonly property bool _hot: triggerHover.hovered || root.hasCursor
readonly property var _borderSpec: Border.controlSpec(trigger._focused ? "focus" : (trigger._hot ? "hover-cursor" : "normal"), root.foreground, root.accent)
color: Style.controlFill(trigger._focused, trigger._hot, root.foreground, root.accent)
border.color: Style.controlBorder(trigger._focused, trigger._hot, root.foreground, root.accent)
border.width: Style.controlBorderWidth(trigger._focused, trigger._hot)
borderSpec: _borderSpec
activeFocusOnTab: true
@@ -300,8 +301,8 @@ Item {
anchors.left: parent.left
anchors.right: chevron.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.spacing.controlPaddingX
anchors.rightMargin: Style.spacing.md
anchors.leftMargin: trigger.borderLeft + Style.spacing.controlPaddingX
anchors.rightMargin: trigger.borderRight + Style.spacing.md
text: root.selectionLabel() || root.triggerLabel || root.noSelectionText
color: root.selectionLabel() ? root.foreground : Qt.darker(root.foreground, 1.5)
font.family: root.fontFamily
@@ -313,7 +314,7 @@ Item {
id: chevron
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Style.spacing.controlGap
anchors.rightMargin: trigger.borderRight + Style.spacing.controlGap
text: "󰅀"
color: Qt.darker(root.foreground, 1.2)
font.family: root.fontFamily
@@ -359,6 +360,10 @@ Item {
// when there isn't room, otherwise the popup overflows the window.
implicitHeight: Math.min(_availableBelow, _idealContent, _maxRowsHeight)
padding: Style.spacing.hairline
leftPadding: Border.left(root.popupBorderSpec) + Style.spacing.hairline
rightPadding: Border.right(root.popupBorderSpec) + Style.spacing.hairline
topPadding: Border.top(root.popupBorderSpec) + Style.spacing.hairline
bottomPadding: Border.bottom(root.popupBorderSpec) + Style.spacing.hairline
focus: true
Connections {
@@ -369,10 +374,9 @@ Item {
function onHeightChanged() { popup.reposition() }
}
background: Rectangle {
background: BorderSurface {
color: root.background
border.color: root.popupBorder
border.width: Style.normalBorderWidth
borderSpec: root.popupBorderSpec
radius: Style.cornerRadius
}
@@ -432,7 +436,7 @@ Item {
}
}
Rectangle {
BorderSurface {
id: refreshButton
visible: root.arrayFrom(root.optionsCommand).length > 0
enabled: !root.loadingOptions
@@ -442,10 +446,9 @@ Item {
color: refreshHover.hovered
? Style.hoverFillFor(root.foreground, root.accent)
: Style.normalFillFor(root.foreground, root.accent)
border.color: refreshHover.hovered
? Style.hoverBorderFor(root.foreground, root.accent)
: Style.normalBorderFor(root.foreground, root.accent)
border.width: refreshHover.hovered ? Style.hoverBorderWidth : Style.normalBorderWidth
borderSpec: refreshHover.hovered
? Border.controlSpec("hover-cursor", root.foreground, root.accent)
: Border.controlSpec("normal", root.foreground, root.accent)
Text {
anchors.centerIn: parent
@@ -550,17 +553,16 @@ Item {
anchors.rightMargin: Style.spacing.controlPaddingX
spacing: Style.spacing.rowGap
Rectangle {
BorderSurface {
id: checkbox
width: Style.space(16)
height: Style.space(16)
radius: Math.max(2, Style.cornerRadius / 2)
anchors.verticalCenter: parent.verticalCenter
color: selected ? Style.selectedFillFor(root.foreground, root.accent) : "transparent"
border.color: selected
? Style.selectedBorderFor(root.foreground, root.accent)
: Style.normalBorderFor(root.foreground, root.accent)
border.width: selected ? Style.selectedBorderWidth : Style.normalBorderWidth
borderSpec: selected
? Border.controlSpec("selected", root.foreground, root.accent)
: Border.controlSpec("normal", root.foreground, root.accent)
Text {
anchors.centerIn: parent
+12 -7
View File
@@ -44,15 +44,20 @@ Column {
font.family: root.fontFamily
font.pixelSize: root.fontSize
readonly property bool _focused: spin.activeFocus
readonly property bool _hot: root._hovered || root.hasCursor
readonly property var _borderSpec: Border.controlSpec(_focused ? "focus" : (_hot ? "hover-cursor" : "normal"), root.foreground, root.accent)
leftPadding: Border.left(_borderSpec) + Style.spacing.controlPaddingX
rightPadding: Border.right(_borderSpec) + Style.spacing.controlPaddingX
topPadding: Border.top(_borderSpec)
bottomPadding: Border.bottom(_borderSpec)
onValueModified: root.modified(value)
background: Rectangle {
readonly property bool _focused: spin.activeFocus
readonly property bool _hot: root._hovered || root.hasCursor
color: Style.controlFill(_focused, _hot, root.foreground, root.accent)
border.color: Style.controlBorder(_focused, _hot, root.foreground, root.accent)
border.width: Style.controlBorderWidth(_focused, _hot)
background: BorderSurface {
color: Style.controlFill(spin._focused, spin._hot, root.foreground, root.accent)
borderSpec: spin._borderSpec
radius: Style.cornerRadius
HoverHandler {
+7 -11
View File
@@ -24,7 +24,7 @@ import qs.Commons
// Use this when a PanelActionButton is itself the cursor target (rather
// than living inside a CursorSurface row). Emits `hovered(bool)` on
// pointer enter/leave so the panel can update its cursor state to match.
Rectangle {
BorderSurface {
id: root
property string iconText: ""
@@ -53,22 +53,18 @@ Rectangle {
readonly property bool _showFocusRing: focusable && activeFocus
readonly property bool _hot: (mouse.containsMouse || root.hasCursor) && root.enabled
readonly property var _borderSpec: _showFocusRing
? Border.controlSpec("focus", hoverColor, hoverColor)
: (_hot && bordered
? Border.controlSpec("hover-cursor", hoverColor, hoverColor)
: (bordered ? Border.controlSpec("normal", foreground, Color.accent) : Border.none()))
color: _showFocusRing
? Style.focusFillFor(hoverColor, hoverColor)
: (_hot
? Style.hoverFillFor(hoverColor, hoverColor)
: "transparent")
border.width: _showFocusRing
? Style.focusBorderWidth
: (_hot && bordered
? Style.hoverBorderWidth
: (bordered ? Style.normalBorderWidth : 0))
border.color: _showFocusRing
? Style.focusBorderFor(hoverColor, hoverColor)
: (_hot && bordered
? Style.hoverBorderFor(hoverColor, hoverColor)
: Style.normalBorderFor(foreground, Color.accent))
borderSpec: _borderSpec
Behavior on color { ColorAnimation { duration: 60 } }
+2 -3
View File
@@ -56,15 +56,14 @@ Item {
height: 1
}
Rectangle {
BorderSurface {
id: detailPill
visible: root.detail !== ""
implicitWidth: detailText.implicitWidth + Style.space(10)
implicitHeight: detailText.implicitHeight + Style.space(4)
anchors.verticalCenter: parent.verticalCenter
color: "transparent"
border.color: Style.normalBorderFor(root.foreground)
border.width: Style.normalBorderWidth
borderSpec: Border.controlSpec("normal", root.foreground, Color.accent)
radius: Style.cornerRadius
Text {
+2 -3
View File
@@ -55,14 +55,13 @@ Item {
}
}
Rectangle {
BorderSurface {
id: knob
width: root.knobSize
height: root.knobSize
radius: root.knobSize / 2
color: root.knobColor
border.color: root.bar ? root.bar.background : "#101315"
border.width: Math.max(1, Style.space(2))
borderSpec: Border.flat(root.bar ? root.bar.background : "#101315", Math.max(1, Style.space(2)))
anchors.verticalCenter: track.verticalCenter
x: Math.max(0, Math.min(track.width - width, track.width * root.progress - width / 2))
scale: root._hot ? 1.15 : 1.0
+8 -7
View File
@@ -24,13 +24,14 @@ ToolTip {
property string fontFamily: Style.font.family
property real fontSize: Style.font.bodySmall
readonly property var panelBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", panelBorder, Color.tooltip.border, Style.normalBorderWidth)
delay: 400
padding: 0
background: Rectangle {
background: BorderSurface {
color: root.panelBackground
border.color: root.panelBorder
border.width: Style.normalBorderWidth
borderSpec: root.panelBorderSpec
radius: Style.cornerRadius
}
@@ -39,9 +40,9 @@ ToolTip {
color: root.panelForeground
font.family: root.fontFamily
font.pixelSize: root.fontSize
leftPadding: Style.spacing.controlPaddingX
rightPadding: Style.spacing.controlPaddingX
topPadding: Style.spacing.controlPaddingY
bottomPadding: Style.spacing.controlPaddingY
leftPadding: Border.left(root.panelBorderSpec) + Style.spacing.controlPaddingX
rightPadding: Border.right(root.panelBorderSpec) + Style.spacing.controlPaddingX
topPadding: Border.top(root.panelBorderSpec) + Style.spacing.controlPaddingY
bottomPadding: Border.bottom(root.panelBorderSpec) + Style.spacing.controlPaddingY
}
}
+8 -4
View File
@@ -14,6 +14,7 @@ PopupWindow {
property int contentWidth: Style.space(280)
property int contentHeight: Style.space(200)
property color borderColor: Color.popups.border
property var borderSpec: Border.localOrSurfaceSpec("popups", "border", borderColor, Color.popups.border, Math.max(1, Style.space(2)))
property bool open: false
property bool centerOnBar: false
// "click" — uses HyprlandFocusGrab so clicking outside dismisses the popup.
@@ -145,12 +146,12 @@ PopupWindow {
}
}
Rectangle {
BorderSurface {
id: card
anchors.fill: parent
color: Color.popups.background
border.color: root.borderColor
border.width: Math.max(1, Style.space(2))
borderSpec: root.borderSpec
padding: root.padding
radius: Style.cornerRadius
opacity: root.open ? 1.0 : 0
@@ -161,7 +162,10 @@ PopupWindow {
Item {
id: contentHolder
anchors.fill: parent
anchors.margins: root.padding
anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
}
HoverHandler {
+13 -9
View File
@@ -29,6 +29,7 @@ Item {
property color background: Color.popups.background
property color popupBorder: Color.popups.border
property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight
@@ -100,7 +101,7 @@ Item {
font.bold: true
}
Rectangle {
BorderSurface {
id: trigger
width: parent.width
height: root.rowHeight
@@ -108,10 +109,10 @@ Item {
readonly property bool _focused: trigger.activeFocus
readonly property bool _hot: triggerHover.hovered || root.hasCursor
readonly property var _borderSpec: Border.controlSpec(trigger._focused ? "focus" : (trigger._hot ? "hover-cursor" : "normal"), root.foreground, root.accent)
color: Style.controlFill(trigger._focused, trigger._hot, root.foreground, root.accent)
border.color: Style.controlBorder(trigger._focused, trigger._hot, root.foreground, root.accent)
border.width: Style.controlBorderWidth(trigger._focused, trigger._hot)
borderSpec: _borderSpec
activeFocusOnTab: true
@@ -134,8 +135,8 @@ Item {
anchors.left: parent.left
anchors.right: chevron.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.spacing.controlPaddingX
anchors.rightMargin: Style.spacing.md
anchors.leftMargin: trigger.borderLeft + Style.spacing.controlPaddingX
anchors.rightMargin: trigger.borderRight + Style.spacing.md
text: root.currentLabel() || root.triggerLabel || root.placeholderText
color: (root.currentLabel() || root.triggerLabel) ? root.foreground : Qt.darker(root.foreground, 1.5)
font.family: root.fontFamily
@@ -147,7 +148,7 @@ Item {
id: chevron
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Style.spacing.controlGap
anchors.rightMargin: trigger.borderRight + Style.spacing.controlGap
text: "󰅀"
color: Qt.darker(root.foreground, 1.2)
font.family: root.fontFamily
@@ -172,12 +173,15 @@ Item {
Math.min(resultList.contentHeight + Style.space(50),
root.popupRowHeight * 6 + 5 * Style.spacing.labelGap + Style.space(50)))
padding: Style.spacing.hairline
leftPadding: Border.left(root.popupBorderSpec) + Style.spacing.hairline
rightPadding: Border.right(root.popupBorderSpec) + Style.spacing.hairline
topPadding: Border.top(root.popupBorderSpec) + Style.spacing.hairline
bottomPadding: Border.bottom(root.popupBorderSpec) + Style.spacing.hairline
focus: true
background: Rectangle {
background: BorderSurface {
color: root.background
border.color: root.popupBorder
border.width: Style.normalBorderWidth
borderSpec: root.popupBorderSpec
radius: Style.cornerRadius
}
+7 -7
View File
@@ -34,6 +34,7 @@ TextField {
readonly property bool _focused: activeFocus
readonly property bool _hot: hovered || hasCursor
readonly property var _borderSpec: Border.controlSpec(_focused ? "focus" : (_hot ? "hover-cursor" : "normal"), root.foreground, root.accent)
echoMode: password ? TextInput.Password : TextInput.Normal
font.family: Style.font.family
@@ -43,15 +44,14 @@ TextField {
selectedTextColor: foreground
placeholderTextColor: Qt.darker(foreground, 1.6)
leftPadding: horizontalPadding
rightPadding: horizontalPadding
topPadding: verticalPadding
bottomPadding: verticalPadding
leftPadding: horizontalPadding + Border.left(_borderSpec)
rightPadding: horizontalPadding + Border.right(_borderSpec)
topPadding: verticalPadding + Border.top(_borderSpec)
bottomPadding: verticalPadding + Border.bottom(_borderSpec)
background: Rectangle {
background: BorderSurface {
color: Style.controlFill(root._focused, root._hot, root.foreground, root.accent)
border.color: Style.controlBorder(root._focused, root._hot, root.foreground, root.accent)
border.width: Style.controlBorderWidth(root._focused, root._hot)
borderSpec: root._borderSpec
radius: Style.cornerRadius
}
}
+9 -10
View File
@@ -12,7 +12,7 @@ import qs.Commons
// `rounded` auto-detects from Style.cornerRadius so the switch follows
// the theme: pill shape when Hyprland corners are rounded, square on sharp.
// Callers can override per-instance.
Rectangle {
BorderSurface {
id: root
property string label: ""
@@ -52,10 +52,10 @@ Rectangle {
radius: Style.cornerRadius
readonly property bool _hot: hasCursor || mouse.containsMouse
readonly property var _borderSpec: Border.controlSpec(activeFocus ? "focus" : (_hot ? "hover-cursor" : "normal"), foreground, accent)
color: Style.controlFill(activeFocus, _hot, foreground, accent)
border.color: Style.controlBorder(activeFocus, _hot, foreground, accent)
border.width: Style.controlBorderWidth(activeFocus, _hot)
borderSpec: _borderSpec
Behavior on color { ColorAnimation { duration: 100 } }
@@ -64,8 +64,8 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.spacing.rowPaddingX
anchors.rightMargin: Style.spacing.rowPaddingX
anchors.leftMargin: root.borderLeft + Style.spacing.rowPaddingX
anchors.rightMargin: root.borderRight + Style.spacing.rowPaddingX
spacing: Style.spacing.rowPaddingX
Column {
@@ -94,7 +94,7 @@ Rectangle {
}
}
Rectangle {
BorderSurface {
id: track
width: root.trackWidth
height: root.trackHeight
@@ -102,10 +102,9 @@ Rectangle {
color: root.checked
? Style.selectedFillFor(root.foreground, root.accent)
: Style.normalFillFor(root.foreground, root.accent)
border.color: root.checked
? Style.selectedBorderFor(root.foreground, root.accent)
: Style.normalBorderFor(root.foreground, root.accent)
border.width: root.checked ? Style.selectedBorderWidth : Style.normalBorderWidth
borderSpec: root.checked
? Border.controlSpec("selected", root.foreground, root.accent)
: Border.controlSpec("normal", root.foreground, root.accent)
anchors.verticalCenter: parent.verticalCenter
Behavior on color { ColorAnimation { duration: 120 } }
+2
View File
@@ -2,6 +2,8 @@ module qs.Ui
BarIndicator 1.0 BarIndicator.qml
BarWidget 1.0 BarWidget.qml
BorderOverlay 1.0 BorderOverlay.qml
BorderSurface 1.0 BorderSurface.qml
Button 1.0 Button.qml
ButtonGroup 1.0 ButtonGroup.qml
ConfirmDialog 1.0 ConfirmDialog.qml