Gallery: replicate the panel cursor model from wifi/audio/bluetooth/monitor
This commit is contained in:
@@ -57,7 +57,7 @@ Grouped by what they're for, not alphabetically.
|
|||||||
| `CursorPill` | `PillButton` that participates in a panel's single-cursor model. Adds a `hovered(bool)` signal so the panel can update its cursor state on mouse enter/leave. Use for DNS-pill / header-pill / segmented-choice patterns. |
|
| `CursorPill` | `PillButton` that participates in a panel's single-cursor model. Adds a `hovered(bool)` signal so the panel can update its cursor state on mouse enter/leave. Use for DNS-pill / header-pill / segmented-choice patterns. |
|
||||||
| `ChoiceButton` | A single button in a mutually-exclusive choice group (segmented control). `selected` uses accent fill+border; focus uses `Style.focusBorderColor` so keyboard nav reads differently from selection. |
|
| `ChoiceButton` | A single button in a mutually-exclusive choice group (segmented control). `selected` uses accent fill+border; focus uses `Style.focusBorderColor` so keyboard nav reads differently from selection. |
|
||||||
| `Toggle` | Title + description + switch. Click anywhere on the row to flip; caller updates `checked` in response. `rounded` auto-detects from `Style.cornerRadius` so the switch is a pill on round-corners themes and square on sharp; override per-instance to force one or the other. |
|
| `Toggle` | Title + description + switch. Click anywhere on the row to flip; caller updates `checked` in response. `rounded` auto-detects from `Style.cornerRadius` so the switch is a pill on round-corners themes and square on sharp; override per-instance to force one or the other. |
|
||||||
| `TextField` | Single-line input. Inherits from Qt Quick Controls `TextField` so all of its base API (text, placeholderText, accepted, validator, ...) is available. Adds `password: bool`, `foreground` / `accent` / `selectionTint` color overrides, and `horizontalPadding` / `verticalPadding` size knobs. Focus styling uses `Style.focusBorderColor` to match `Toggle` and `ChoiceButton`. `hasCursor` paints the same focus ring so a panel cursor lands on the field identically; emits `hovered(bool)`. |
|
| `TextField` | Single-line input. Inherits from Qt Quick Controls `TextField` so all of its base API (text, placeholderText, accepted, validator, ...) is available. Adds `password: bool`, `foreground` / `accent` / `selectionTint` color overrides, and `horizontalPadding` / `verticalPadding` size knobs. Focus styling uses `Style.focusBorderColor` to match `Toggle` and `ChoiceButton`. `hasCursor` paints the same focus ring so a panel cursor lands on the field identically. For hover wiring use QQC TextField's inherited `hovered` property (via `onHoveredChanged`) — the sibling signal name would shadow it. |
|
||||||
| `Dropdown` | Single-select dropdown with a themed popup (no platform-native ComboBox chrome). `options` accepts `string[]` or `[{ value, label }]`. Keyboard: Tab to focus trigger, Enter/Space opens, j/k or arrows walk options, Enter selects. `hasCursor` paints the focus ring on the trigger; emits `hovered(bool)`. `popupOpen` plus `open()` / `close()` / `toggle()` let a parent panel suspend its own key catcher while the popup owns keys. |
|
| `Dropdown` | Single-select dropdown with a themed popup (no platform-native ComboBox chrome). `options` accepts `string[]` or `[{ value, label }]`. Keyboard: Tab to focus trigger, Enter/Space opens, j/k or arrows walk options, Enter selects. `hasCursor` paints the focus ring on the trigger; emits `hovered(bool)`. `popupOpen` plus `open()` / `close()` / `toggle()` let a parent panel suspend its own key catcher while the popup owns keys. |
|
||||||
| `SearchableDropdown` | `Dropdown` with an embedded search field at the top of the popup that filters options as you type. Use when the option count is high enough that scanning is friction (e.g. bar settings "+ Add widget"). Options can also carry a `description` string that the filter matches against. Same `hasCursor` / `popupOpen` / `open()` / `close()` / `toggle()` / `hovered(bool)` surface as `Dropdown`. |
|
| `SearchableDropdown` | `Dropdown` with an embedded search field at the top of the popup that filters options as you type. Use when the option count is high enough that scanning is friction (e.g. bar settings "+ Add widget"). Options can also carry a `description` string that the filter matches against. Same `hasCursor` / `popupOpen` / `open()` / `close()` / `toggle()` / `hovered(bool)` surface as `Dropdown`. |
|
||||||
| `PanelActionButton` | 22×22 right-edge action button (confirm, forget, unpair). `hoverColor` swaps between default foreground tint and urgent (red) tint. `focusable: true` enables Tab-focus with an accent ring — used for the bar settings widget-card row controls. `hasCursor: true` paints the hover fill so a panel cursor can land on the button directly when it isn't living inside a `CursorSurface` row; emits `hovered(bool)`. |
|
| `PanelActionButton` | 22×22 right-edge action button (confirm, forget, unpair). `hoverColor` swaps between default foreground tint and urgent (red) tint. `focusable: true` enables Tab-focus with an accent ring — used for the bar settings widget-card row controls. `hasCursor: true` paints the hover fill so a panel cursor can land on the button directly when it isn't living inside a `CursorSurface` row; emits `hovered(bool)`. |
|
||||||
|
|||||||
@@ -29,12 +29,11 @@ TextField {
|
|||||||
// Panel-cursor flag. When true (and the field isn't already focused),
|
// Panel-cursor flag. When true (and the field isn't already focused),
|
||||||
// the background paints the same accent ring as activeFocus so the
|
// the background paints the same accent ring as activeFocus so the
|
||||||
// panel's keyboard cursor lands here identically to a mouse hover.
|
// panel's keyboard cursor lands here identically to a mouse hover.
|
||||||
// Emits `hovered(bool)` on pointer enter/leave so the panel can update
|
// For mouse-enter/leave the consumer reads QQC TextField's inherited
|
||||||
// its cursor state to match.
|
// `hovered` property (via onHoveredChanged) — we don't add a sibling
|
||||||
|
// signal because the inherited property would shadow it.
|
||||||
property bool hasCursor: false
|
property bool hasCursor: false
|
||||||
|
|
||||||
signal hovered(bool isHovered)
|
|
||||||
|
|
||||||
readonly property bool _focused: activeFocus || hasCursor
|
readonly property bool _focused: activeFocus || hasCursor
|
||||||
|
|
||||||
echoMode: password ? TextInput.Password : TextInput.Normal
|
echoMode: password ? TextInput.Password : TextInput.Normal
|
||||||
@@ -57,8 +56,4 @@ TextField {
|
|||||||
border.width: root._focused ? Style.focusBorderWidth : 1
|
border.width: root._focused ? Style.focusBorderWidth : 1
|
||||||
radius: Style.cornerRadius
|
radius: Style.cornerRadius
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
onHoveredChanged: root.hovered(hovered)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,15 +63,190 @@ Item {
|
|||||||
readonly property int barSize: 26
|
readonly property int barSize: 26
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- cursor demo state --------------------------------------------------
|
// ---- cursor model -------------------------------------------------------
|
||||||
property int cursorDemoIndex: 1
|
//
|
||||||
property int pillDemoIndex: 1
|
// The gallery itself uses the same recipe wifi / audio / bluetooth /
|
||||||
|
// monitor panels use: focusSection + selectedIndex drive a single
|
||||||
|
// highlight that crosses kit primitives uniformly, with j/k walking
|
||||||
|
// targets (jumping section boundaries automatically), h/l acting
|
||||||
|
// locally (horizontal rows / slider adjustment), Enter activating, and
|
||||||
|
// Esc closing. Mouse hover updates the same (focusSection,
|
||||||
|
// selectedIndex) so keyboard and pointer never diverge.
|
||||||
|
//
|
||||||
|
// Plugin authors: copy this section verbatim as a template. Replace
|
||||||
|
// the section IDs with whatever your panel needs. The shape
|
||||||
|
// (visibleSections, sectionCount, sectionIsHorizontal,
|
||||||
|
// sectionAdjustsValue, moveCursor, moveCursorH, activateCursor,
|
||||||
|
// ensureCursorVisible, clampCursor) is the canonical pattern.
|
||||||
|
property string focusSection: "cursor-surface"
|
||||||
|
property int selectedIndex: 0
|
||||||
|
|
||||||
|
// Demo state mutated by activation.
|
||||||
property string choiceDemoValue: "top"
|
property string choiceDemoValue: "top"
|
||||||
property bool toggleDemoOn: true
|
property bool toggleDemoOn: true
|
||||||
property bool toggleSquareOn: false
|
property bool toggleSquareOn: false
|
||||||
property string dropdownDemoValue: "calendar"
|
property string dropdownDemoValue: "calendar"
|
||||||
property string searchableDemoValue: ""
|
property string searchableDemoValue: ""
|
||||||
|
|
||||||
|
readonly property var visibleSections: [
|
||||||
|
"cursor-surface", "pill-button", "cursor-pill", "panel-action-button",
|
||||||
|
"panel-tool-tip", "slider", "choice-button", "text-field", "toggle",
|
||||||
|
"dropdown", "searchable-dropdown", "composed"
|
||||||
|
]
|
||||||
|
|
||||||
|
function sectionCount(section) {
|
||||||
|
switch (section) {
|
||||||
|
case "cursor-surface": return 3
|
||||||
|
case "pill-button": return 5
|
||||||
|
case "cursor-pill": return 4
|
||||||
|
case "panel-action-button": return 4
|
||||||
|
case "panel-tool-tip": return 1
|
||||||
|
case "slider": return 1
|
||||||
|
case "choice-button": return 4
|
||||||
|
case "text-field": return 2
|
||||||
|
case "toggle": return 2
|
||||||
|
case "dropdown": return 1
|
||||||
|
case "searchable-dropdown": return 1
|
||||||
|
case "composed": return 2
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// True for sections whose primitives lay out horizontally (a row of
|
||||||
|
// pills, choice buttons, etc.) — j/k jumps to the next/prev section,
|
||||||
|
// h/l walks within the row.
|
||||||
|
function sectionIsHorizontal(section) {
|
||||||
|
return section === "pill-button"
|
||||||
|
|| section === "cursor-pill"
|
||||||
|
|| section === "panel-action-button"
|
||||||
|
|| section === "choice-button"
|
||||||
|
}
|
||||||
|
|
||||||
|
// True for sections where h/l should adjust a value rather than walk.
|
||||||
|
function sectionAdjustsValue(section) {
|
||||||
|
return section === "slider"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where to land when entering a section from above / below.
|
||||||
|
function sectionFirstIndex(section) { return 0 }
|
||||||
|
function sectionLastIndex(section) { return Math.max(0, sectionCount(section) - 1) }
|
||||||
|
|
||||||
|
function moveCursor(delta) {
|
||||||
|
var sections = visibleSections
|
||||||
|
var sIdx = sections.indexOf(focusSection)
|
||||||
|
if (sIdx < 0) {
|
||||||
|
focusSection = sections[0]
|
||||||
|
selectedIndex = sectionFirstIndex(focusSection)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (sectionIsHorizontal(focusSection) || sectionAdjustsValue(focusSection)
|
||||||
|
|| sectionCount(focusSection) <= 1) {
|
||||||
|
// Single-row / horizontal / value-adjust sections: j/k crosses to
|
||||||
|
// the next section.
|
||||||
|
if (delta > 0 && sIdx < sections.length - 1) {
|
||||||
|
focusSection = sections[sIdx + 1]
|
||||||
|
selectedIndex = sectionFirstIndex(focusSection)
|
||||||
|
} else if (delta < 0 && sIdx > 0) {
|
||||||
|
focusSection = sections[sIdx - 1]
|
||||||
|
selectedIndex = sectionLastIndex(focusSection)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Vertical multi-row section: walk within, then cross at boundaries.
|
||||||
|
var next = selectedIndex + delta
|
||||||
|
if (next < 0) {
|
||||||
|
if (sIdx > 0) {
|
||||||
|
focusSection = sections[sIdx - 1]
|
||||||
|
selectedIndex = sectionLastIndex(focusSection)
|
||||||
|
}
|
||||||
|
} else if (next >= sectionCount(focusSection)) {
|
||||||
|
if (sIdx < sections.length - 1) {
|
||||||
|
focusSection = sections[sIdx + 1]
|
||||||
|
selectedIndex = sectionFirstIndex(focusSection)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedIndex = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveCursorH(delta) {
|
||||||
|
if (sectionAdjustsValue(focusSection)) {
|
||||||
|
// h/l on the slider section nudges the demo volume by 5%.
|
||||||
|
sliderRow.demoVolume = Math.max(0, Math.min(1, sliderRow.demoVolume + delta * 0.05))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!sectionIsHorizontal(focusSection)) return
|
||||||
|
var next = selectedIndex + delta
|
||||||
|
var max = sectionCount(focusSection) - 1
|
||||||
|
if (next < 0) next = 0
|
||||||
|
if (next > max) next = max
|
||||||
|
selectedIndex = next
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateCursor() {
|
||||||
|
if (focusSection === "choice-button") {
|
||||||
|
var opts = ["top", "right", "bottom", "left"]
|
||||||
|
if (selectedIndex >= 0 && selectedIndex < opts.length)
|
||||||
|
root.choiceDemoValue = opts[selectedIndex]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (focusSection === "toggle") {
|
||||||
|
if (selectedIndex === 0) root.toggleDemoOn = !root.toggleDemoOn
|
||||||
|
else root.toggleSquareOn = !root.toggleSquareOn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (focusSection === "dropdown") {
|
||||||
|
demoDropdown.toggle()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (focusSection === "searchable-dropdown") {
|
||||||
|
demoSearchableDropdown.toggle()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (focusSection === "text-field") {
|
||||||
|
if (selectedIndex === 0) demoTextField.forceActiveFocus()
|
||||||
|
else demoPasswordField.forceActiveFocus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// pill / panel-action-button / cursor-surface / composed: nothing to
|
||||||
|
// mutate in a demo, but real consumers would call their clicked().
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampCursor() {
|
||||||
|
var sections = visibleSections
|
||||||
|
if (sections.indexOf(focusSection) < 0) {
|
||||||
|
focusSection = sections[0]
|
||||||
|
selectedIndex = sectionFirstIndex(focusSection)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (selectedIndex < 0) selectedIndex = 0
|
||||||
|
var max = sectionLastIndex(focusSection)
|
||||||
|
if (selectedIndex > max) selectedIndex = max
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll the gallery so the given Item is fully visible inside
|
||||||
|
// scrollArea's viewport, with a 20px breathing margin. Wired into the
|
||||||
|
// hasCursor change handler of every cursor target below.
|
||||||
|
function ensureCursorVisible(item) {
|
||||||
|
if (!item || !scrollArea) return
|
||||||
|
var sb = scrollArea.ScrollBar.vertical
|
||||||
|
if (!sb || scrollArea.contentHeight <= scrollArea.height) return
|
||||||
|
var contentItem = scrollArea.contentItem
|
||||||
|
if (!contentItem) return
|
||||||
|
var p = item.mapToItem(contentItem, 0, 0)
|
||||||
|
var itemTop = p.y
|
||||||
|
var itemBottom = p.y + item.height
|
||||||
|
var viewTop = sb.position * scrollArea.contentHeight
|
||||||
|
var viewBottom = viewTop + scrollArea.height
|
||||||
|
var pad = 20
|
||||||
|
if (itemTop < viewTop + pad) {
|
||||||
|
sb.position = Math.max(0, (itemTop - pad) / scrollArea.contentHeight)
|
||||||
|
} else if (itemBottom > viewBottom - pad) {
|
||||||
|
var newPos = (itemBottom + pad - scrollArea.height) / scrollArea.contentHeight
|
||||||
|
sb.position = Math.max(0, Math.min(1 - sb.size, newPos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FloatingWindow {
|
FloatingWindow {
|
||||||
id: window
|
id: window
|
||||||
title: "Omarchy shell – dev gallery"
|
title: "Omarchy shell – dev gallery"
|
||||||
@@ -90,9 +265,6 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
// Scroll the gallery's ScrollView by `dy` pixels. Bound below 0 and
|
|
||||||
// above (1 - thumbSize) so the thumb stays on the track when the
|
|
||||||
// user mashes Page_Down past the end.
|
|
||||||
function scrollBy(dy) {
|
function scrollBy(dy) {
|
||||||
var sb = scrollArea.ScrollBar.vertical
|
var sb = scrollArea.ScrollBar.vertical
|
||||||
if (!sb || scrollArea.contentHeight <= scrollArea.height) return
|
if (!sb || scrollArea.contentHeight <= scrollArea.height) return
|
||||||
@@ -118,22 +290,24 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panel-style key dispatch. j/k scroll the gallery; h/l walk the
|
// Panel-style key dispatch — the gallery demonstrates the standard,
|
||||||
// cursor / pill demos; Esc closes. Same component the wifi, audio,
|
// so it USES the standard. j/k walks cursor targets across sections,
|
||||||
// bluetooth, monitor, and settings panels all use — the gallery
|
// h/l acts locally (rows + slider adjust), Enter activates the
|
||||||
// is meant to demonstrate the standard, so it should USE the
|
// current target, Esc closes. The catcher suspends itself while a
|
||||||
// standard rather than reimplement its own keyhandler.
|
// dropdown popup or text field owns keyboard input, so typing into
|
||||||
|
// the embedded controls doesn't double-drive the panel cursor.
|
||||||
PanelKeyCatcher {
|
PanelKeyCatcher {
|
||||||
id: keyCatcher
|
id: keyCatcher
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
blocked: demoDropdown.popupOpen
|
||||||
|
|| demoSearchableDropdown.popupOpen
|
||||||
|
|| demoTextField.activeFocus
|
||||||
|
|| demoPasswordField.activeFocus
|
||||||
onMoveRequested: function(dx, dy) {
|
onMoveRequested: function(dx, dy) {
|
||||||
if (dy !== 0) {
|
if (dy !== 0) root.moveCursor(dy)
|
||||||
focusScope.scrollBy(dy * 60)
|
else if (dx !== 0) root.moveCursorH(dx)
|
||||||
} else if (dx !== 0) {
|
|
||||||
root.cursorDemoIndex = Math.max(0, Math.min(2, root.cursorDemoIndex + dx))
|
|
||||||
root.pillDemoIndex = Math.max(0, Math.min(3, root.pillDemoIndex + dx))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
onActivateRequested: root.activateCursor()
|
||||||
onCloseRequested: root.requestClose()
|
onCloseRequested: root.requestClose()
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
@@ -320,10 +494,11 @@ Item {
|
|||||||
required property int index
|
required property int index
|
||||||
width: parent.width
|
width: parent.width
|
||||||
implicitHeight: csLabel.implicitHeight + 16
|
implicitHeight: csLabel.implicitHeight + 16
|
||||||
hasCursor: root.cursorDemoIndex === index
|
hasCursor: root.focusSection === "cursor-surface" && root.selectedIndex === index
|
||||||
current: index === 1
|
current: index === 1
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
fill: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18)
|
fill: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18)
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: csLabel
|
id: csLabel
|
||||||
@@ -343,7 +518,10 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onContainsMouseChanged: if (containsMouse) root.cursorDemoIndex = parent.index
|
onContainsMouseChanged: if (containsMouse) {
|
||||||
|
root.focusSection = "cursor-surface"
|
||||||
|
root.selectedIndex = parent.index
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -394,6 +572,9 @@ Item {
|
|||||||
tooltipForeground: root.foreground
|
tooltipForeground: root.foreground
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
|
hasCursor: root.focusSection === "pill-button" && root.selectedIndex === 0
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
HoverHandler { onHoveredChanged: if (hovered) { root.focusSection = "pill-button"; root.selectedIndex = 0 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
PillButton {
|
PillButton {
|
||||||
@@ -404,6 +585,9 @@ Item {
|
|||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
active: true
|
active: true
|
||||||
|
hasCursor: root.focusSection === "pill-button" && root.selectedIndex === 1
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
HoverHandler { onHoveredChanged: if (hovered) { root.focusSection = "pill-button"; root.selectedIndex = 1 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
PillButton {
|
PillButton {
|
||||||
@@ -415,6 +599,9 @@ Item {
|
|||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
horizontalPadding: 8
|
horizontalPadding: 8
|
||||||
verticalPadding: 4
|
verticalPadding: 4
|
||||||
|
hasCursor: root.focusSection === "pill-button" && root.selectedIndex === 2
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
HoverHandler { onHoveredChanged: if (hovered) { root.focusSection = "pill-button"; root.selectedIndex = 2 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
PillButton {
|
PillButton {
|
||||||
@@ -426,6 +613,9 @@ Item {
|
|||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
active: true
|
active: true
|
||||||
|
hasCursor: root.focusSection === "pill-button" && root.selectedIndex === 3
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
HoverHandler { onHoveredChanged: if (hovered) { root.focusSection = "pill-button"; root.selectedIndex = 3 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
PillButton {
|
PillButton {
|
||||||
@@ -434,6 +624,9 @@ Item {
|
|||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
focusable: true
|
focusable: true
|
||||||
bordered: true
|
bordered: true
|
||||||
|
hasCursor: root.focusSection === "pill-button" && root.selectedIndex === 4
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
HoverHandler { onHoveredChanged: if (hovered) { root.focusSection = "pill-button"; root.selectedIndex = 4 } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -486,10 +679,13 @@ Item {
|
|||||||
tooltipForeground: root.foreground
|
tooltipForeground: root.foreground
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
tooltipText: "Pick " + modelData
|
tooltipText: "Pick " + modelData
|
||||||
hasCursor: root.pillDemoIndex === index
|
hasCursor: root.focusSection === "cursor-pill" && root.selectedIndex === index
|
||||||
active: modelData === "Cloudflare"
|
active: modelData === "Cloudflare"
|
||||||
onHovered: function(h) { if (h) root.pillDemoIndex = index }
|
onHovered: function(h) {
|
||||||
onClicked: root.pillDemoIndex = index
|
if (h) { root.focusSection = "cursor-pill"; root.selectedIndex = index }
|
||||||
|
}
|
||||||
|
onClicked: { root.focusSection = "cursor-pill"; root.selectedIndex = index }
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -538,6 +734,11 @@ Item {
|
|||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
panelBackground: root.background
|
panelBackground: root.background
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
|
hasCursor: root.focusSection === "panel-action-button" && root.selectedIndex === 0
|
||||||
|
onHovered: function(h) {
|
||||||
|
if (h) { root.focusSection = "panel-action-button"; root.selectedIndex = 0 }
|
||||||
|
}
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelActionButton {
|
PanelActionButton {
|
||||||
@@ -547,6 +748,11 @@ Item {
|
|||||||
hoverColor: root.urgent
|
hoverColor: root.urgent
|
||||||
panelBackground: root.background
|
panelBackground: root.background
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
|
hasCursor: root.focusSection === "panel-action-button" && root.selectedIndex === 1
|
||||||
|
onHovered: function(h) {
|
||||||
|
if (h) { root.focusSection = "panel-action-button"; root.selectedIndex = 1 }
|
||||||
|
}
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelActionButton {
|
PanelActionButton {
|
||||||
@@ -556,6 +762,11 @@ Item {
|
|||||||
panelBackground: root.background
|
panelBackground: root.background
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
enabled: false
|
enabled: false
|
||||||
|
hasCursor: root.focusSection === "panel-action-button" && root.selectedIndex === 2
|
||||||
|
onHovered: function(h) {
|
||||||
|
if (h) { root.focusSection = "panel-action-button"; root.selectedIndex = 2 }
|
||||||
|
}
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelActionButton {
|
PanelActionButton {
|
||||||
@@ -567,6 +778,11 @@ Item {
|
|||||||
fontSize: 13
|
fontSize: 13
|
||||||
size: 26
|
size: 26
|
||||||
focusable: true
|
focusable: true
|
||||||
|
hasCursor: root.focusSection === "panel-action-button" && root.selectedIndex === 3
|
||||||
|
onHovered: function(h) {
|
||||||
|
if (h) { root.focusSection = "panel-action-button"; root.selectedIndex = 3 }
|
||||||
|
}
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -594,12 +810,19 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
id: tipSwatch
|
||||||
width: 140
|
width: 140
|
||||||
height: 36
|
height: 36
|
||||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
readonly property bool focused: root.focusSection === "panel-tool-tip"
|
||||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.35)
|
color: tipMouse.containsMouse || focused
|
||||||
border.width: 1
|
? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18)
|
||||||
|
: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||||
|
border.color: focused
|
||||||
|
? Style.focusBorderColor
|
||||||
|
: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.35)
|
||||||
|
border.width: focused ? Style.focusBorderWidth : 1
|
||||||
radius: Style.cornerRadius
|
radius: Style.cornerRadius
|
||||||
|
onFocusedChanged: if (focused) root.ensureCursorVisible(this)
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -614,10 +837,14 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onContainsMouseChanged: if (containsMouse) {
|
||||||
|
root.focusSection = "panel-tool-tip"
|
||||||
|
root.selectedIndex = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelToolTip {
|
PanelToolTip {
|
||||||
visible: tipMouse.containsMouse
|
visible: tipMouse.containsMouse || tipSwatch.focused
|
||||||
text: "Styled tooltip — drop into any panel"
|
text: "Styled tooltip — drop into any panel"
|
||||||
panelForeground: root.foreground
|
panelForeground: root.foreground
|
||||||
panelBackground: root.background
|
panelBackground: root.background
|
||||||
@@ -649,12 +876,24 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
id: sliderWrapper
|
||||||
width: parent.width
|
width: parent.width
|
||||||
implicitHeight: sliderRow.implicitHeight + 24
|
implicitHeight: sliderRow.implicitHeight + 24
|
||||||
|
readonly property bool focused: root.focusSection === "slider"
|
||||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||||
radius: Style.cornerRadius
|
radius: Style.cornerRadius
|
||||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
border.color: focused
|
||||||
border.width: 1
|
? Style.focusBorderColor
|
||||||
|
: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||||
|
border.width: focused ? Style.focusBorderWidth : 1
|
||||||
|
onFocusedChanged: if (focused) root.ensureCursorVisible(this)
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
onHoveredChanged: if (hovered) {
|
||||||
|
root.focusSection = "slider"
|
||||||
|
root.selectedIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: sliderRow
|
id: sliderRow
|
||||||
@@ -738,13 +977,23 @@ Item {
|
|||||||
model: ["top", "right", "bottom", "left"]
|
model: ["top", "right", "bottom", "left"]
|
||||||
ChoiceButton {
|
ChoiceButton {
|
||||||
required property string modelData
|
required property string modelData
|
||||||
|
required property int index
|
||||||
text: modelData
|
text: modelData
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
background: root.background
|
background: root.background
|
||||||
accent: root.accent
|
accent: root.accent
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
selected: root.choiceDemoValue === modelData
|
selected: root.choiceDemoValue === modelData
|
||||||
onClicked: root.choiceDemoValue = modelData
|
hasCursor: root.focusSection === "choice-button" && root.selectedIndex === index
|
||||||
|
onClicked: {
|
||||||
|
root.focusSection = "choice-button"
|
||||||
|
root.selectedIndex = index
|
||||||
|
root.choiceDemoValue = modelData
|
||||||
|
}
|
||||||
|
onHovered: function(h) {
|
||||||
|
if (h) { root.focusSection = "choice-button"; root.selectedIndex = index }
|
||||||
|
}
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -790,15 +1039,28 @@ Item {
|
|||||||
spacing: 10
|
spacing: 10
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
|
id: demoTextField
|
||||||
width: parent.width
|
width: parent.width
|
||||||
placeholderText: "Type something"
|
placeholderText: "Type something (Enter to start editing, Esc to leave)"
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
accent: root.accent
|
accent: root.accent
|
||||||
font.family: root.fontFamily
|
font.family: root.fontFamily
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
|
hasCursor: !activeFocus && root.focusSection === "text-field" && root.selectedIndex === 0
|
||||||
|
onHoveredChanged: if (hovered) {
|
||||||
|
root.focusSection = "text-field"; root.selectedIndex = 0
|
||||||
|
}
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
Keys.onPressed: function(event) {
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
focus = false
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
|
id: demoPasswordField
|
||||||
width: parent.width
|
width: parent.width
|
||||||
password: true
|
password: true
|
||||||
placeholderText: "Password"
|
placeholderText: "Password"
|
||||||
@@ -806,6 +1068,17 @@ Item {
|
|||||||
accent: root.accent
|
accent: root.accent
|
||||||
font.family: root.fontFamily
|
font.family: root.fontFamily
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
|
hasCursor: !activeFocus && root.focusSection === "text-field" && root.selectedIndex === 1
|
||||||
|
onHoveredChanged: if (hovered) {
|
||||||
|
root.focusSection = "text-field"; root.selectedIndex = 1
|
||||||
|
}
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
Keys.onPressed: function(event) {
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
focus = false
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -840,7 +1113,15 @@ Item {
|
|||||||
accent: root.accent
|
accent: root.accent
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
checked: root.toggleDemoOn
|
checked: root.toggleDemoOn
|
||||||
onClicked: root.toggleDemoOn = !root.toggleDemoOn
|
hasCursor: root.focusSection === "toggle" && root.selectedIndex === 0
|
||||||
|
onHovered: function(h) {
|
||||||
|
if (h) { root.focusSection = "toggle"; root.selectedIndex = 0 }
|
||||||
|
}
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
onClicked: {
|
||||||
|
root.focusSection = "toggle"; root.selectedIndex = 0
|
||||||
|
root.toggleDemoOn = !root.toggleDemoOn
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Toggle {
|
Toggle {
|
||||||
@@ -852,7 +1133,15 @@ Item {
|
|||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
rounded: false
|
rounded: false
|
||||||
checked: root.toggleSquareOn
|
checked: root.toggleSquareOn
|
||||||
onClicked: root.toggleSquareOn = !root.toggleSquareOn
|
hasCursor: root.focusSection === "toggle" && root.selectedIndex === 1
|
||||||
|
onHovered: function(h) {
|
||||||
|
if (h) { root.focusSection = "toggle"; root.selectedIndex = 1 }
|
||||||
|
}
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
onClicked: {
|
||||||
|
root.focusSection = "toggle"; root.selectedIndex = 1
|
||||||
|
root.toggleSquareOn = !root.toggleSquareOn
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -895,11 +1184,17 @@ Item {
|
|||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
Dropdown {
|
Dropdown {
|
||||||
|
id: demoDropdown
|
||||||
width: 260
|
width: 260
|
||||||
label: "Center anchor"
|
label: "Center anchor"
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
options: ["calendar", "weather", "clock", "battery"]
|
options: ["calendar", "weather", "clock", "battery"]
|
||||||
value: root.dropdownDemoValue
|
value: root.dropdownDemoValue
|
||||||
|
hasCursor: root.focusSection === "dropdown" && root.selectedIndex === 0
|
||||||
|
onHovered: function(h) {
|
||||||
|
if (h) { root.focusSection = "dropdown"; root.selectedIndex = 0 }
|
||||||
|
}
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
onChanged: function(v) { root.dropdownDemoValue = v }
|
onChanged: function(v) { root.dropdownDemoValue = v }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -945,10 +1240,16 @@ Item {
|
|||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
SearchableDropdown {
|
SearchableDropdown {
|
||||||
|
id: demoSearchableDropdown
|
||||||
width: 280
|
width: 280
|
||||||
label: "Add widget"
|
label: "Add widget"
|
||||||
fontFamily: root.fontFamily
|
fontFamily: root.fontFamily
|
||||||
placeholderText: "Search widgets..."
|
placeholderText: "Search widgets..."
|
||||||
|
hasCursor: root.focusSection === "searchable-dropdown" && root.selectedIndex === 0
|
||||||
|
onHovered: function(h) {
|
||||||
|
if (h) { root.focusSection = "searchable-dropdown"; root.selectedIndex = 0 }
|
||||||
|
}
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
options: [
|
options: [
|
||||||
{ value: "clock", label: "Clock", description: "Time + date display" },
|
{ value: "clock", label: "Clock", description: "Time + date display" },
|
||||||
{ value: "weather", label: "Weather", description: "Local conditions and forecast" },
|
{ value: "weather", label: "Weather", description: "Local conditions and forecast" },
|
||||||
@@ -1023,6 +1324,14 @@ Item {
|
|||||||
current: true
|
current: true
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
fill: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18)
|
fill: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18)
|
||||||
|
hasCursor: root.focusSection === "composed" && root.selectedIndex === 0
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
onHoveredChanged: if (hovered) {
|
||||||
|
root.focusSection = "composed"; root.selectedIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: composedRow
|
id: composedRow
|
||||||
@@ -1090,6 +1399,14 @@ Item {
|
|||||||
implicitHeight: idleRow.implicitHeight + 12
|
implicitHeight: idleRow.implicitHeight + 12
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
fill: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18)
|
fill: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18)
|
||||||
|
hasCursor: root.focusSection === "composed" && root.selectedIndex === 1
|
||||||
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this)
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
onHoveredChanged: if (hovered) {
|
||||||
|
root.focusSection = "composed"; root.selectedIndex = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: idleRow
|
id: idleRow
|
||||||
|
|||||||
Reference in New Issue
Block a user