Standardize shell UI theme tokens
This commit is contained in:
+36
-36
@@ -6,11 +6,11 @@ import qs.Commons
|
||||
// States compose independently and are applied in priority order:
|
||||
//
|
||||
// pressed (mouse down) pressed fill
|
||||
// activeFocus (Tab focus) accent ring + accent fill
|
||||
// selected accent fill + accent border
|
||||
// active foreground tint fill (highlighted)
|
||||
// hasCursor || hover hot fill
|
||||
// idle transparent or 1px border if `bordered`
|
||||
// activeFocus (Tab focus) focus fill + focus border token
|
||||
// hasCursor || hover hover-cursor fill (+ border if `bordered`)
|
||||
// selected selected fill + optional selected border
|
||||
// active selected fill
|
||||
// idle transparent or normal border if `bordered`
|
||||
//
|
||||
// All fills/borders come from `qs.Commons.Style` tokens, so themes
|
||||
// control the look via [style] in shell.toml.
|
||||
@@ -41,8 +41,8 @@ Rectangle {
|
||||
property real fontSize: Style.font.body
|
||||
property real iconSize: Style.font.icon
|
||||
property real iconRotation: 0
|
||||
property real horizontalPadding: 10
|
||||
property real verticalPadding: 6
|
||||
property real horizontalPadding: Style.spacing.controlPaddingX
|
||||
property real verticalPadding: Style.spacing.controlPaddingY
|
||||
property bool leftAlign: false
|
||||
|
||||
// Tooltip palette. Auto-rendered if tooltipText is set.
|
||||
@@ -64,32 +64,32 @@ Rectangle {
|
||||
|
||||
readonly property bool hot: mouseArea.containsMouse || hasCursor
|
||||
readonly property bool _showFocusRing: focusable && activeFocus
|
||||
readonly property color _selectedColor: Style.selectedStateColor(root.foreground, root.accent)
|
||||
|
||||
color: mouseArea.pressed ? Style.pressedFill
|
||||
: _showFocusRing ? Style.focusFillColor
|
||||
: selected ? Style.selectedAccentFill
|
||||
: hot ? Style.hotFill
|
||||
: active ? Style.selectedFill
|
||||
color: mouseArea.pressed ? Style.pressedFillFor(root.foreground, root.accent)
|
||||
: _showFocusRing ? Style.focusFillFor(root.foreground, root.accent)
|
||||
: hot ? Style.hoverFillFor(root.foreground, root.accent)
|
||||
: selected ? Style.selectedFillFor(root.foreground, root.accent)
|
||||
: active ? Style.selectedFillFor(root.foreground, root.accent)
|
||||
: background
|
||||
|
||||
// Border color follows the same precedence as fill: focus ring wins,
|
||||
// then selected, then cursor on bordered (paints accent so the chip
|
||||
// structure clearly reads as "cursor is here"), then plain bordered
|
||||
// (foreground), then nothing.
|
||||
border.color: _showFocusRing ? Style.focusBorderColor
|
||||
: selected ? accent
|
||||
: (bordered && hot) ? Style.focusBorderColor
|
||||
: bordered ? foreground
|
||||
: Style.idleBorderColor
|
||||
// Border follows the same state precedence as fill. Buttons stay
|
||||
// borderless at rest unless `bordered` is set, but hover-cursor/focus
|
||||
// always use the shared cursor border so the keyboard target is visible
|
||||
// and consistent with the rest of the kit. Selected borders are off by
|
||||
// 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"
|
||||
|
||||
// selected+hot thickens to the focus-ring width so the cursor remains
|
||||
// visible on the chosen option (otherwise selected's accent fill+border
|
||||
// masks any hot fill). bordered+hot also thickens so the chip cursor
|
||||
// reads as a deliberate state change rather than a faint tint.
|
||||
border.width: _showFocusRing ? Style.focusBorderWidth
|
||||
: selected ? (hot ? Style.focusBorderWidth : Math.max(Style.borderWidth, 2))
|
||||
: (bordered && hot) ? Style.focusBorderWidth
|
||||
: bordered ? Style.borderWidth
|
||||
: hot ? Style.hoverBorderWidth
|
||||
: selected ? (Style.selectedBorderWidth > 0 ? Style.selectedBorderWidth : (bordered ? Style.normalBorderWidth : 0))
|
||||
: bordered ? Style.normalBorderWidth
|
||||
: 0
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 120 } }
|
||||
@@ -102,7 +102,7 @@ Rectangle {
|
||||
background: Rectangle {
|
||||
color: root.tooltipBackground
|
||||
border.color: root.tooltipForeground
|
||||
border.width: 1
|
||||
border.width: Math.max(1, Style.normalBorderWidth)
|
||||
radius: 0
|
||||
opacity: 0.97
|
||||
}
|
||||
@@ -111,10 +111,10 @@ Rectangle {
|
||||
color: root.tooltipForeground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
leftPadding: 10
|
||||
rightPadding: 10
|
||||
topPadding: 6
|
||||
bottomPadding: 6
|
||||
leftPadding: Style.spacing.controlPaddingX
|
||||
rightPadding: Style.spacing.controlPaddingX
|
||||
topPadding: Style.spacing.controlPaddingY
|
||||
bottomPadding: Style.spacing.controlPaddingY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,12 +124,12 @@ Rectangle {
|
||||
anchors.left: root.leftAlign ? parent.left : undefined
|
||||
anchors.leftMargin: root.leftAlign ? root.horizontalPadding : 0
|
||||
anchors.horizontalCenter: root.leftAlign ? undefined : parent.horizontalCenter
|
||||
spacing: 8
|
||||
spacing: Style.spacing.controlGap
|
||||
|
||||
Text {
|
||||
visible: root.iconText !== ""
|
||||
text: root.iconText
|
||||
color: root.selected ? root.accent : root.foreground
|
||||
color: root.selected ? root._selectedColor : root.foreground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: root.iconSize
|
||||
rotation: root.iconRotation
|
||||
@@ -140,7 +140,7 @@ Rectangle {
|
||||
Text {
|
||||
visible: root.text !== ""
|
||||
text: root.text
|
||||
color: root.selected ? root.accent : root.foreground
|
||||
color: root.selected ? root._selectedColor : root.foreground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: root.fontSize
|
||||
font.bold: root.selected
|
||||
|
||||
+70
-12
@@ -8,10 +8,18 @@ import qs.Commons
|
||||
// `options` is either a plain string[] (label == value) or an array of
|
||||
// { value, label, icon?, tooltip? } objects. Mixing is fine.
|
||||
//
|
||||
// Panels with their own keyboard cursor model bind `cursorIndex` to the
|
||||
// currently-focused option (-1 = no cursor) and listen on `hovered` to
|
||||
// keep that state synced with the mouse. Forms that don't care about
|
||||
// the panel cursor model can leave both alone.
|
||||
// Keyboard navigation. The group itself is a single Tab stop, not one
|
||||
// stop per chip — so in a form that walks `activeFocusOnTab` items with
|
||||
// Tab / j / k, the cursor enters the group as a unit. Once focused,
|
||||
// h / l / Left / Right walks between chips and Enter / Space activates
|
||||
// the current one. The selected chip is the default landing point so
|
||||
// users see their existing choice on arrival.
|
||||
//
|
||||
// Panel-cursor consumers (the bar widget panels) drive `cursorIndex`
|
||||
// directly and listen on `hovered` to sync the mouse — Tab focus and
|
||||
// `cursorIndex` are independent; either one paints the chip's hot
|
||||
// state, and the bar widget panels never give Tab focus to a
|
||||
// ButtonGroup so they only see the cursorIndex path.
|
||||
Row {
|
||||
id: root
|
||||
|
||||
@@ -22,16 +30,24 @@ Row {
|
||||
property color accent: Color.accent
|
||||
property string fontFamily: Style.font.family
|
||||
property real fontSize: Style.font.body
|
||||
property bool focusable: false
|
||||
property bool focusable: true
|
||||
|
||||
// -1 disables the cursor highlight (the form case). Set from a panel
|
||||
// to drive Button.hasCursor on the matching index.
|
||||
// -1 disables the external cursor highlight (the panel-cursor case).
|
||||
// Driven by a containing panel; the group's own Tab-focus h/l
|
||||
// tracking is internal and lives in _focusedIndex.
|
||||
property int cursorIndex: -1
|
||||
|
||||
// Internal: which chip h / l / Left / Right is currently sitting on
|
||||
// when the group itself has Tab focus. Reset to the selected option
|
||||
// each time focus arrives so the user sees their existing choice.
|
||||
property int _focusedIndex: -1
|
||||
|
||||
signal changed(string value)
|
||||
signal hovered(int index, bool isHovered)
|
||||
|
||||
spacing: 6
|
||||
spacing: Style.spacing.md
|
||||
|
||||
activeFocusOnTab: focusable
|
||||
|
||||
function optionValue(o) {
|
||||
return (o && typeof o === "object") ? String(o.value) : String(o)
|
||||
@@ -46,6 +62,46 @@ Row {
|
||||
return (o && typeof o === "object" && o.tooltip) ? String(o.tooltip) : ""
|
||||
}
|
||||
|
||||
function selectedOptionIndex() {
|
||||
for (var i = 0; i < options.length; i++)
|
||||
if (optionValue(options[i]) === value) return i
|
||||
return -1
|
||||
}
|
||||
|
||||
function activateFocused() {
|
||||
if (_focusedIndex < 0 || _focusedIndex >= options.length) return
|
||||
var v = optionValue(options[_focusedIndex])
|
||||
root.changed(v)
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus) {
|
||||
var idx = selectedOptionIndex()
|
||||
_focusedIndex = idx < 0 ? 0 : idx
|
||||
} else {
|
||||
_focusedIndex = -1
|
||||
}
|
||||
}
|
||||
|
||||
Keys.priority: Keys.BeforeItem
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Left || event.key === Qt.Key_H
|
||||
|| event.text === "h") {
|
||||
_focusedIndex = Math.max(0, (_focusedIndex < 0 ? 0 : _focusedIndex) - 1)
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Right || event.key === Qt.Key_L
|
||||
|| event.text === "l") {
|
||||
var max = options.length - 1
|
||||
var next = (_focusedIndex < 0 ? 0 : _focusedIndex) + 1
|
||||
_focusedIndex = Math.min(max, next)
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter
|
||||
|| event.key === Qt.Key_Space) {
|
||||
activateFocused()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.options
|
||||
|
||||
@@ -56,17 +112,19 @@ Row {
|
||||
iconText: root.optionIcon(modelData)
|
||||
tooltipText: root.optionTooltip(modelData)
|
||||
selected: root.optionValue(modelData) === root.value
|
||||
// Chip lights up when either the external panel cursor lands here
|
||||
// or the group has Tab focus and h/l has walked to this index.
|
||||
hasCursor: root.cursorIndex === index
|
||||
// Every chip carries an idle border so the group reads as a row of
|
||||
// distinct options. selected paints accent; the cursor recolors the
|
||||
// chip's border to accent via Button's bordered+hot path.
|
||||
|| (root.activeFocus && root._focusedIndex === index)
|
||||
// Every chip carries the standard bordered-button chrome so the
|
||||
// group reads as a row of distinct options. selected / hover-cursor /
|
||||
// focus are all painted by Button from Style's shared state tokens.
|
||||
bordered: true
|
||||
foreground: root.foreground
|
||||
background: root.background
|
||||
accent: root.accent
|
||||
fontFamily: root.fontFamily
|
||||
fontSize: root.fontSize
|
||||
focusable: root.focusable
|
||||
onClicked: root.changed(root.optionValue(modelData))
|
||||
onHovered: function(h) { root.hovered(index, h) }
|
||||
}
|
||||
|
||||
+21
-17
@@ -7,35 +7,39 @@ import qs.Commons
|
||||
// `hasCursor` / `current`. That's what guarantees a single highlight on
|
||||
// screen at any time across both keyboard and mouse interaction.
|
||||
//
|
||||
// Two cursor visuals are supported:
|
||||
//
|
||||
// default (outline: false) — paint a tinted fill across the row when
|
||||
// hasCursor is true. Use for narrow text rows (wifi networks, audio
|
||||
// devices, menu items) where fill reads cleanly.
|
||||
//
|
||||
// outline: true — paint an accent border instead of a fill. Use for
|
||||
// wide content rows where a fill would obscure the row's chrome
|
||||
// (slider rows in audio / monitor panels). The fill / currentFill
|
||||
// props are ignored in this mode.
|
||||
// Cursor paint is always the shared hover-cursor fill plus optional
|
||||
// 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 {
|
||||
id: root
|
||||
|
||||
property bool hasCursor: false
|
||||
property bool current: false
|
||||
property bool outline: false
|
||||
property bool bordered: false
|
||||
|
||||
property color foreground: Color.foreground
|
||||
property color fill: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.08)
|
||||
property color currentFill: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.18)
|
||||
property color accent: Color.accent
|
||||
property color fill: Style.hoverFillFor(foreground, accent)
|
||||
property color currentFill: Style.selectedFillFor(foreground, accent)
|
||||
|
||||
radius: Style.cornerRadius
|
||||
|
||||
color: root.outline
|
||||
? "transparent"
|
||||
: (hasCursor ? fill : (current ? currentFill : "transparent"))
|
||||
color: hasCursor ? fill : (current ? currentFill : "transparent")
|
||||
|
||||
border.color: root.outline && hasCursor ? Style.focusBorderColor : foreground
|
||||
border.width: root.outline && hasCursor ? Style.focusBorderWidth : 0
|
||||
border.color: root.hasCursor
|
||||
? Style.hoverBorderFor(root.foreground, root.accent)
|
||||
: (root.current
|
||||
? Style.selectedBorderFor(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))
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 60 }
|
||||
|
||||
+35
-26
@@ -27,12 +27,12 @@ Item {
|
||||
property color popupBorder: Color.popups.border
|
||||
property color accent: Color.accent
|
||||
property string fontFamily: Style.font.family
|
||||
property int rowHeight: 28
|
||||
property int popupRowHeight: 28
|
||||
property int rowHeight: Style.spacing.controlHeight
|
||||
property int popupRowHeight: Style.spacing.popupRowHeight
|
||||
property bool showLabel: true
|
||||
|
||||
// Panel-cursor flag. When true, the trigger renders the same focus ring
|
||||
// as Tab-focus so a panel's keyboard cursor lands here identically.
|
||||
// Panel-cursor flag. When true, the trigger renders the shared
|
||||
// hover-cursor state. Active Qt focus defaults to the same visuals.
|
||||
// Emits `hovered(bool)` on pointer enter/leave so the panel can keep
|
||||
// its cursor state in sync with the mouse.
|
||||
property bool hasCursor: false
|
||||
@@ -62,12 +62,12 @@ Item {
|
||||
return value
|
||||
}
|
||||
|
||||
implicitWidth: 240
|
||||
implicitHeight: showLabel && label !== "" ? rowHeight + 18 : rowHeight
|
||||
implicitWidth: Style.spacing.dropdownWidth
|
||||
implicitHeight: showLabel && label !== "" ? rowHeight + Style.spacing.huge : rowHeight
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: 4
|
||||
spacing: Style.spacing.labelGap
|
||||
|
||||
Text {
|
||||
visible: root.showLabel && root.label !== ""
|
||||
@@ -84,18 +84,27 @@ Item {
|
||||
height: root.rowHeight
|
||||
radius: Style.cornerRadius
|
||||
|
||||
readonly property bool _focused: trigger.activeFocus || root.hasCursor
|
||||
readonly property bool _focused: trigger.activeFocus
|
||||
readonly property bool _hot: triggerHover.hovered || root.hasCursor
|
||||
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b,
|
||||
trigger._focused ? 0.08 : 0.04)
|
||||
color: trigger._focused
|
||||
? Style.focusFillFor(root.foreground, root.accent)
|
||||
: (trigger._hot
|
||||
? Style.hoverFillFor(root.foreground, root.accent)
|
||||
: Style.normalFillFor(root.foreground, root.accent))
|
||||
border.color: trigger._focused
|
||||
? Style.focusBorderColor
|
||||
: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.4)
|
||||
border.width: trigger._focused ? Style.focusBorderWidth : 1
|
||||
? Style.focusBorderFor(root.foreground, root.accent)
|
||||
: (trigger._hot
|
||||
? Style.hoverBorderFor(root.foreground, root.accent)
|
||||
: Style.normalBorderFor(root.foreground, root.accent))
|
||||
border.width: trigger._focused
|
||||
? Style.focusBorderWidth
|
||||
: (trigger._hot ? Style.hoverBorderWidth : Style.normalBorderWidth)
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
HoverHandler {
|
||||
id: triggerHover
|
||||
onHoveredChanged: root.hovered(hovered)
|
||||
}
|
||||
|
||||
@@ -113,8 +122,8 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: chevron.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 6
|
||||
anchors.leftMargin: Style.spacing.controlPaddingX
|
||||
anchors.rightMargin: Style.spacing.md
|
||||
text: root.currentLabel()
|
||||
color: root.foreground
|
||||
font.family: root.fontFamily
|
||||
@@ -126,7 +135,7 @@ Item {
|
||||
id: chevron
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: 8
|
||||
anchors.rightMargin: Style.spacing.controlGap
|
||||
text: ""
|
||||
color: Qt.darker(root.foreground, 1.2)
|
||||
font.family: root.fontFamily
|
||||
@@ -145,17 +154,17 @@ Item {
|
||||
Popup {
|
||||
id: popup
|
||||
x: 0
|
||||
y: trigger.height + 2
|
||||
y: trigger.height + Style.spacing.xxs
|
||||
width: trigger.width
|
||||
implicitHeight: Math.min(root.options.length * root.popupRowHeight + Math.max(0, root.options.length - 1) * 4 + 2,
|
||||
root.popupRowHeight * 8 + 7 * 4 + 2)
|
||||
padding: 1
|
||||
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
|
||||
focus: true
|
||||
|
||||
background: Rectangle {
|
||||
color: root.background
|
||||
border.color: root.popupBorder
|
||||
border.width: 1
|
||||
border.width: Style.normalBorderWidth
|
||||
radius: Style.cornerRadius
|
||||
}
|
||||
|
||||
@@ -166,7 +175,7 @@ Item {
|
||||
|
||||
contentItem: ListView {
|
||||
id: optionList
|
||||
spacing: 4
|
||||
spacing: Style.spacing.labelGap
|
||||
|
||||
Keys.priority: Keys.BeforeItem
|
||||
Keys.onPressed: function(event) {
|
||||
@@ -207,17 +216,17 @@ Item {
|
||||
width: optionList.width
|
||||
height: root.popupRowHeight
|
||||
color: index === optionList.currentIndex
|
||||
? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.14)
|
||||
? Style.hoverFillFor(root.foreground, root.accent)
|
||||
: "transparent"
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
anchors.leftMargin: Style.spacing.controlPaddingX
|
||||
anchors.rightMargin: Style.spacing.controlPaddingX
|
||||
text: root.optionLabel(modelData)
|
||||
color: index === optionList.currentIndex ? root.accent : root.foreground
|
||||
color: index === optionList.currentIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.body
|
||||
elide: Text.ElideRight
|
||||
|
||||
@@ -38,12 +38,12 @@ PanelWindow {
|
||||
required property Item anchorItem
|
||||
required property QtObject bar
|
||||
property var owner: null
|
||||
property int margin: 10
|
||||
property int padding: 14
|
||||
property int margin: Style.gapsOut
|
||||
property int padding: Style.spacing.popupPadding
|
||||
property int contentWidth: 280
|
||||
property int contentHeight: 200
|
||||
property bool open: false
|
||||
property int gap: 10 // distance between bar edge and panel
|
||||
property int gap: Style.gapsOut // distance between bar edge and panel
|
||||
|
||||
default property alias contentItem: contentHolder.children
|
||||
|
||||
@@ -123,6 +123,32 @@ PanelWindow {
|
||||
readonly property real anchorH: anchorItem ? anchorItem.height : 0
|
||||
readonly property real screenW: screen ? screen.width : 0
|
||||
readonly property real screenH: screen ? screen.height : 0
|
||||
readonly property real availableCardWidth: screenW > 0
|
||||
? Math.max(120, screenW - ((barPos === "left" || barPos === "right") ? barW + gap + margin : margin * 2))
|
||||
: 0
|
||||
readonly property real availableCardHeight: screenH > 0
|
||||
? Math.max(120, screenH - ((barPos === "top" || barPos === "bottom") ? barH + gap + margin : margin * 2))
|
||||
: 0
|
||||
|
||||
function fittedContentWidth(width, cap) {
|
||||
var desired = Math.max(1, Number(width) || 1)
|
||||
var maxWidth = root.availableCardWidth > 0 ? root.availableCardWidth : desired
|
||||
if (cap !== undefined && Number(cap) > 0) maxWidth = Math.min(maxWidth, Number(cap))
|
||||
return Math.round(Math.min(desired, maxWidth))
|
||||
}
|
||||
|
||||
function fittedContentHeight(implicitHeight, cap) {
|
||||
var desired = Math.max(root.padding * 2, (Number(implicitHeight) || 0) + root.padding * 2)
|
||||
var maxHeight = root.availableCardHeight > 0 ? root.availableCardHeight : desired
|
||||
if (cap !== undefined && Number(cap) > 0) maxHeight = Math.min(maxHeight, Number(cap))
|
||||
return Math.round(Math.min(desired, maxHeight))
|
||||
}
|
||||
|
||||
function cappedContentHeight(height) {
|
||||
var desired = Math.max(root.padding * 2, Number(height) || root.padding * 2)
|
||||
var maxHeight = root.availableCardHeight > 0 ? root.availableCardHeight : desired
|
||||
return Math.round(Math.min(desired, maxHeight))
|
||||
}
|
||||
|
||||
// Desired top-left of the card in screen coordinates. For the
|
||||
// perpendicular axis (away-from-bar) we anchor to the bar window's edge
|
||||
@@ -192,7 +218,7 @@ PanelWindow {
|
||||
height: root.contentHeight
|
||||
color: Color.popups.background
|
||||
border.color: Color.popups.border
|
||||
border.width: 2
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
radius: Style.cornerRadius
|
||||
opacity: root.open ? 1.0 : 0
|
||||
Behavior on opacity {
|
||||
|
||||
+25
-10
@@ -14,14 +14,15 @@ Column {
|
||||
property color accent: Color.accent
|
||||
property string fontFamily: Style.font.family
|
||||
property real fontSize: Style.font.body
|
||||
property real fieldWidth: 120
|
||||
property real fieldWidth: Style.spacing.numberFieldWidth
|
||||
property bool hasCursor: false
|
||||
property bool _hovered: false
|
||||
property alias field: spin
|
||||
|
||||
signal modified(int value)
|
||||
signal hovered(bool on)
|
||||
|
||||
spacing: 6
|
||||
spacing: Style.spacing.md
|
||||
|
||||
Text {
|
||||
visible: root.label !== ""
|
||||
@@ -34,6 +35,7 @@ Column {
|
||||
QQC.SpinBox {
|
||||
id: spin
|
||||
width: root.fieldWidth
|
||||
implicitHeight: Math.max(Style.spacing.controlHeight, root.fontSize + Style.spacing.controlPaddingY * 2)
|
||||
from: root.from
|
||||
to: root.to
|
||||
stepSize: root.stepSize
|
||||
@@ -45,16 +47,29 @@ Column {
|
||||
onValueModified: root.modified(value)
|
||||
|
||||
background: Rectangle {
|
||||
readonly property bool _hot: spin.activeFocus || (root.hasCursor && !spin.activeFocus)
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, _hot ? 0.10 : 0.05)
|
||||
border.color: _hot
|
||||
? Style.focusBorderColor
|
||||
: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.3)
|
||||
border.width: _hot ? Style.focusBorderWidth : 1
|
||||
readonly property bool _focused: spin.activeFocus
|
||||
readonly property bool _hot: root._hovered || root.hasCursor
|
||||
|
||||
color: _focused
|
||||
? Style.focusFillFor(root.foreground, root.accent)
|
||||
: (_hot
|
||||
? Style.hoverFillFor(root.foreground, root.accent)
|
||||
: Style.normalFillFor(root.foreground, root.accent))
|
||||
border.color: _focused
|
||||
? Style.focusBorderFor(root.foreground, root.accent)
|
||||
: (_hot
|
||||
? Style.hoverBorderFor(root.foreground, root.accent)
|
||||
: Style.normalBorderFor(root.foreground, root.accent))
|
||||
border.width: _focused
|
||||
? Style.focusBorderWidth
|
||||
: (_hot ? Style.hoverBorderWidth : Style.normalBorderWidth)
|
||||
radius: Style.cornerRadius
|
||||
|
||||
HoverHandler {
|
||||
onHoveredChanged: root.hovered(hovered)
|
||||
onHoveredChanged: {
|
||||
root._hovered = hovered
|
||||
root.hovered(hovered)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +77,7 @@ Column {
|
||||
text: spin.displayText
|
||||
font: spin.font
|
||||
color: root.foreground
|
||||
selectionColor: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.35)
|
||||
selectionColor: Style.selectionFillFor(root.foreground, root.accent)
|
||||
selectedTextColor: root.foreground
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
|
||||
@@ -13,13 +13,13 @@ import qs.Commons
|
||||
// here because action buttons are not cursor targets — the row they live
|
||||
// in is.
|
||||
//
|
||||
// Set `focusable: true` to make the button keyboard-tabbable with an
|
||||
// accent focus ring (Style.focusBorderColor / FillColor / Width). Use this
|
||||
// Set `focusable: true` to make the button keyboard-tabbable with the
|
||||
// shared hover-cursor/focus tokens. Use this
|
||||
// in form contexts (the bar settings widget cards) where Tab walks a list
|
||||
// of controls; leave it false for the right-edge actions on panel rows
|
||||
// where the row's CursorSurface owns the keyboard cursor.
|
||||
//
|
||||
// Set `hasCursor: true` to have the button render the same fill as a
|
||||
// Set `hasCursor: true` to have the button render the same hover state as
|
||||
// mouse hover — so a panel's keyboard cursor lands on it identically.
|
||||
// Use this when a PanelActionButton is itself the cursor target (rather
|
||||
// than living inside a CursorSurface row). Emits `hovered(bool)` on
|
||||
@@ -34,10 +34,11 @@ Rectangle {
|
||||
property color panelBackground: Color.background
|
||||
property string fontFamily: Style.font.family
|
||||
property real fontSize: Style.font.icon
|
||||
property real size: 22
|
||||
property real size: Math.max(Style.space(22), fontSize + Style.spacing.sm * 2)
|
||||
|
||||
property bool focusable: false
|
||||
property bool hasCursor: false
|
||||
property bool bordered: false
|
||||
|
||||
signal clicked()
|
||||
signal hovered(bool isHovered)
|
||||
@@ -55,12 +56,20 @@ Rectangle {
|
||||
readonly property bool _hot: (mouse.containsMouse || root.hasCursor) && root.enabled
|
||||
|
||||
color: _showFocusRing
|
||||
? Style.focusFillColor
|
||||
? Style.focusFillFor(hoverColor, hoverColor)
|
||||
: (_hot
|
||||
? Qt.rgba(hoverColor.r, hoverColor.g, hoverColor.b, 0.20)
|
||||
? Style.hoverFillFor(hoverColor, hoverColor)
|
||||
: "transparent")
|
||||
border.width: _showFocusRing ? Style.focusBorderWidth : 0
|
||||
border.color: _showFocusRing ? Style.focusBorderColor : "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))
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 60 } }
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -9,11 +10,12 @@ Item {
|
||||
property real maximum: 1
|
||||
property real step: 0.05
|
||||
property bool integer: false
|
||||
property color trackColor: bar ? Qt.rgba(bar.foreground.r, bar.foreground.g, bar.foreground.b, 0.18) : "#333"
|
||||
property color trackColor: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "#333"
|
||||
property color fillColor: bar ? bar.foreground : "#cacccc"
|
||||
property color knobColor: bar ? bar.foreground : "#cacccc"
|
||||
property bool dragging: false
|
||||
property real trackHeight: 4
|
||||
property real trackHeight: Math.max(4, Math.round(Style.spacing.controlHeight * 0.11))
|
||||
property real knobSize: Math.max(14, Math.round(Style.spacing.controlHeight * 0.38))
|
||||
property real liveValue: value
|
||||
|
||||
onValueChanged: if (!dragging) liveValue = value
|
||||
@@ -21,8 +23,8 @@ Item {
|
||||
signal moved(real value)
|
||||
signal released(real value)
|
||||
|
||||
implicitWidth: 200
|
||||
implicitHeight: 22
|
||||
implicitWidth: Style.space(200)
|
||||
implicitHeight: Math.max(Style.space(22), knobSize + Style.spacing.md)
|
||||
|
||||
readonly property real range: Math.max(0.0001, maximum - minimum)
|
||||
readonly property real progress: Math.max(0, Math.min(1, (liveValue - minimum) / range))
|
||||
@@ -54,12 +56,12 @@ Item {
|
||||
|
||||
Rectangle {
|
||||
id: knob
|
||||
width: 14
|
||||
height: 14
|
||||
radius: 7
|
||||
width: root.knobSize
|
||||
height: root.knobSize
|
||||
radius: root.knobSize / 2
|
||||
color: root.knobColor
|
||||
border.color: root.bar ? root.bar.background : "#101315"
|
||||
border.width: 2
|
||||
border.width: 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: mouseArea.containsMouse || root.dragging ? 1.15 : 1.0
|
||||
|
||||
@@ -27,8 +27,8 @@ ToolTip {
|
||||
|
||||
background: Rectangle {
|
||||
color: root.panelBackground
|
||||
border.color: root.panelForeground
|
||||
border.width: 1
|
||||
border.color: Style.normalBorderFor(root.panelForeground, Color.accent)
|
||||
border.width: Style.normalBorderWidth
|
||||
radius: 0
|
||||
opacity: 0.97
|
||||
}
|
||||
@@ -38,9 +38,9 @@ ToolTip {
|
||||
color: root.panelForeground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: root.fontSize
|
||||
leftPadding: 10
|
||||
rightPadding: 10
|
||||
topPadding: 6
|
||||
bottomPadding: 6
|
||||
leftPadding: Style.spacing.controlPaddingX
|
||||
rightPadding: Style.spacing.controlPaddingX
|
||||
topPadding: Style.spacing.controlPaddingY
|
||||
bottomPadding: Style.spacing.controlPaddingY
|
||||
}
|
||||
}
|
||||
|
||||
+34
-3
@@ -9,8 +9,8 @@ PopupWindow {
|
||||
required property Item anchorItem
|
||||
required property QtObject bar
|
||||
property var owner: null
|
||||
property int margin: 10
|
||||
property int padding: 14
|
||||
property int margin: Style.gapsOut
|
||||
property int padding: Style.spacing.popupPadding
|
||||
property int contentWidth: 280
|
||||
property int contentHeight: 200
|
||||
property color borderColor: Color.popups.border
|
||||
@@ -22,7 +22,38 @@ PopupWindow {
|
||||
|
||||
readonly property var coordinatorKey: owner || root
|
||||
readonly property var anchorWindow: anchorItem ? anchorItem.QsWindow.window : null
|
||||
readonly property var popupScreen: anchorWindow ? anchorWindow.screen : null
|
||||
readonly property bool containsMouse: cardHover.hovered
|
||||
readonly property real screenW: popupScreen ? popupScreen.width : 0
|
||||
readonly property real screenH: popupScreen ? popupScreen.height : 0
|
||||
readonly property real barW: anchorWindow ? anchorWindow.width : 0
|
||||
readonly property real barH: anchorWindow ? anchorWindow.height : 0
|
||||
readonly property real availableCardWidth: screenW > 0
|
||||
? Math.max(120, screenW - ((bar && (bar.position === "left" || bar.position === "right")) ? barW : 0) - root.margin * 2)
|
||||
: 0
|
||||
readonly property real availableCardHeight: screenH > 0
|
||||
? Math.max(120, screenH - ((bar && (bar.position === "top" || bar.position === "bottom")) ? barH : 0) - root.margin * 2)
|
||||
: 0
|
||||
|
||||
function fittedContentWidth(width, cap) {
|
||||
var desired = Math.max(1, Number(width) || 1)
|
||||
var maxWidth = root.availableCardWidth > 0 ? root.availableCardWidth : desired
|
||||
if (cap !== undefined && Number(cap) > 0) maxWidth = Math.min(maxWidth, Number(cap))
|
||||
return Math.round(Math.min(desired, maxWidth))
|
||||
}
|
||||
|
||||
function fittedContentHeight(implicitHeight, cap) {
|
||||
var desired = Math.max(root.padding * 2, (Number(implicitHeight) || 0) + root.padding * 2)
|
||||
var maxHeight = root.availableCardHeight > 0 ? root.availableCardHeight : desired
|
||||
if (cap !== undefined && Number(cap) > 0) maxHeight = Math.min(maxHeight, Number(cap))
|
||||
return Math.round(Math.min(desired, maxHeight))
|
||||
}
|
||||
|
||||
function cappedContentHeight(height) {
|
||||
var desired = Math.max(root.padding * 2, Number(height) || root.padding * 2)
|
||||
var maxHeight = root.availableCardHeight > 0 ? root.availableCardHeight : desired
|
||||
return Math.round(Math.min(desired, maxHeight))
|
||||
}
|
||||
|
||||
function closePopout() {
|
||||
if (owner && "closePopout" in owner) owner.closePopout()
|
||||
@@ -119,7 +150,7 @@ PopupWindow {
|
||||
anchors.fill: parent
|
||||
color: Color.popups.background
|
||||
border.color: root.borderColor
|
||||
border.width: 2
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
radius: Style.cornerRadius
|
||||
opacity: root.open ? 1.0 : 0
|
||||
|
||||
|
||||
@@ -30,13 +30,13 @@ Item {
|
||||
property color popupBorder: Color.popups.border
|
||||
property color accent: Color.accent
|
||||
property string fontFamily: Style.font.family
|
||||
property int rowHeight: 28
|
||||
property int popupRowHeight: 28
|
||||
property int popupMinHeight: 220
|
||||
property int rowHeight: Style.spacing.controlHeight
|
||||
property int popupRowHeight: Style.spacing.popupRowHeight
|
||||
property int popupMinHeight: Style.spacing.searchablePopupMinHeight
|
||||
property bool showLabel: true
|
||||
|
||||
// Panel-cursor flag. When true, the trigger renders the same focus ring
|
||||
// as Tab-focus so a panel's keyboard cursor lands here identically.
|
||||
// Panel-cursor flag. When true, the trigger renders the shared
|
||||
// hover-cursor state. Active Qt focus defaults to the same visuals.
|
||||
// Emits `hovered(bool)` on pointer enter/leave so the panel can keep
|
||||
// its cursor state in sync with the mouse.
|
||||
property bool hasCursor: false
|
||||
@@ -84,12 +84,12 @@ Item {
|
||||
|
||||
onOptionsChanged: recomputeFiltered()
|
||||
|
||||
implicitWidth: 260
|
||||
implicitHeight: showLabel && label !== "" ? rowHeight + 18 : rowHeight
|
||||
implicitWidth: Style.spacing.searchableDropdownWidth
|
||||
implicitHeight: showLabel && label !== "" ? rowHeight + Style.spacing.huge : rowHeight
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: 4
|
||||
spacing: Style.spacing.labelGap
|
||||
|
||||
Text {
|
||||
visible: root.showLabel && root.label !== ""
|
||||
@@ -106,18 +106,27 @@ Item {
|
||||
height: root.rowHeight
|
||||
radius: Style.cornerRadius
|
||||
|
||||
readonly property bool _focused: trigger.activeFocus || root.hasCursor
|
||||
readonly property bool _focused: trigger.activeFocus
|
||||
readonly property bool _hot: triggerHover.hovered || root.hasCursor
|
||||
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b,
|
||||
trigger._focused ? 0.08 : 0.04)
|
||||
color: trigger._focused
|
||||
? Style.focusFillFor(root.foreground, root.accent)
|
||||
: (trigger._hot
|
||||
? Style.hoverFillFor(root.foreground, root.accent)
|
||||
: Style.normalFillFor(root.foreground, root.accent))
|
||||
border.color: trigger._focused
|
||||
? Style.focusBorderColor
|
||||
: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.4)
|
||||
border.width: trigger._focused ? Style.focusBorderWidth : 1
|
||||
? Style.focusBorderFor(root.foreground, root.accent)
|
||||
: (trigger._hot
|
||||
? Style.hoverBorderFor(root.foreground, root.accent)
|
||||
: Style.normalBorderFor(root.foreground, root.accent))
|
||||
border.width: trigger._focused
|
||||
? Style.focusBorderWidth
|
||||
: (trigger._hot ? Style.hoverBorderWidth : Style.normalBorderWidth)
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
HoverHandler {
|
||||
id: triggerHover
|
||||
onHoveredChanged: root.hovered(hovered)
|
||||
}
|
||||
|
||||
@@ -135,8 +144,8 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: chevron.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 6
|
||||
anchors.leftMargin: Style.spacing.controlPaddingX
|
||||
anchors.rightMargin: 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
|
||||
@@ -148,7 +157,7 @@ Item {
|
||||
id: chevron
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: 8
|
||||
anchors.rightMargin: Style.spacing.controlGap
|
||||
text: ""
|
||||
color: Qt.darker(root.foreground, 1.2)
|
||||
font.family: root.fontFamily
|
||||
@@ -167,18 +176,18 @@ Item {
|
||||
QQC.Popup {
|
||||
id: popup
|
||||
x: 0
|
||||
y: trigger.height + 2
|
||||
y: trigger.height + Style.spacing.xxs
|
||||
width: trigger.width
|
||||
implicitHeight: Math.max(root.popupMinHeight,
|
||||
Math.min(resultList.contentHeight + 50,
|
||||
root.popupRowHeight * 6 + 5 * 4 + 50))
|
||||
padding: 1
|
||||
Math.min(resultList.contentHeight + Style.space(50),
|
||||
root.popupRowHeight * 6 + 5 * Style.spacing.labelGap + Style.space(50)))
|
||||
padding: Style.spacing.hairline
|
||||
focus: true
|
||||
|
||||
background: Rectangle {
|
||||
color: root.background
|
||||
border.color: root.popupBorder
|
||||
border.width: 1
|
||||
border.width: Style.normalBorderWidth
|
||||
radius: Style.cornerRadius
|
||||
}
|
||||
|
||||
@@ -193,13 +202,14 @@ Item {
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
id: searchHeader
|
||||
width: parent.width
|
||||
height: 38
|
||||
height: root.popupRowHeight + Style.spacing.controlPaddingX
|
||||
|
||||
TextField {
|
||||
id: searchField
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
anchors.margins: Style.spacing.md
|
||||
placeholderText: root.placeholderText
|
||||
foreground: root.foreground
|
||||
accent: root.accent
|
||||
@@ -234,12 +244,12 @@ Item {
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
color: Style.alpha(root.foreground, 0.10)
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: popup.height - 38 - 2 - 1
|
||||
height: popup.height - searchHeader.height - Style.spacing.xxs - 1
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -253,7 +263,7 @@ Item {
|
||||
ListView {
|
||||
id: resultList
|
||||
anchors.fill: parent
|
||||
spacing: 4
|
||||
spacing: Style.spacing.labelGap
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
model: root.filtered
|
||||
@@ -294,9 +304,9 @@ Item {
|
||||
required property var modelData
|
||||
required property int index
|
||||
width: resultList.width
|
||||
height: Math.max(root.popupRowHeight, rowContent.implicitHeight + 12)
|
||||
height: Math.max(root.popupRowHeight, rowContent.implicitHeight + Style.spacing.rowPaddingX)
|
||||
color: index === resultList.currentIndex
|
||||
? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.14)
|
||||
? Style.hoverFillFor(root.foreground, root.accent)
|
||||
: "transparent"
|
||||
|
||||
Column {
|
||||
@@ -304,13 +314,13 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
spacing: 2
|
||||
anchors.leftMargin: Style.spacing.controlPaddingX
|
||||
anchors.rightMargin: Style.spacing.controlPaddingX
|
||||
spacing: Style.spacing.xxs
|
||||
|
||||
Text {
|
||||
text: root.optionLabel(modelData)
|
||||
color: index === resultList.currentIndex ? root.accent : root.foreground
|
||||
color: index === resultList.currentIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.body
|
||||
elide: Text.ElideRight
|
||||
|
||||
+20
-14
@@ -9,9 +9,8 @@ import qs.Commons
|
||||
//
|
||||
// Defaults bind to qs.Commons.Color so a caller with no theme overrides
|
||||
// just works; foreground / accent / selectionTint can be overridden per
|
||||
// instance. Focus styling uses Style.focusBorderColor (the same accent
|
||||
// ring Toggle and Button paint) so keyboard cursor and form focus
|
||||
// chrome stay consistent across the shell.
|
||||
// instance. activeFocus and mouse hover / panel cursor use the same
|
||||
// hover-cursor defaults, so text inputs match Button, Toggle, and Dropdown.
|
||||
//
|
||||
// Sizing is driven by font.pixelSize + verticalPadding. The default 30px
|
||||
// implicitHeight fits dialog forms; inline callers (wifi's row-embedded
|
||||
@@ -21,20 +20,20 @@ TextField {
|
||||
|
||||
property color foreground: Color.foreground
|
||||
property color accent: Color.accent
|
||||
property color selectionTint: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.35)
|
||||
property color selectionTint: Style.selectionFillFor(foreground, accent)
|
||||
property bool password: false
|
||||
property real horizontalPadding: 10
|
||||
property real verticalPadding: 7
|
||||
property real horizontalPadding: Style.spacing.controlPaddingX
|
||||
property real verticalPadding: Style.spacing.inputPaddingY
|
||||
|
||||
// Panel-cursor flag. When true (and the field isn't already focused),
|
||||
// the background paints the same accent ring as activeFocus so the
|
||||
// panel's keyboard cursor lands here identically to a mouse hover.
|
||||
// the background paints the shared hover/cursor state.
|
||||
// For mouse-enter/leave the consumer reads QQC TextField's inherited
|
||||
// `hovered` property (via onHoveredChanged) — we don't add a sibling
|
||||
// signal because the inherited property would shadow it.
|
||||
property bool hasCursor: false
|
||||
|
||||
readonly property bool _focused: activeFocus || hasCursor
|
||||
readonly property bool _focused: activeFocus
|
||||
readonly property bool _hot: hovered || hasCursor
|
||||
|
||||
echoMode: password ? TextInput.Password : TextInput.Normal
|
||||
font.family: Style.font.family
|
||||
@@ -50,12 +49,19 @@ TextField {
|
||||
bottomPadding: verticalPadding
|
||||
|
||||
background: Rectangle {
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b,
|
||||
root._focused ? 0.08 : 0.04)
|
||||
color: root._focused
|
||||
? Style.focusFillFor(root.foreground, root.accent)
|
||||
: (root._hot
|
||||
? Style.hoverFillFor(root.foreground, root.accent)
|
||||
: Style.normalFillFor(root.foreground, root.accent))
|
||||
border.color: root._focused
|
||||
? Style.focusBorderColor
|
||||
: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18)
|
||||
border.width: root._focused ? Style.focusBorderWidth : 1
|
||||
? Style.focusBorderFor(root.foreground, root.accent)
|
||||
: (root._hot
|
||||
? Style.hoverBorderFor(root.foreground, root.accent)
|
||||
: Style.normalBorderFor(root.foreground, root.accent))
|
||||
border.width: root._focused
|
||||
? Style.focusBorderWidth
|
||||
: (root._hot ? Style.hoverBorderWidth : Style.normalBorderWidth)
|
||||
radius: Style.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
+40
-30
@@ -6,10 +6,8 @@ import qs.Commons
|
||||
// flip `checked` in response (the component is stateless about the actual
|
||||
// value so it composes cleanly with model-driven UI).
|
||||
//
|
||||
// Cursor and focus styling match the rest of the kit: hasCursor (panel
|
||||
// keyboard cursor / mouse hover) is a fill only, mirroring CursorSurface;
|
||||
// activeFocus (Tab focus) adds the accent border ring on top via the
|
||||
// shared Style tokens.
|
||||
// Cursor and focus styling match the rest of the kit: hasCursor / mouse
|
||||
// hover and activeFocus share the hover-cursor defaults.
|
||||
//
|
||||
// `rounded` auto-detects from Style.cornerRadius so the switch follows
|
||||
// the theme: pill shape on round-corners themes, square on sharp.
|
||||
@@ -23,8 +21,7 @@ Rectangle {
|
||||
|
||||
// Panel-cursor flag. Same role as Button.hasCursor:
|
||||
// panels with their own keyboard cursor bind this to drive the highlight
|
||||
// separately from activeFocus. Renders as a tinted fill only — the accent
|
||||
// border ring is reserved for Tab focus (activeFocus).
|
||||
// separately from activeFocus. Visuals use the same hover-cursor tokens.
|
||||
property bool hasCursor: false
|
||||
|
||||
// Switch shape follows the theme by default: pill on round, square on sharp.
|
||||
@@ -45,17 +42,30 @@ Rectangle {
|
||||
Keys.onEnterPressed: root.clicked()
|
||||
Keys.onSpacePressed: root.clicked()
|
||||
|
||||
implicitHeight: Math.max(54, content.implicitHeight + 18)
|
||||
implicitWidth: 240
|
||||
readonly property int trackHeight: Math.max(22, Math.round(Style.spacing.controlHeight * 0.55))
|
||||
readonly property int trackWidth: Math.max(42, Math.round(trackHeight * 1.9))
|
||||
readonly property int knobSize: Math.max(16, Math.round(trackHeight * 0.72))
|
||||
readonly property int knobInset: Math.max(2, Math.round((trackHeight - knobSize) / 2))
|
||||
|
||||
implicitHeight: Math.max(54, content.implicitHeight + Style.spacing.huge)
|
||||
implicitWidth: Style.space(240)
|
||||
radius: Style.cornerRadius
|
||||
|
||||
readonly property bool _hot: hasCursor || mouse.containsMouse
|
||||
|
||||
color: activeFocus
|
||||
? Style.focusFillColor
|
||||
: ((hasCursor || mouse.containsMouse) ? Qt.rgba(foreground.r, foreground.g, foreground.b, 0.08) : Qt.rgba(foreground.r, foreground.g, foreground.b, 0.03))
|
||||
? Style.focusFillFor(foreground, accent)
|
||||
: (_hot
|
||||
? Style.hoverFillFor(foreground, accent)
|
||||
: Style.normalFillFor(foreground, accent))
|
||||
border.color: activeFocus
|
||||
? Style.focusBorderColor
|
||||
: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.12)
|
||||
border.width: activeFocus ? Style.focusBorderWidth : 1
|
||||
? Style.focusBorderFor(foreground, accent)
|
||||
: (_hot
|
||||
? Style.hoverBorderFor(foreground, accent)
|
||||
: Style.normalBorderFor(foreground, accent))
|
||||
border.width: activeFocus
|
||||
? Style.focusBorderWidth
|
||||
: (_hot ? Style.hoverBorderWidth : Style.normalBorderWidth)
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
|
||||
@@ -64,13 +74,13 @@ Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
spacing: 12
|
||||
anchors.leftMargin: Style.spacing.rowPaddingX
|
||||
anchors.rightMargin: Style.spacing.rowPaddingX
|
||||
spacing: Style.spacing.rowPaddingX
|
||||
|
||||
Column {
|
||||
width: parent.width - track.width - parent.spacing
|
||||
spacing: 3
|
||||
spacing: Style.spacing.xs
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
@@ -96,27 +106,27 @@ Rectangle {
|
||||
|
||||
Rectangle {
|
||||
id: track
|
||||
width: 42
|
||||
height: 22
|
||||
width: root.trackWidth
|
||||
height: root.trackHeight
|
||||
radius: root.rounded ? height / 2 : 0
|
||||
color: root.checked
|
||||
? Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.35)
|
||||
: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12)
|
||||
? Style.selectedFillFor(root.foreground, root.accent)
|
||||
: Style.normalFillFor(root.foreground, root.accent)
|
||||
border.color: root.checked
|
||||
? root.accent
|
||||
: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.28)
|
||||
border.width: 1
|
||||
? Style.selectedBorderFor(root.foreground, root.accent)
|
||||
: Style.normalBorderFor(root.foreground, root.accent)
|
||||
border.width: root.checked ? Style.selectedBorderWidth : Style.normalBorderWidth
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 120 } }
|
||||
|
||||
Rectangle {
|
||||
width: 16
|
||||
height: 16
|
||||
radius: root.rounded ? 8 : 0
|
||||
x: root.checked ? track.width - width - 3 : 3
|
||||
y: 3
|
||||
color: root.checked ? root.accent : Qt.darker(root.foreground, 1.25)
|
||||
width: root.knobSize
|
||||
height: root.knobSize
|
||||
radius: root.rounded ? height / 2 : 0
|
||||
x: root.checked ? track.width - width - root.knobInset : root.knobInset
|
||||
y: root.knobInset
|
||||
color: root.checked ? Style.selectedStateColor(root.foreground, root.accent) : Qt.darker(root.foreground, 1.25)
|
||||
|
||||
Behavior on x { NumberAnimation { duration: 120; easing.type: Easing.OutCubic } }
|
||||
Behavior on color { ColorAnimation { duration: 120 } }
|
||||
|
||||
@@ -25,11 +25,14 @@ Item {
|
||||
|
||||
readonly property bool vertical: bar ? bar.vertical : false
|
||||
readonly property int barSize: bar ? bar.barSize : Style.bar.sizeHorizontal
|
||||
readonly property real scaledHorizontalMargin: Style.spaceReal(horizontalMargin)
|
||||
readonly property real scaledRightExtraMargin: Style.spaceReal(rightExtraMargin)
|
||||
readonly property real scaledVerticalPadding: Style.spaceReal(verticalPadding)
|
||||
|
||||
visible: text !== "" || keepSpace
|
||||
opacity: text === "" ? 0 : 1
|
||||
implicitWidth: fixedWidth > 0 ? fixedWidth : (vertical ? barSize : Math.max(12, label.implicitWidth + horizontalMargin * 2 + rightExtraMargin))
|
||||
implicitHeight: fixedHeight > 0 ? fixedHeight : (vertical ? Math.max(12, label.implicitHeight + verticalPadding * 2) : barSize)
|
||||
implicitWidth: fixedWidth > 0 ? fixedWidth : (vertical ? barSize : Math.max(12, label.implicitWidth + scaledHorizontalMargin * 2 + scaledRightExtraMargin))
|
||||
implicitHeight: fixedHeight > 0 ? fixedHeight : (vertical ? Math.max(12, label.implicitHeight + scaledVerticalPadding * 2) : barSize)
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
|
||||
@@ -38,7 +41,7 @@ Item {
|
||||
Text {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: root.vertical ? 0 : -root.rightExtraMargin / 2
|
||||
anchors.horizontalCenterOffset: root.vertical ? 0 : -root.scaledRightExtraMargin / 2
|
||||
text: root.text
|
||||
color: root.active ? root.activeColor : root.foreground
|
||||
font.family: root.fontFamily
|
||||
|
||||
Reference in New Issue
Block a user