Add Qt/QML desktop shell and Nebula.UI
Introduce a new production desktop shell at `shells/desktop/shell` built with C++20, Qt Quick/QML, and LayerShellQt, including top bar, launcher, desktop surfaces, shell state, and mock app data. Add the shared `packages/nebula-ui` QML module (theme, controls, focus, icons) and wire it into the shell build. Retire the old GTK/WebKit native host by removing `shells/desktop/native`, move the React shell UI to `shells/desktop/prototype-react` as an archived design reference, and update root/compositor/session documentation to reflect the new Linux-only production architecture and temporary Sway role.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
if(NOT TARGET Qt6::Quick)
|
||||
project(NebulaUI LANGUAGES CXX)
|
||||
find_package(Qt6 6.4 REQUIRED COMPONENTS Quick)
|
||||
qt_standard_project_setup(REQUIRES 6.4)
|
||||
endif()
|
||||
|
||||
qt_add_library(NebulaUI STATIC)
|
||||
|
||||
set(NEBULA_UI_QML_DIR "${CMAKE_CURRENT_SOURCE_DIR}/qml/Nebula/UI")
|
||||
|
||||
set(NEBULA_UI_QML_FILES
|
||||
Theme.qml
|
||||
NebulaButton.qml
|
||||
NebulaCard.qml
|
||||
NebulaIconButton.qml
|
||||
NebulaTextField.qml
|
||||
FocusFrame.qml
|
||||
NebulaIcon.qml
|
||||
)
|
||||
|
||||
set(NEBULA_UI_QML_PATHS)
|
||||
foreach(qml_file IN LISTS NEBULA_UI_QML_FILES)
|
||||
set(qml_path "${NEBULA_UI_QML_DIR}/${qml_file}")
|
||||
set_source_files_properties("${qml_path}" PROPERTIES
|
||||
QT_RESOURCE_ALIAS "${qml_file}"
|
||||
)
|
||||
list(APPEND NEBULA_UI_QML_PATHS "${qml_path}")
|
||||
endforeach()
|
||||
|
||||
set_source_files_properties("${NEBULA_UI_QML_DIR}/Theme.qml" PROPERTIES
|
||||
QT_QML_SINGLETON_TYPE TRUE
|
||||
)
|
||||
|
||||
qt_add_qml_module(NebulaUI
|
||||
URI Nebula.UI
|
||||
VERSION 1.0
|
||||
RESOURCE_PREFIX /qt/qml
|
||||
QML_FILES ${NEBULA_UI_QML_PATHS}
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
# Nebula.UI
|
||||
|
||||
Shared Qt Quick/QML design system for NebulaOS shells.
|
||||
|
||||
Desktop and the future Bigscreen shell should both import:
|
||||
|
||||
```qml
|
||||
import Nebula.UI
|
||||
```
|
||||
|
||||
This module contains reusable visual language only:
|
||||
|
||||
- colour, spacing, type, and motion tokens (`Theme`)
|
||||
- generic controls (`NebulaButton`, `NebulaCard`, `NebulaIconButton`, `NebulaTextField`)
|
||||
- focus treatment (`FocusFrame`)
|
||||
- compact vector icons (`NebulaIcon`)
|
||||
|
||||
Do not put Desktop- or Bigscreen-specific chrome here (top bar, launcher, session flow).
|
||||
|
||||
Built as part of `shells/desktop/shell`. It is not a standalone application.
|
||||
@@ -0,0 +1,24 @@
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property Item target: parent
|
||||
property real radius: Theme.radiusSmall
|
||||
property bool active: target && target.activeFocus
|
||||
|
||||
anchors.fill: target
|
||||
anchors.margins: -2
|
||||
z: 20
|
||||
enabled: false
|
||||
visible: active
|
||||
Accessible.ignored: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.width: 2
|
||||
border.color: Theme.focus
|
||||
radius: root.radius + 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string text: ""
|
||||
property bool checked: false
|
||||
property color textColor: checked ? Theme.accentStrong : Theme.textPrimary
|
||||
|
||||
default property alias icon: iconHolder.data
|
||||
|
||||
signal clicked()
|
||||
|
||||
implicitWidth: row.implicitWidth + 18
|
||||
implicitHeight: Theme.controlHeight
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: root.text
|
||||
Accessible.checkable: true
|
||||
Accessible.checked: root.checked
|
||||
Accessible.onPressAction: root.clicked()
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.radiusSmall
|
||||
color: (hover.containsMouse || root.checked) ? Theme.accentSoft : "transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row
|
||||
anchors.centerIn: parent
|
||||
spacing: 8
|
||||
|
||||
Item {
|
||||
id: iconHolder
|
||||
width: children.length > 0 ? 18 : 0
|
||||
height: 18
|
||||
visible: width > 0
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.text
|
||||
color: root.textColor
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.weight: Font.DemiBold
|
||||
visible: root.text.length > 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: hover
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
FocusFrame {
|
||||
radius: Theme.radiusSmall
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
color: Theme.surface
|
||||
border.color: Theme.border
|
||||
border.width: 1
|
||||
radius: Theme.radiusLarge
|
||||
|
||||
Rectangle {
|
||||
id: shadow
|
||||
z: -1
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 10
|
||||
anchors.bottomMargin: -10
|
||||
radius: root.radius
|
||||
color: Theme.shadow
|
||||
opacity: 0.85
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string name: "nebula"
|
||||
property color color: Theme.textPrimary
|
||||
property int size: 16
|
||||
property real value: 1
|
||||
|
||||
implicitWidth: size
|
||||
implicitHeight: size
|
||||
Accessible.ignored: true
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
onPaint: root.paint(getContext("2d"))
|
||||
}
|
||||
|
||||
onNameChanged: canvas.requestPaint()
|
||||
onColorChanged: canvas.requestPaint()
|
||||
onValueChanged: canvas.requestPaint()
|
||||
onWidthChanged: canvas.requestPaint()
|
||||
onHeightChanged: canvas.requestPaint()
|
||||
|
||||
function paint(ctx) {
|
||||
ctx.reset()
|
||||
ctx.strokeStyle = root.color
|
||||
ctx.fillStyle = root.color
|
||||
ctx.lineCap = "round"
|
||||
ctx.lineJoin = "round"
|
||||
ctx.scale(width / 24, height / 24)
|
||||
|
||||
switch (root.name) {
|
||||
case "nebula":
|
||||
paintNebula(ctx)
|
||||
break
|
||||
case "search":
|
||||
paintSearch(ctx)
|
||||
break
|
||||
case "wifi":
|
||||
paintWifi(ctx)
|
||||
break
|
||||
case "speaker":
|
||||
paintSpeaker(ctx)
|
||||
break
|
||||
case "battery":
|
||||
paintBattery(ctx)
|
||||
break
|
||||
case "power":
|
||||
paintPower(ctx)
|
||||
break
|
||||
case "restart":
|
||||
paintRestart(ctx)
|
||||
break
|
||||
case "folder":
|
||||
paintFolder(ctx)
|
||||
break
|
||||
case "globe":
|
||||
paintGlobe(ctx)
|
||||
break
|
||||
case "terminal":
|
||||
paintTerminal(ctx)
|
||||
break
|
||||
case "settings":
|
||||
paintSettings(ctx)
|
||||
break
|
||||
case "game":
|
||||
paintGame(ctx)
|
||||
break
|
||||
case "photo":
|
||||
paintPhoto(ctx)
|
||||
break
|
||||
case "music":
|
||||
paintMusic(ctx)
|
||||
break
|
||||
case "document":
|
||||
paintDocument(ctx)
|
||||
break
|
||||
default:
|
||||
paintFolder(ctx)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function paintNebula(ctx) {
|
||||
ctx.beginPath()
|
||||
ctx.arc(12, 12, 4.2, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
|
||||
ctx.save()
|
||||
ctx.translate(12, 12)
|
||||
ctx.rotate(-26 * Math.PI / 180)
|
||||
ctx.scale(10 / 4.15, 1)
|
||||
ctx.lineWidth = 1.35 * (4.15 / 10)
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, 4.15, 0, Math.PI * 2)
|
||||
ctx.stroke()
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
function paintSearch(ctx) {
|
||||
ctx.lineWidth = 1.6
|
||||
ctx.beginPath()
|
||||
ctx.arc(11, 11, 6.25, 0, Math.PI * 2)
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(15.8, 15.8)
|
||||
ctx.lineTo(20, 20)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
function paintWifi(ctx) {
|
||||
const strength = root.value
|
||||
ctx.lineWidth = 1.6
|
||||
ctx.globalAlpha = strength > 0 ? 1 : 0.28
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(4.8, 15.2)
|
||||
ctx.bezierCurveTo(8.8, 11.2, 15.2, 11.2, 19.2, 15.2)
|
||||
ctx.stroke()
|
||||
ctx.globalAlpha = strength > 1 ? 1 : 0.28
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(7.4, 17.4)
|
||||
ctx.bezierCurveTo(10, 14.9, 14, 14.9, 16.6, 17.4)
|
||||
ctx.stroke()
|
||||
ctx.globalAlpha = 1
|
||||
ctx.beginPath()
|
||||
ctx.arc(12, 20, 1.15, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
function paintSpeaker(ctx) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(4.5, 9.4)
|
||||
ctx.lineTo(7.6, 9.4)
|
||||
ctx.lineTo(12, 6.2)
|
||||
ctx.lineTo(12, 17.8)
|
||||
ctx.lineTo(7.6, 14.6)
|
||||
ctx.lineTo(4.5, 14.6)
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.beginPath()
|
||||
ctx.arc(12.5, 12, 4.2, -0.7, 0.7)
|
||||
ctx.stroke()
|
||||
ctx.globalAlpha = 0.7
|
||||
ctx.beginPath()
|
||||
ctx.arc(12.5, 12, 7.2, -0.85, 0.85)
|
||||
ctx.stroke()
|
||||
ctx.globalAlpha = 1
|
||||
}
|
||||
|
||||
function paintBattery(ctx) {
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.beginPath()
|
||||
roundedRect(ctx, 2.5, 7.25, 16.5, 9.5, 2)
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
roundedRect(ctx, 19.5, 10, 2, 4, 0.6)
|
||||
ctx.fill()
|
||||
const fillWidth = Math.max(1.5, (14 * Math.min(100, Math.max(0, root.value))) / 100)
|
||||
ctx.beginPath()
|
||||
roundedRect(ctx, 4.2, 9, fillWidth, 6, 1)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
function paintPower(ctx) {
|
||||
ctx.lineWidth = 1.7
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(12, 4.5)
|
||||
ctx.lineTo(12, 11.7)
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.arc(12, 13, 6.2, Math.PI * 1.15, Math.PI * -0.15, false)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
function paintRestart(ctx) {
|
||||
ctx.lineWidth = 1.7
|
||||
ctx.beginPath()
|
||||
ctx.arc(12, 12, 7, -0.2, Math.PI * 1.55, true)
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(19, 5.2)
|
||||
ctx.lineTo(19, 9.6)
|
||||
ctx.lineTo(14.6, 9.6)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
function paintFolder(ctx) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(3.5, 8.2)
|
||||
ctx.quadraticCurveTo(3.5, 6.4, 5.3, 6.4)
|
||||
ctx.lineTo(9.4, 6.4)
|
||||
ctx.lineTo(11, 8.2)
|
||||
ctx.lineTo(18.7, 8.2)
|
||||
ctx.quadraticCurveTo(20.5, 8.2, 20.5, 10)
|
||||
ctx.lineTo(20.5, 16.8)
|
||||
ctx.quadraticCurveTo(20.5, 18.6, 18.7, 18.6)
|
||||
ctx.lineTo(5.3, 18.6)
|
||||
ctx.quadraticCurveTo(3.5, 18.6, 3.5, 16.8)
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
function paintGlobe(ctx) {
|
||||
ctx.lineWidth = 1.6
|
||||
ctx.beginPath()
|
||||
ctx.arc(12, 12, 7.25, 0, Math.PI * 2)
|
||||
ctx.stroke()
|
||||
ctx.lineWidth = 1.4
|
||||
ctx.save()
|
||||
ctx.translate(12, 12)
|
||||
ctx.scale(3.1 / 7.25, 1)
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, 7.25, 0, Math.PI * 2)
|
||||
ctx.stroke()
|
||||
ctx.restore()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(5.2, 12)
|
||||
ctx.lineTo(18.8, 12)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
function paintTerminal(ctx) {
|
||||
ctx.lineWidth = 1.7
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(7, 9.2)
|
||||
ctx.lineTo(10.4, 12)
|
||||
ctx.lineTo(7, 14.8)
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(12.2, 15.2)
|
||||
ctx.lineTo(17, 15.2)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
function paintSettings(ctx) {
|
||||
ctx.beginPath()
|
||||
ctx.arc(12, 12, 2.4, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.lineWidth = 1.6
|
||||
const ticks = [
|
||||
[12, 5.2, 12, 6.8],
|
||||
[12, 17.2, 12, 18.8],
|
||||
[5.2, 12, 6.8, 12],
|
||||
[17.2, 12, 18.8, 12],
|
||||
[7.2, 7.2, 8.3, 8.3],
|
||||
[15.7, 15.7, 16.8, 16.8],
|
||||
[16.8, 7.2, 15.7, 8.3],
|
||||
[8.3, 15.7, 7.2, 16.8]
|
||||
]
|
||||
for (let i = 0; i < ticks.length; i++) {
|
||||
const t = ticks[i]
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(t[0], t[1])
|
||||
ctx.lineTo(t[2], t[3])
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
function paintGame(ctx) {
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(5.2, 10.4)
|
||||
ctx.lineTo(18.8, 10.4)
|
||||
ctx.quadraticCurveTo(20.4, 10.4, 20.3, 12.1)
|
||||
ctx.lineTo(19.8, 15.3)
|
||||
ctx.quadraticCurveTo(19.6, 16.9, 18.5, 16.9)
|
||||
ctx.lineTo(16.3, 16.9)
|
||||
ctx.lineTo(15, 15.1)
|
||||
ctx.lineTo(10, 15.1)
|
||||
ctx.lineTo(8.7, 16.9)
|
||||
ctx.lineTo(6.5, 16.9)
|
||||
ctx.quadraticCurveTo(5.4, 16.9, 5.2, 15.3)
|
||||
ctx.lineTo(4.7, 12.1)
|
||||
ctx.quadraticCurveTo(4.6, 10.4, 5.2, 10.4)
|
||||
ctx.closePath()
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(8, 12.4)
|
||||
ctx.lineTo(8, 15.6)
|
||||
ctx.moveTo(6.4, 14)
|
||||
ctx.lineTo(9.6, 14)
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.arc(15.4, 13.2, 0.9, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.beginPath()
|
||||
ctx.arc(17.2, 15, 0.9, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
function paintPhoto(ctx) {
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.beginPath()
|
||||
roundedRect(ctx, 4, 6.2, 16, 11.6, 2)
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.arc(9, 10.2, 1.3, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(7.2, 15.8)
|
||||
ctx.lineTo(10.6, 12.4)
|
||||
ctx.lineTo(13.2, 15)
|
||||
ctx.lineTo(15.4, 12.9)
|
||||
ctx.lineTo(18.8, 15.8)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
function paintMusic(ctx) {
|
||||
ctx.lineWidth = 1.6
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(10, 16.6)
|
||||
ctx.lineTo(10, 7.4)
|
||||
ctx.lineTo(18, 5.8)
|
||||
ctx.lineTo(18, 15.2)
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.arc(8.2, 16.6, 2.2, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.beginPath()
|
||||
ctx.arc(16.2, 15.2, 2.2, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
function paintDocument(ctx) {
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(7, 5.5)
|
||||
ctx.lineTo(13.2, 5.5)
|
||||
ctx.lineTo(17.5, 10)
|
||||
ctx.lineTo(17.5, 18.5)
|
||||
ctx.quadraticCurveTo(17.5, 20, 16, 20)
|
||||
ctx.lineTo(7, 20)
|
||||
ctx.quadraticCurveTo(5.5, 20, 5.5, 18.5)
|
||||
ctx.lineTo(5.5, 7)
|
||||
ctx.quadraticCurveTo(5.5, 5.5, 7, 5.5)
|
||||
ctx.closePath()
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(13.2, 5.5)
|
||||
ctx.lineTo(13.2, 10)
|
||||
ctx.lineTo(17.5, 10)
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(8.8, 13.4)
|
||||
ctx.lineTo(15.2, 13.4)
|
||||
ctx.moveTo(8.8, 16.2)
|
||||
ctx.lineTo(13.2, 16.2)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
function roundedRect(ctx, x, y, w, h, r) {
|
||||
const radius = Math.min(r, w / 2, h / 2)
|
||||
ctx.moveTo(x + radius, y)
|
||||
ctx.lineTo(x + w - radius, y)
|
||||
ctx.quadraticCurveTo(x + w, y, x + w, y + radius)
|
||||
ctx.lineTo(x + w, y + h - radius)
|
||||
ctx.quadraticCurveTo(x + w, y + h, x + w - radius, y + h)
|
||||
ctx.lineTo(x + radius, y + h)
|
||||
ctx.quadraticCurveTo(x, y + h, x, y + h - radius)
|
||||
ctx.lineTo(x, y + radius)
|
||||
ctx.quadraticCurveTo(x, y, x + radius, y)
|
||||
ctx.closePath()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string label: ""
|
||||
property bool active: false
|
||||
property bool danger: false
|
||||
readonly property bool hovered: hover.containsMouse
|
||||
readonly property color iconColor: {
|
||||
if (danger && hovered)
|
||||
return Theme.danger
|
||||
if (active)
|
||||
return Theme.accentStrong
|
||||
if (hovered)
|
||||
return Theme.textPrimary
|
||||
return Theme.textSecondary
|
||||
}
|
||||
|
||||
default property alias icon: iconHolder.data
|
||||
|
||||
signal clicked()
|
||||
|
||||
implicitWidth: Theme.controlHeight
|
||||
implicitHeight: Theme.controlHeight
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: root.label
|
||||
Accessible.onPressAction: root.clicked()
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.radiusSmall
|
||||
color: {
|
||||
if (root.active)
|
||||
return Theme.accentSoft
|
||||
if (hover.containsMouse)
|
||||
return Theme.hoverFill
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: iconHolder
|
||||
anchors.centerIn: parent
|
||||
width: 16
|
||||
height: 16
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: hover
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
FocusFrame {
|
||||
radius: Theme.radiusSmall
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property alias text: input.text
|
||||
property alias placeholderText: placeholder.text
|
||||
property alias input: input
|
||||
|
||||
signal accepted()
|
||||
|
||||
implicitHeight: Theme.searchHeight
|
||||
implicitWidth: 200
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.name: placeholder.text
|
||||
Accessible.editable: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.radiusMedium
|
||||
color: Theme.searchFill
|
||||
border.width: 1
|
||||
border.color: input.activeFocus ? Theme.borderStrong : Theme.border
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Theme.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
spacing: 10
|
||||
|
||||
NebulaIcon {
|
||||
id: searchIcon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: "search"
|
||||
size: 16
|
||||
color: input.activeFocus ? Theme.textPrimary : Theme.textSecondary
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - searchIcon.width - row.spacing
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
id: placeholder
|
||||
anchors.fill: parent
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: qsTr("Search")
|
||||
color: Theme.textTertiary
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
visible: input.text.length === 0 && !input.preeditText
|
||||
}
|
||||
|
||||
TextInput {
|
||||
id: input
|
||||
anchors.fill: parent
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Theme.textPrimary
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
clip: true
|
||||
selectByMouse: true
|
||||
activeFocusOnTab: true
|
||||
onAccepted: root.accepted()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FocusFrame {
|
||||
target: root
|
||||
active: input.activeFocus
|
||||
radius: Theme.radiusMedium
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
// Colours from the Nebula Desktop React prototype (tokens.css).
|
||||
readonly property color background: "#0b0f16"
|
||||
readonly property color backgroundDeep: "#080b11"
|
||||
|
||||
readonly property color surface: Qt.rgba(20 / 255, 26 / 255, 36 / 255, 0.78)
|
||||
readonly property color surfaceRaised: Qt.rgba(32 / 255, 40 / 255, 54 / 255, 0.9)
|
||||
readonly property color topBarBackground: Qt.rgba(10 / 255, 13 / 255, 18 / 255, 0.55)
|
||||
|
||||
readonly property color border: Qt.rgba(232 / 255, 236 / 255, 242 / 255, 0.08)
|
||||
readonly property color borderStrong: Qt.rgba(232 / 255, 236 / 255, 242 / 255, 0.14)
|
||||
|
||||
readonly property color textPrimary: "#e8ecf2"
|
||||
readonly property color textSecondary: "#8e99ab"
|
||||
readonly property color textTertiary: "#6b7587"
|
||||
|
||||
readonly property color accent: "#7ea0c4"
|
||||
readonly property color accentStrong: "#9bb6d4"
|
||||
readonly property color accentSoft: Qt.rgba(126 / 255, 160 / 255, 196 / 255, 0.16)
|
||||
|
||||
readonly property color danger: "#c98888"
|
||||
readonly property color focus: Qt.rgba(126 / 255, 160 / 255, 196 / 255, 0.55)
|
||||
|
||||
readonly property color hoverFill: Qt.rgba(1, 1, 1, 0.06)
|
||||
readonly property color tileHover: Qt.rgba(1, 1, 1, 0.05)
|
||||
readonly property color iconWell: Qt.rgba(1, 1, 1, 0.05)
|
||||
readonly property color iconWellHighlight: Qt.rgba(1, 1, 1, 0.06)
|
||||
readonly property color searchFill: Qt.rgba(8 / 255, 11 / 255, 17 / 255, 0.45)
|
||||
readonly property color shadow: Qt.rgba(0, 0, 0, 0.42)
|
||||
|
||||
readonly property int radiusSmall: 6
|
||||
readonly property int radiusMedium: 10
|
||||
readonly property int radiusLarge: 14
|
||||
|
||||
readonly property int spacingSmall: 8
|
||||
readonly property int spacingMedium: 16
|
||||
readonly property int spacingLarge: 24
|
||||
|
||||
readonly property int animationFast: 120
|
||||
readonly property int animationNormal: 200
|
||||
|
||||
readonly property int topBarHeight: 40
|
||||
readonly property int controlHeight: 28
|
||||
readonly property int searchHeight: 36
|
||||
|
||||
readonly property int fontSize: 13
|
||||
readonly property int fontSizeApp: 12
|
||||
readonly property int fontSizeSmall: 11
|
||||
|
||||
readonly property string fontFamily: "Ubuntu"
|
||||
}
|
||||
Reference in New Issue
Block a user