mirror of
https://gitlab.com/eql/lqml.git
synced 2025-12-06 02:30:38 -08:00
add Qt6 version of some examples (see below); revisions
'9999', 'advanced-qml-auto-reload', 'planets', 'sokoban'
This commit is contained in:
parent
ca79dec909
commit
dc29ac9084
87 changed files with 963 additions and 74 deletions
2
examples/Qt6/9999/.gitignore
vendored
Normal file
2
examples/Qt6/9999/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
69
examples/Qt6/9999/qml/main.qml
Normal file
69
examples/Qt6/9999/qml/main.qml
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Window
|
||||
|
||||
Rectangle {
|
||||
id: main
|
||||
width: 200
|
||||
height: 300 + input.height
|
||||
color: "lavender"
|
||||
|
||||
TextField {
|
||||
id: input
|
||||
objectName: "input"
|
||||
width: parent.width
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
text: "0000"
|
||||
inputMask: "9999"
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
focus: true
|
||||
|
||||
onTextChanged: Lisp.call("app:draw-number", Number(text))
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
objectName: "canvas"
|
||||
y: input.height
|
||||
width: parent.width
|
||||
height: {
|
||||
var h = Qt.inputMethod.keyboardRectangle.y
|
||||
var f = (Qt.platform.os === "android") ? Screen.devicePixelRatio : 1
|
||||
h = (h === 0) ? main.height : h / f
|
||||
return (h - input.height)
|
||||
}
|
||||
|
||||
property var ctx
|
||||
|
||||
// functions to be called from Lisp
|
||||
|
||||
function begin(color, width) {
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = color
|
||||
ctx.lineWidth = width
|
||||
ctx.lineCap = "round"
|
||||
}
|
||||
|
||||
function end() {
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
function drawLine(x1, y1, x2, y2) {
|
||||
ctx.moveTo(x1, y1)
|
||||
ctx.lineTo(x2, y2)
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2)
|
||||
var s = height / 340
|
||||
ctx.scale(s, s)
|
||||
|
||||
Lisp.call("app:paint")
|
||||
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
2
examples/Qt6/advanced-qml-auto-reload/.gitignore
vendored
Normal file
2
examples/Qt6/advanced-qml-auto-reload/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
(in-package :cl-user)
|
||||
|
||||
(defparameter *dir* *load-truename*)
|
||||
|
||||
(defvar *template* (with-open-file (s (merge-pathnames ".template.qml" *dir*))
|
||||
(let ((str (make-string (file-length s))))
|
||||
(read-sequence str s)
|
||||
str)))
|
||||
|
||||
(defun create-qml-loaders ()
|
||||
(dolist (file (directory (merge-pathnames "ext/**/*.qml" *dir*)))
|
||||
(let* ((name (namestring file))
|
||||
(p (1+ (search "/ext/" name)))
|
||||
(loader (concatenate 'string (subseq name 0 p) "." (subseq name p))))
|
||||
(unless (probe-file loader)
|
||||
(ensure-directories-exist loader)
|
||||
(with-open-file (s loader :direction :output)
|
||||
(let ((new (subseq name p)))
|
||||
(format t "~&creating .~A~%" new)
|
||||
(format s *template* (subseq name p))))))))
|
||||
|
||||
(create-qml-loaders)
|
||||
15
examples/Qt6/advanced-qml-auto-reload/qml/.ext/Page1.qml
Normal file
15
examples/Qt6/advanced-qml-auto-reload/qml/.ext/Page1.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import QtQuick
|
||||
|
||||
Loader {
|
||||
objectName: "ext/Page1.qml"
|
||||
source: objectName
|
||||
|
||||
Component.onCompleted: if (width === 0) { anchors.fill = parent }
|
||||
|
||||
function reload() {
|
||||
var src = source
|
||||
source = ""
|
||||
Engine.clearCache()
|
||||
source = src
|
||||
}
|
||||
}
|
||||
15
examples/Qt6/advanced-qml-auto-reload/qml/.ext/Page2.qml
Normal file
15
examples/Qt6/advanced-qml-auto-reload/qml/.ext/Page2.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import QtQuick
|
||||
|
||||
Loader {
|
||||
objectName: "ext/Page2.qml"
|
||||
source: objectName
|
||||
|
||||
Component.onCompleted: if (width === 0) { anchors.fill = parent }
|
||||
|
||||
function reload() {
|
||||
var src = source
|
||||
source = ""
|
||||
Engine.clearCache()
|
||||
source = src
|
||||
}
|
||||
}
|
||||
15
examples/Qt6/advanced-qml-auto-reload/qml/.ext/Page3.qml
Normal file
15
examples/Qt6/advanced-qml-auto-reload/qml/.ext/Page3.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import QtQuick
|
||||
|
||||
Loader {
|
||||
objectName: "ext/Page3.qml"
|
||||
source: objectName
|
||||
|
||||
Component.onCompleted: if (width === 0) { anchors.fill = parent }
|
||||
|
||||
function reload() {
|
||||
var src = source
|
||||
source = ""
|
||||
Engine.clearCache()
|
||||
source = src
|
||||
}
|
||||
}
|
||||
15
examples/Qt6/advanced-qml-auto-reload/qml/.ext/Repl.qml
Normal file
15
examples/Qt6/advanced-qml-auto-reload/qml/.ext/Repl.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import QtQuick
|
||||
|
||||
Loader {
|
||||
objectName: "ext/Repl.qml"
|
||||
source: objectName
|
||||
|
||||
Component.onCompleted: if (width === 0) { anchors.fill = parent }
|
||||
|
||||
function reload() {
|
||||
var src = source
|
||||
source = ""
|
||||
Engine.clearCache()
|
||||
source = src
|
||||
}
|
||||
}
|
||||
15
examples/Qt6/advanced-qml-auto-reload/qml/.template.qml
Normal file
15
examples/Qt6/advanced-qml-auto-reload/qml/.template.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import QtQuick
|
||||
|
||||
Loader {
|
||||
objectName: "~A"
|
||||
source: objectName
|
||||
|
||||
Component.onCompleted: if (width === 0) { anchors.fill = parent }
|
||||
|
||||
function reload() {
|
||||
var src = source
|
||||
source = ""
|
||||
Engine.clearCache()
|
||||
source = src
|
||||
}
|
||||
}
|
||||
15
examples/Qt6/advanced-qml-auto-reload/qml/ext/Page1.qml
Normal file
15
examples/Qt6/advanced-qml-auto-reload/qml/ext/Page1.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.lighter("red", 1.5)
|
||||
border.width: 10
|
||||
border.color: "red"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "<h2>page 1</h2>"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
examples/Qt6/advanced-qml-auto-reload/qml/ext/Page2.qml
Normal file
16
examples/Qt6/advanced-qml-auto-reload/qml/ext/Page2.qml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 100
|
||||
color: Qt.lighter("green", 3.0)
|
||||
border.width: 10
|
||||
border.color: "green"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "<h2>page 2</h2>"
|
||||
}
|
||||
}
|
||||
}
|
||||
18
examples/Qt6/advanced-qml-auto-reload/qml/ext/Page3.qml
Normal file
18
examples/Qt6/advanced-qml-auto-reload/qml/ext/Page3.qml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.width, parent.height)
|
||||
height: width
|
||||
radius: width
|
||||
color: Qt.lighter("blue", 1.7)
|
||||
border.width: 10
|
||||
border.color: "blue"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "<h2>page 3</h2>"
|
||||
}
|
||||
}
|
||||
}
|
||||
160
examples/Qt6/advanced-qml-auto-reload/qml/ext/Repl.qml
Normal file
160
examples/Qt6/advanced-qml-auto-reload/qml/ext/Repl.qml
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
Item {
|
||||
id: repl
|
||||
z: 1
|
||||
anchors.fill: parent
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
z: 1
|
||||
|
||||
Text {
|
||||
text: "REPL"
|
||||
anchors.verticalCenter: show.verticalCenter
|
||||
visible: !show.checked
|
||||
}
|
||||
|
||||
Switch {
|
||||
id: show
|
||||
|
||||
onCheckedChanged: container.enabled = checked
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: container
|
||||
opacity: 0
|
||||
|
||||
Rectangle {
|
||||
width: repl.parent.width
|
||||
height: repl.parent.height / 4
|
||||
color: "#101010"
|
||||
|
||||
ListView {
|
||||
id: replOutput
|
||||
objectName: "repl_output"
|
||||
anchors.fill: parent
|
||||
contentWidth: parent.width * 4
|
||||
clip: true
|
||||
model: replModel
|
||||
flickableDirection: Flickable.HorizontalAndVerticalFlick
|
||||
|
||||
delegate: Column {
|
||||
Rectangle {
|
||||
width: replOutput.contentWidth
|
||||
height: 1
|
||||
color: "#707070"
|
||||
visible: mLine
|
||||
}
|
||||
|
||||
Text {
|
||||
x: 2
|
||||
padding: 2
|
||||
textFormat: Text.PlainText
|
||||
font.family: fontHack.name
|
||||
font.bold: mBold
|
||||
text: mText
|
||||
color: mColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: replModel
|
||||
objectName: "repl_model"
|
||||
|
||||
function appendText(data) {
|
||||
append(data)
|
||||
replOutput.contentX = 0
|
||||
replOutput.positionViewAtEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: repl.parent.width
|
||||
|
||||
TextField {
|
||||
id: input
|
||||
objectName: "repl_input"
|
||||
width: repl.parent.width - 2 * back.width
|
||||
font.family: fontHack.name
|
||||
font.bold: true
|
||||
color: "#c0c0c0"
|
||||
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
|
||||
focus: show.checked
|
||||
palette {
|
||||
highlight: "#e0e0e0"
|
||||
highlightedText: "#101010"
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "#101010"
|
||||
border.width: 2
|
||||
border.color: "gray"
|
||||
}
|
||||
|
||||
onAccepted: Lisp.call("eval:eval-in-thread", text)
|
||||
}
|
||||
|
||||
Button {
|
||||
id: back
|
||||
objectName: "history_back"
|
||||
width: 40
|
||||
height: input.height
|
||||
focusPolicy: Qt.NoFocus
|
||||
font.family: fontIcons.name
|
||||
font.pixelSize: 26
|
||||
text: "\uf100"
|
||||
|
||||
onClicked: Lisp.call("eval:history-move", "back")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: input.height
|
||||
color: "#101010"
|
||||
}
|
||||
|
||||
Button {
|
||||
id: forward
|
||||
objectName: "history_forward"
|
||||
width: back.width
|
||||
height: input.height
|
||||
focusPolicy: Qt.NoFocus
|
||||
font.family: fontIcons.name
|
||||
font.pixelSize: 26
|
||||
text: "\uf101"
|
||||
|
||||
onClicked: Lisp.call("eval:history-move", "forward")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: repl.parent.width
|
||||
height: 1
|
||||
color: "#101010"
|
||||
}
|
||||
}
|
||||
|
||||
ProgressBar {
|
||||
objectName: "progress"
|
||||
anchors.top: container.bottom
|
||||
width: repl.width
|
||||
z: 1
|
||||
indeterminate: true
|
||||
enabled: visible
|
||||
visible: false
|
||||
}
|
||||
|
||||
states: [
|
||||
State { when: show.checked; PropertyChanges { target: container; opacity: 0.9; y: 0 }},
|
||||
State { when: !show.checked; PropertyChanges { target: container; opacity: 0.0; y: -height }}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition { NumberAnimation { properties: "opacity,y"; duration: 250; easing.type: Easing.InCubic }}
|
||||
]
|
||||
}
|
||||
49
examples/Qt6/advanced-qml-auto-reload/qml/main.qml
Normal file
49
examples/Qt6/advanced-qml-auto-reload/qml/main.qml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import ".ext/" as Ext // for single file auto reload (development)
|
||||
//import "ext/" as Ext // release version
|
||||
|
||||
Rectangle {
|
||||
id: main
|
||||
width: 300
|
||||
height: 500
|
||||
objectName: "main"
|
||||
color: "black"
|
||||
|
||||
SwipeView {
|
||||
id: view
|
||||
objectName: "view"
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
color: "white"
|
||||
|
||||
Ext.Repl {}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "swipe for next page"
|
||||
}
|
||||
}
|
||||
|
||||
// N.B. don't use Loader inside a Repeater here, won't work with single
|
||||
// file auto reload (which already uses a Loader)
|
||||
|
||||
Ext.Page1 {}
|
||||
Ext.Page2 {}
|
||||
Ext.Page3 {}
|
||||
}
|
||||
|
||||
PageIndicator {
|
||||
anchors.bottom: view.bottom
|
||||
anchors.bottomMargin: 10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
count: view.count
|
||||
currentIndex: view.currentIndex
|
||||
}
|
||||
|
||||
FontLoader { id: fontIcons; source: "fonts/fontawesome-webfont.ttf" }
|
||||
FontLoader { id: fontHack; source: "fonts/Hack-Regular.ttf" }
|
||||
FontLoader { id: fontHackBold; source: "fonts/Hack-Bold.ttf" }
|
||||
}
|
||||
2
examples/Qt6/meshtastic/.gitignore
vendored
Normal file
2
examples/Qt6/meshtastic/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.cl.meshtastic"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
|
||||
<uses-permission android:name="android.permission.USB_PERMISSION"/>
|
||||
<uses-permission android:name="android.permission.USB_HOST"/>
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true"/>
|
||||
<application
|
||||
android:name=".MeServiceApplication"
|
||||
android:label="Mesh SMS"
|
||||
android:hardwareAccelerated="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@drawable/icon">
|
||||
<activity
|
||||
android:name=".MeActivity"
|
||||
android:label="Mesh SMS"
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
|
||||
android:screenOrientation="unspecified"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/splashScreenTheme"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/device_filter"/>
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="app"/>
|
||||
<meta-data
|
||||
android:name="android.app.arguments"
|
||||
android:value=""/>
|
||||
<meta-data
|
||||
android:name="android.app.extract_android_style"
|
||||
android:value="minimal"/>
|
||||
<meta-data
|
||||
android:name="android.app.load_local_jars"
|
||||
android:value="jar/QtAndroid.jar:jar/QtAndroidExtras.jar:jar/QtAndroidBluetooth.jar:jar/QtAndroidBearer.jar"/>
|
||||
<meta-data
|
||||
android:name="android.app.static_init_classes"
|
||||
android:value="org.qtproject.qt.android.bluetooth.QtBluetoothBroadcastReceiver"/>
|
||||
<meta-data
|
||||
android:name="android.app.background_running"
|
||||
android:value="true"/>
|
||||
<meta-data
|
||||
android:name="android.app.splash_screen_drawable"
|
||||
android:resource="@drawable/splashscreen"/>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:process=":qt_service"
|
||||
android:name=".MeAndroidService"
|
||||
android:foregroundServiceType="connectedDevice"
|
||||
android:stopWithTask="true"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="service"/>
|
||||
<meta-data
|
||||
android:name="android.app.background_running"
|
||||
android:value="true"/>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
54
examples/Qt6/meshtastic/qml/ext/common/ComboBox.qml
Normal file
54
examples/Qt6/meshtastic/qml/ext/common/ComboBox.qml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
ComboBox {
|
||||
id: control
|
||||
font.pixelSize: 16
|
||||
font.family: fontText.name
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: control.width
|
||||
height: control.height
|
||||
contentItem: Text {
|
||||
text: modelData
|
||||
font: control.font
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
highlighted: control.highlightedIndex === index
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: control.displayText
|
||||
font: control.font
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
radius: 5
|
||||
color: "#f0f0f0"
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
objectName: "popup"
|
||||
y: control.height
|
||||
width: control.width + 24
|
||||
implicitHeight: contentItem.implicitHeight + 14
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight + 10
|
||||
model: control.popup.visible ? control.delegateModel : null
|
||||
currentIndex: control.highlightedIndex
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "#e0e0e0"
|
||||
border.width: 1
|
||||
border.color: "gray"
|
||||
radius: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
172
examples/Qt6/meshtastic/qml/ext/common/Help.qml
Normal file
172
examples/Qt6/meshtastic/qml/ext/common/Help.qml
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
Rectangle {
|
||||
id: help
|
||||
y: -rootItem.height
|
||||
color: "#e0e0f0"
|
||||
opacity: 0
|
||||
|
||||
Button {
|
||||
width: 42
|
||||
height: width
|
||||
z: 1 // stay on top
|
||||
anchors.right: parent.right
|
||||
flat: true
|
||||
font.family: fontText.name
|
||||
font.pixelSize: 22
|
||||
text: "x"
|
||||
|
||||
onClicked: help.enabled = false
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: flick
|
||||
anchors.fill: parent
|
||||
contentWidth: html.paintedWidth + 2 * html.padding
|
||||
contentHeight: html.paintedHeight + 50
|
||||
clip: true
|
||||
|
||||
Text {
|
||||
id: html
|
||||
width: help.width
|
||||
padding: 10
|
||||
wrapMode: Text.WordWrap
|
||||
font.family: fontText.name
|
||||
font.pixelSize: 18
|
||||
color: "#303030"
|
||||
textFormat: Text.RichText
|
||||
text: "
|
||||
<h3>
|
||||
<img src='../../img/radio.png' width=60 height=60>
|
||||
<br>Radios
|
||||
</h3>
|
||||
<h4>BLE</h4>
|
||||
<p>
|
||||
If you use more than 1 radio, switch here to the radio you want to use.
|
||||
</p>
|
||||
<p>
|
||||
To manually restart device discovery, press-and-hold on the radio icon.
|
||||
</p>
|
||||
<p>
|
||||
If your radio is not found, it may help to turn it off/on again.
|
||||
</p>
|
||||
%1
|
||||
%2
|
||||
<h4>WiFi</h4>
|
||||
%3
|
||||
<p>
|
||||
Use the Python CLI to setup your connection like this:
|
||||
</p>
|
||||
<pre>
|
||||
meshtastic \
|
||||
--set network.wifi_enabled true \
|
||||
--set network.wifi_ssid \"<name>\" \
|
||||
--set network.wifi_psk \"<password>\"
|
||||
</pre>
|
||||
<p>
|
||||
The app will ask for your radio IP, which can be found on its screen as soon as it is connected to WiFi.
|
||||
</p>
|
||||
<h3>
|
||||
<img src='../../img/group.png' width=60 height=60>
|
||||
<br>Group
|
||||
</h3>
|
||||
<p>
|
||||
Here you can see the list of all radios using your same channel name. Every radio represents a person. This view is populated automatically.
|
||||
</p>
|
||||
<p>
|
||||
Choose 'Broadcast' (on top) to send a message to every person in the group.
|
||||
</p>
|
||||
<p>
|
||||
You can set a name to every radio/person listed here, which defaults to 'Anonym': a press-and-hold on the name will enter edit mode.
|
||||
</p>
|
||||
<p>
|
||||
In the main menu you can change your channel name (which defaults to 'LongFast'). Only radios which share the same channel name are able to exchange messages.
|
||||
</p>
|
||||
<p>
|
||||
A tap on the location item on the right shows a map with all known positions of the persons. The map is cached automatically for offline usage, which means: once you visited a place on the map, it will remain available even without internet connection.
|
||||
</p>
|
||||
<p>
|
||||
To set your location manually, see 'hand' button (top right). This will override any eventually received GPS location.
|
||||
</p>
|
||||
<h3>
|
||||
<img src='../../img/message.png' width=60 height=60>
|
||||
<br>Messages
|
||||
</h3>
|
||||
<p>
|
||||
Since the message length is limited, the border of the editor will turn red if the message is too long for sending.
|
||||
</p>
|
||||
<p>
|
||||
To copy a message to the clipboard, press-and-hold it.
|
||||
</p>
|
||||
<p>
|
||||
To see the exact date of a message, tap on its hour.
|
||||
</p>
|
||||
<p>
|
||||
To delete a message, swipe it to the right and tap on the delete button.
|
||||
</p>
|
||||
<p>
|
||||
Tap on search (icon on the right) to enter/leave search mode. The search term (case insensitive) is highlighted in red.
|
||||
</p>
|
||||
<p>
|
||||
Eventual unread messages from other persons are indicated by a red circle, and the number of unread messages in <b>Group</b>.
|
||||
</p>
|
||||
<p>
|
||||
A double click on a message will switch to <b>Group</b>.
|
||||
</p>
|
||||
<p> </p>
|
||||
<h3>Advanced topics</h3>
|
||||
<h4>Simple signal strength test</h4>
|
||||
<p>
|
||||
For a trivial signal test you can use the special text message <b>:e</b> (for 'echo'), which will send back the text you sent, adding signal <b>SNR</b>/<b>RSSI</b>, position and distance. This is convenient to test signal strength from different places, and have it logged in your messages.
|
||||
</p>
|
||||
<p>
|
||||
Please note that this requires the receiver to run this app in foreground mode.
|
||||
</p>
|
||||
<h4>Save / Restore app data</h4>
|
||||
<p>
|
||||
A local web-server is included on mobile for saving and restoring all of: message database, app settings, eventually cached map tiles (for offline usage). Just use special text message <b>:w</b> (for 'web-server') and <b>:ws</b> (for 'stop web-server') after you're done.
|
||||
</p>
|
||||
<p>
|
||||
After starting the server, enter the shown URL in your desktop browser, and follow the instructions.
|
||||
</p>
|
||||
<p>
|
||||
Using this method you can easily transfer all data from one mobile device to any other device.
|
||||
</p>
|
||||
<p>
|
||||
The desktop data paths are:
|
||||
<ul>
|
||||
<li><b>Linux</b>: <br><code>/home/<user>/.local/share/cl-meshtastic/</code>
|
||||
<li><b>macOS</b>: <br><code>/Users/<user>/Library/Application Support/cl-meshtastic/</code>
|
||||
<li><b>Windows</b>: <br><code>C:\\Users\\<user>\\AppData\\Local\\cl-meshtastic\\</code>
|
||||
</ul>
|
||||
<p>
|
||||
Eventual backups are saved in above path under <code>backups/</code>. On the desktop see 'Make backup' in main menu.
|
||||
</p>
|
||||
<p>
|
||||
To autmatically restore data from a backup on the desktop, put the backup files directly in above path (that is, under <code>.../cl-meshtastic/</code>) and restart the app. The data will be restored and the (obsolete) backup files will be deleted.
|
||||
</p>".arg((Qt.platform.os === "android")
|
||||
? "<p>On some devices it may be necessary to first unpair your radio, then press-and-hold on the radio icon (to restart device discovery).</p><p><i>N.B: If you previously used a radio with the official app, you'll need to disable the radio in the official app first, otherwise it will not show up in this app.</i></p>"
|
||||
: "")
|
||||
.arg((Qt.platform.os !== "ios")
|
||||
? "<h4>USB</h4>
|
||||
<p>
|
||||
You may need to install serial drivers first (except on android), and you need to use a data USB cable.
|
||||
</p>"
|
||||
: "")
|
||||
.arg((Qt.platform.os === "ios")
|
||||
? "<p><i><font color=crimson><b>Warning:</b></font> WiFi will disconnect in background mode, and only re-connect when app is brought back to foreground (iOS only).</i></p>"
|
||||
: "")
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State { when: help.enabled; PropertyChanges { target: help; opacity: 1; y: 0; }},
|
||||
State { when: !help.enabled; PropertyChanges { target: help; opacity: 0; y: -rootItem.height; }}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition { NumberAnimation { properties: "opacity,y"; duration: 300; easing.type: Easing.InOutQuad }}
|
||||
]
|
||||
}
|
||||
42
examples/Qt6/meshtastic/qml/ext/common/Hourglass.qml
Normal file
42
examples/Qt6/meshtastic/qml/ext/common/Hourglass.qml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#ccebc5"
|
||||
visible: animation.running
|
||||
|
||||
Image {
|
||||
id: hourglass1
|
||||
anchors.centerIn: parent
|
||||
width: 40
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../../img/hourglass.png"
|
||||
}
|
||||
|
||||
Image {
|
||||
id: hourglass2
|
||||
anchors.centerIn: parent
|
||||
width: hourglass1.width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../../img/hourglass.png"
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: animation
|
||||
objectName: "hourglass"
|
||||
loops: Animation.Infinite
|
||||
running: true
|
||||
|
||||
RotationAnimation { target: hourglass1; from: 0; to: 180; duration: 1000; easing.type: Easing.InOutSine }
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation { target: hourglass1; property: "opacity"; from: 1; to: 0; duration: 1500; easing.type: Easing.InOutSine }
|
||||
NumberAnimation { target: hourglass2; property: "opacity"; from: 0; to: 1; duration: 1500; easing.type: Easing.InOutSine }
|
||||
}
|
||||
|
||||
// reset
|
||||
NumberAnimation { target: hourglass1; property: "opacity"; to: 1; duration: 0 }
|
||||
NumberAnimation { target: hourglass2; property: "opacity"; to: 0; duration: 0 }
|
||||
}
|
||||
}
|
||||
14
examples/Qt6/meshtastic/qml/ext/common/MainIcon.qml
Normal file
14
examples/Qt6/meshtastic/qml/ext/common/MainIcon.qml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import QtQuick
|
||||
|
||||
Image {
|
||||
horizontalAlignment: Image.AlignHCenter
|
||||
verticalAlignment: Image.AlignVCenter
|
||||
width: header.height
|
||||
height: width
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: swipeView.currentIndex = parent.Positioner.index
|
||||
onPressAndHold: Lisp.call("app:icon-press-and-hold", parent.objectName)
|
||||
}
|
||||
}
|
||||
87
examples/Qt6/meshtastic/qml/ext/common/MainView.qml
Normal file
87
examples/Qt6/meshtastic/qml/ext/common/MainView.qml
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import "." as Ext
|
||||
import "../group/" as Grp
|
||||
import "../messages/" as Msg
|
||||
import "../radios/" as Rad
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
property alias pageIndex: swipeView.currentIndex
|
||||
|
||||
Rectangle {
|
||||
id: header
|
||||
width: parent.width
|
||||
height: rootItem.headerHeight
|
||||
color: "#f2f2f2"
|
||||
|
||||
Row {
|
||||
height: parent.height
|
||||
spacing: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Ext.MainIcon {
|
||||
objectName: "group_icon"
|
||||
source: "../../img/group.png"
|
||||
|
||||
Rectangle {
|
||||
objectName: "unread_messages"
|
||||
width: 10
|
||||
height: width
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 7
|
||||
radius: width / 2
|
||||
color: "#ff4040"
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
|
||||
Ext.MainIcon {
|
||||
objectName: "message_icon"
|
||||
source: "../../img/message.png"
|
||||
}
|
||||
|
||||
Ext.MainIcon {
|
||||
objectName: "radio_icon"
|
||||
source: "../../img/radio.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
objectName: "main_view"
|
||||
y: header.height
|
||||
width: parent.width
|
||||
height: parent.height - header.height
|
||||
currentIndex: 1
|
||||
interactive: false
|
||||
|
||||
Grp.Group { id: group }
|
||||
Msg.Messages {}
|
||||
Rad.Radios {}
|
||||
|
||||
onCurrentIndexChanged: Lisp.call("app:view-index-changed", currentIndex)
|
||||
}
|
||||
|
||||
PageIndicator {
|
||||
id: control
|
||||
y: header.height - 12
|
||||
count: swipeView.count
|
||||
currentIndex: swipeView.currentIndex
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
delegate: Rectangle {
|
||||
width: header.height
|
||||
height: 5
|
||||
radius: width / 2
|
||||
color: "dodgerblue"
|
||||
opacity: (index === control.currentIndex) ? 1 : 0
|
||||
|
||||
Behavior on opacity { OpacityAnimator { duration: 500 }}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
examples/Qt6/meshtastic/qml/ext/common/Menu.qml
Normal file
10
examples/Qt6/meshtastic/qml/ext/common/Menu.qml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
Menu {
|
||||
width: 250
|
||||
font.family: fontText.name
|
||||
font.pixelSize: 18
|
||||
}
|
||||
|
||||
9
examples/Qt6/meshtastic/qml/ext/common/MenuItem.qml
Normal file
9
examples/Qt6/meshtastic/qml/ext/common/MenuItem.qml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
MenuItem {
|
||||
font.family: fontText.name
|
||||
font.pixelSize: 18
|
||||
}
|
||||
|
||||
67
examples/Qt6/meshtastic/qml/ext/common/Toast.qml
Normal file
67
examples/Qt6/meshtastic/qml/ext/common/Toast.qml
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
id: toast
|
||||
objectName: "toast"
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
z: 99
|
||||
width: msg.contentWidth + 70
|
||||
height: msg.contentHeight + 30
|
||||
color: "#303030"
|
||||
border.width: 2
|
||||
border.color: "white"
|
||||
radius: Math.min(25, height / 2)
|
||||
opacity: 0
|
||||
visible: false
|
||||
|
||||
function message(text, seconds) { // called from Lisp
|
||||
pause.duration = 1000 * ((seconds === 0) ? (24 * 60 * 60) : seconds)
|
||||
toast.visible = true
|
||||
msg.text = text
|
||||
anim.start()
|
||||
}
|
||||
|
||||
Text {
|
||||
id: msg
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
anchors.centerIn: parent
|
||||
color: "white"
|
||||
wrapMode: Text.WordWrap
|
||||
width: toast.parent.width - 2 * toast.radius - 10
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: toast.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: anim
|
||||
onFinished: { toast.visible = false }
|
||||
|
||||
OpacityAnimator {
|
||||
from: 0
|
||||
to: 0.8
|
||||
target: toast
|
||||
easing.type: Easing.InOutQuart
|
||||
duration: 500
|
||||
}
|
||||
|
||||
PauseAnimation {
|
||||
id: pause
|
||||
duration: 3000
|
||||
}
|
||||
|
||||
OpacityAnimator {
|
||||
from: 0.8
|
||||
to: 0
|
||||
target: toast
|
||||
easing.type: Easing.InOutQuart
|
||||
duration: 1500
|
||||
}
|
||||
}
|
||||
}
|
||||
25
examples/Qt6/meshtastic/qml/ext/dialogs/Confirm.qml
Normal file
25
examples/Qt6/meshtastic/qml/ext/dialogs/Confirm.qml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Dialogs
|
||||
|
||||
Dialog {
|
||||
anchors.centerIn: parent
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
|
||||
property alias text: message.text
|
||||
property string callback
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
id: message
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: Lisp.call(callback, true)
|
||||
onRejected: Lisp.call(callback, false)
|
||||
}
|
||||
28
examples/Qt6/meshtastic/qml/ext/dialogs/ConfirmMobile.qml
Normal file
28
examples/Qt6/meshtastic/qml/ext/dialogs/ConfirmMobile.qml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
Dialog {
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: 18
|
||||
modal: true
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
|
||||
property alias text: message.text
|
||||
property string callback
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
id: message
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: 18
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: Lisp.call(callback, true)
|
||||
onRejected: Lisp.call(callback, false)
|
||||
}
|
||||
65
examples/Qt6/meshtastic/qml/ext/dialogs/Dialogs.qml
Normal file
65
examples/Qt6/meshtastic/qml/ext/dialogs/Dialogs.qml
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: dialogs
|
||||
objectName: "dialogs"
|
||||
anchors.fill: parent
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
function message(text) {
|
||||
loader.active = false // force reload
|
||||
if (rootItem.mobile) {
|
||||
loader.source = "MessageMobile.qml"
|
||||
} else {
|
||||
loader.source = "Message.qml"
|
||||
}
|
||||
loader.active = true
|
||||
loader.item.text = text
|
||||
rootItem.showKeyboard(false)
|
||||
loader.item.open()
|
||||
}
|
||||
|
||||
function confirm(text, callback) {
|
||||
loader.active = false // force reload
|
||||
if (rootItem.mobile) {
|
||||
loader.source = "ConfirmMobile.qml"
|
||||
} else {
|
||||
loader.source = "Confirm.qml"
|
||||
}
|
||||
loader.active = true
|
||||
loader.item.text = text
|
||||
loader.item.callback = callback
|
||||
rootItem.showKeyboard(false)
|
||||
loader.item.open()
|
||||
}
|
||||
|
||||
function input(label, callback, text, placeholderText,
|
||||
maxLength, inputMask, numbersOnly,
|
||||
from, to, value) {
|
||||
loader.active = false // force reload
|
||||
if (rootItem.mobile) {
|
||||
loader.source = "InputMobile.qml"
|
||||
} else {
|
||||
loader.source = "Input.qml"
|
||||
}
|
||||
loader.active = true
|
||||
loader.item.label = label
|
||||
loader.item.callback = callback
|
||||
loader.item.text = text
|
||||
loader.item.placeholderText = placeholderText
|
||||
loader.item.maxLength = maxLength
|
||||
loader.item.inputMask = inputMask
|
||||
loader.item.numbersOnly = numbersOnly
|
||||
loader.item.from = from
|
||||
loader.item.to = to
|
||||
loader.item.value = value
|
||||
var keyboard = (text !== "") || (placeholderText !== "")
|
||||
loader.item.open()
|
||||
if (keyboard) loader.item.setFocus()
|
||||
Qt.callLater(rootItem.showKeyboard, keyboard)
|
||||
}
|
||||
}
|
||||
50
examples/Qt6/meshtastic/qml/ext/dialogs/Input.qml
Normal file
50
examples/Qt6/meshtastic/qml/ext/dialogs/Input.qml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Dialogs
|
||||
|
||||
Dialog {
|
||||
anchors.centerIn: parent
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
|
||||
property alias label: label.text
|
||||
property alias text: edit.text
|
||||
property alias placeholderText: edit.placeholderText
|
||||
property alias inputMask: edit.inputMask
|
||||
property alias maxLength: edit.maximumLength
|
||||
property alias from: spinBox.from
|
||||
property alias to: spinBox.to
|
||||
property alias value: spinBox.value
|
||||
property bool numbersOnly
|
||||
property string callback
|
||||
|
||||
function setFocus() { edit.forceActiveFocus() }
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
id: label
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
visible: (text !== "")
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: edit
|
||||
objectName: "dialog_line_edit"
|
||||
width: parent.width
|
||||
visible: !spinBox.visible
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: spinBox
|
||||
objectName: "dialog_spin_box"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: !!value
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: Lisp.call(callback, true)
|
||||
onRejected: Lisp.call(callback, false)
|
||||
}
|
||||
54
examples/Qt6/meshtastic/qml/ext/dialogs/InputMobile.qml
Normal file
54
examples/Qt6/meshtastic/qml/ext/dialogs/InputMobile.qml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
Dialog {
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: 18
|
||||
modal: true
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
|
||||
property alias label: label.text
|
||||
property alias text: edit.text
|
||||
property alias placeholderText: edit.placeholderText
|
||||
property alias inputMask: edit.inputMask
|
||||
property alias maxLength: edit.maximumLength
|
||||
property alias from: spinBox.from
|
||||
property alias to: spinBox.to
|
||||
property alias value: spinBox.value
|
||||
property bool numbersOnly
|
||||
property string callback
|
||||
|
||||
function setFocus() { edit.forceActiveFocus() }
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
id: label
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: 18
|
||||
visible: (text !== "")
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: edit
|
||||
objectName: "dialog_line_edit"
|
||||
width: parent.width
|
||||
visible: !spinBox.visible
|
||||
inputMethodHints: numbersOnly ? Qt.ImhFormattedNumbersOnly : Qt.ImhNone
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: spinBox
|
||||
objectName: "dialog_spin_box"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: !!value
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: Lisp.call(callback, true)
|
||||
onRejected: Lisp.call(callback, false)
|
||||
}
|
||||
16
examples/Qt6/meshtastic/qml/ext/dialogs/Message.qml
Normal file
16
examples/Qt6/meshtastic/qml/ext/dialogs/Message.qml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import QtQuick
|
||||
import QtQuick.Dialogs
|
||||
|
||||
Dialog {
|
||||
anchors.centerIn: parent
|
||||
title: qsTr("Info")
|
||||
standardButtons: Dialog.Ok
|
||||
|
||||
property alias text: message.text
|
||||
|
||||
Text {
|
||||
id: message
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
20
examples/Qt6/meshtastic/qml/ext/dialogs/MessageMobile.qml
Normal file
20
examples/Qt6/meshtastic/qml/ext/dialogs/MessageMobile.qml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
Dialog {
|
||||
anchors.centerIn: parent
|
||||
title: qsTr("Info")
|
||||
font.pixelSize: 18
|
||||
modal: true
|
||||
standardButtons: Dialog.Ok
|
||||
|
||||
property alias text: message.text
|
||||
|
||||
Text {
|
||||
id: message
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: 18
|
||||
}
|
||||
}
|
||||
275
examples/Qt6/meshtastic/qml/ext/group/Group.qml
Normal file
275
examples/Qt6/meshtastic/qml/ext/group/Group.qml
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import "." as Grp
|
||||
import "../common/" as Com
|
||||
|
||||
Rectangle {
|
||||
id: rect
|
||||
color: "#ccebc5"
|
||||
|
||||
Row {
|
||||
id: rowModem
|
||||
padding: 9
|
||||
spacing: 9
|
||||
|
||||
Com.ComboBox {
|
||||
id: modem
|
||||
objectName: "modem"
|
||||
width: 160
|
||||
font.pixelSize: 16
|
||||
font.family: fontText.name
|
||||
|
||||
onActivated: Lisp.call("lora:change-modem-preset")
|
||||
}
|
||||
|
||||
Text {
|
||||
height: modem.height
|
||||
font.pixelSize: 16
|
||||
font.family: fontText.name
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: qsTr("modem preset")
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: view
|
||||
objectName: "group_view"
|
||||
anchors.topMargin: rowModem.height
|
||||
anchors.bottomMargin: channel.height
|
||||
anchors.fill: parent
|
||||
anchors.margins: 9
|
||||
spacing: 9
|
||||
clip: true
|
||||
delegate: groupDelegate
|
||||
model: group
|
||||
currentIndex: -1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: channel
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
height: 28
|
||||
color: "#555"
|
||||
|
||||
Text {
|
||||
objectName: "channel_name"
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: 16
|
||||
font.family: fontText.name
|
||||
font.weight: Font.DemiBold
|
||||
color: rect.color
|
||||
text: "cl-app"
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: group
|
||||
objectName: "group"
|
||||
|
||||
// hack to define all model key _types_
|
||||
ListElement {
|
||||
radioName: ""; customName: ""; nodeNum: ""; unread: 0; current: false
|
||||
}
|
||||
|
||||
function addPerson(person) {
|
||||
// insert sorted
|
||||
var i = 1; // 0 is broadcast
|
||||
var broadcast = (count === 0)
|
||||
for (; i < count; i++) {
|
||||
if (person.customName < get(i).customName) {
|
||||
insert(i, person)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (broadcast || (i === count)) {
|
||||
append(person)
|
||||
}
|
||||
|
||||
if (person.current) {
|
||||
view.currentIndex = broadcast ? 0 : i
|
||||
view.positionViewAtIndex(view.currentIndex, ListView.Contain)
|
||||
rootItem.broadcast = broadcast
|
||||
}
|
||||
}
|
||||
|
||||
function sortRenamed(name, index) {
|
||||
var to = -1
|
||||
if (name < get(1).customName) { // 0 is broadcast
|
||||
to = 1
|
||||
} else if (name >= get(count - 1).customName) {
|
||||
to = count - 1
|
||||
} else {
|
||||
for (var i = 1; i < count; i++) {
|
||||
if ((i !== index) && (name < get(i).customName)) {
|
||||
to = (index > i) ? i : i - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (to !== -1) {
|
||||
move(index, to, 1)
|
||||
view.currentIndex = to
|
||||
view.positionViewAtIndex(to, ListView.Contain)
|
||||
}
|
||||
}
|
||||
|
||||
function radioNames() {
|
||||
var names = []
|
||||
for (var i = 0; i < count; i++) {
|
||||
names.push(get(i).radioName)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
function setUnread(name, n) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (get(i).radioName === name) {
|
||||
setProperty(i, "unread", n)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: remove(0) // see hack above
|
||||
}
|
||||
|
||||
Component {
|
||||
id: groupDelegate
|
||||
|
||||
Rectangle {
|
||||
id: delegate
|
||||
width: Math.min(265, view.width)
|
||||
height: 35
|
||||
color: (index === view.currentIndex) ? "firebrick" : "darkcyan"
|
||||
radius: height / 2
|
||||
|
||||
Rectangle {
|
||||
id: rectRadio
|
||||
x: (index === 0) ? 18 : 10
|
||||
width: (index === 0) ? 28 : 42
|
||||
height: (index === 0) ? width : 15
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: "#f0f0f0"
|
||||
radius: height / 2
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: 20
|
||||
height: width
|
||||
source: "../../img/broadcast.png"
|
||||
visible: (index === 0)
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: 12
|
||||
font.family: fontText.name
|
||||
font.weight: Font.DemiBold
|
||||
color: "black"
|
||||
text: model.radioName
|
||||
visible: (index !== 0)
|
||||
}
|
||||
}
|
||||
|
||||
function selected() {
|
||||
view.currentIndex = index
|
||||
Lisp.call("lora:change-receiver", model.nodeNum)
|
||||
rootItem.broadcast = (index === 0)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: selected()
|
||||
}
|
||||
|
||||
// custom name
|
||||
|
||||
TextField {
|
||||
id: name
|
||||
x: 58
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
leftPadding: 2
|
||||
font.pixelSize: 18
|
||||
font.family: fontText.name
|
||||
font.weight: Font.DemiBold
|
||||
color: readOnly ? "white" : "#505050"
|
||||
palette.highlight: "darkcyan"
|
||||
palette.highlightedText: "white"
|
||||
text: (model.customName === "~") ? "Anonym" : model.customName
|
||||
readOnly: true
|
||||
|
||||
background: Rectangle {
|
||||
y: 4
|
||||
width: delegate.width - 1.5 * delegate.height - rectRadio.width
|
||||
height: delegate.height - 12
|
||||
color: name.readOnly ? "transparent" : "#f0f0f0"
|
||||
border.width: 0
|
||||
}
|
||||
|
||||
onPressAndHold: {
|
||||
if (index !== 0) {
|
||||
readOnly = false
|
||||
selectAll()
|
||||
forceActiveFocus()
|
||||
Qt.inputMethod.show() // needed for SailfishOS
|
||||
}
|
||||
}
|
||||
|
||||
onEditingFinished: {
|
||||
if (!readOnly) {
|
||||
readOnly = true
|
||||
var _text = (text === "") ? "~" : text
|
||||
group.setProperty(index, "customName", _text)
|
||||
Lisp.call("group:name-edited", model.radioName, _text)
|
||||
Qt.callLater(group.sortRenamed, _text, index) // 'Qt.callLater': prevent UI thread related crash
|
||||
if (_text === "~") text = "Anonym"
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: if (readOnly) selected()
|
||||
}
|
||||
|
||||
// unread messages
|
||||
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: 8
|
||||
width: 22
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: "#ff4040"
|
||||
visible: (model.unread > 0)
|
||||
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 12
|
||||
font.weight: Font.DemiBold
|
||||
font.family: fontText.name
|
||||
text: model.unread
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Grp.Map {
|
||||
objectName: "map_view"
|
||||
anchors.fill: rect
|
||||
visible: false
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 15 * 60 * 1000 // 15 min
|
||||
repeat: true
|
||||
running: true
|
||||
onTriggered: Lisp.call("lora:get-node-config")
|
||||
}
|
||||
}
|
||||
|
||||
204
examples/Qt6/meshtastic/qml/ext/group/Map.qml
Normal file
204
examples/Qt6/meshtastic/qml/ext/group/Map.qml
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtLocation
|
||||
import QtPositioning
|
||||
import "." as Ext
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Component {
|
||||
id: mapComponent
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
property bool manualLocation: false
|
||||
property var myMarker: null
|
||||
|
||||
Map {
|
||||
id: map
|
||||
objectName: "map"
|
||||
anchors.fill: parent
|
||||
plugin: mapPlugin
|
||||
zoomLevel: 14
|
||||
|
||||
property geoCoordinate startCentroid
|
||||
|
||||
SequentialAnimation {
|
||||
id: markerAnimation
|
||||
loops: Animation.Infinite
|
||||
running: manualLocation && (myMarker !== null)
|
||||
|
||||
OpacityAnimator { target: myMarker; from: 1.0; to: 0.2; duration: 500; easing.type: Easing.InOutSine }
|
||||
OpacityAnimator { target: myMarker; from: 0.2; to: 1.0; duration: 500; easing.type: Easing.InOutSine }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: (mouse) => {
|
||||
if (manualLocation) {
|
||||
manualLocation = false
|
||||
var coord = map.toCoordinate(Qt.point(mouse.x, mouse.y))
|
||||
myMarker.coordinate = coord
|
||||
myMarker.opacity = 1
|
||||
Lisp.call("loc:position-selected", coord.latitude, coord.longitude)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function coordinate(pos) {
|
||||
return QtPositioning.coordinate(pos[0], pos[1])
|
||||
}
|
||||
|
||||
function setCenter(pos) {
|
||||
center = coordinate(pos)
|
||||
}
|
||||
|
||||
function showMarker(n, nodeNum, name, customName = "") {
|
||||
var pos = Lisp.call("loc:position*", nodeNum)
|
||||
if (pos) {
|
||||
var marker = markers.itemAt(n)
|
||||
marker.radioName = name
|
||||
marker.customName = (customName === "~") ? "" : customName
|
||||
marker.coordinate = coordinate(pos)
|
||||
marker.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
function updatePositions(myNum, myName, group) {
|
||||
var n = 0
|
||||
showMarker(n++, myNum, myName)
|
||||
for (var i = 1; i < group.count; i++) {
|
||||
var data = group.get(i)
|
||||
showMarker(n++, data.nodeNum, data.radioName, data.customName)
|
||||
}
|
||||
}
|
||||
|
||||
Plugin {
|
||||
id: mapPlugin
|
||||
name: "osm" // Open Street Map
|
||||
|
||||
// for downloading tiles
|
||||
PluginParameter {
|
||||
name: "osm.mapping.cache.directory"
|
||||
value: Lisp.call("loc:tile-path")
|
||||
}
|
||||
// for offline tiles (from cache)
|
||||
PluginParameter {
|
||||
name: "osm.mapping.offline.directory"
|
||||
value: Lisp.call("loc:tile-path")
|
||||
}
|
||||
// number tiles (instead of MB)
|
||||
PluginParameter {
|
||||
name: "osm.mapping.cache.disk.cost_strategy"
|
||||
value: "unitary"
|
||||
}
|
||||
// max number cached/offline tiles
|
||||
PluginParameter {
|
||||
name: "osm.mapping.cache.disk.size"
|
||||
value: 10000
|
||||
}
|
||||
// local tile provider (no API key needed)
|
||||
PluginParameter {
|
||||
name: "osm.mapping.providersrepository.address"
|
||||
value: Lisp.call("loc:tile-provider-path")
|
||||
}
|
||||
}
|
||||
|
||||
// handlers and shortcuts taken from Qt6 minimal map example
|
||||
PinchHandler {
|
||||
id: pinch
|
||||
target: null
|
||||
onActiveChanged: if (active) {
|
||||
map.startCentroid = map.toCoordinate(pinch.centroid.position, false)
|
||||
}
|
||||
onScaleChanged: (delta) => {
|
||||
map.zoomLevel += Math.log2(delta)
|
||||
map.alignCoordinateToPoint(map.startCentroid, pinch.centroid.position)
|
||||
}
|
||||
onRotationChanged: (delta) => {
|
||||
map.bearing -= delta
|
||||
map.alignCoordinateToPoint(map.startCentroid, pinch.centroid.position)
|
||||
}
|
||||
grabPermissions: PointerHandler.TakeOverForbidden
|
||||
}
|
||||
WheelHandler {
|
||||
id: wheel
|
||||
// workaround
|
||||
acceptedDevices: Qt.platform.pluginName === "cocoa" || Qt.platform.pluginName === "wayland"
|
||||
? PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
: PointerDevice.Mouse
|
||||
rotationScale: 1/120
|
||||
property: "zoomLevel"
|
||||
}
|
||||
DragHandler {
|
||||
id: drag
|
||||
target: null
|
||||
onTranslationChanged: (delta) => map.pan(-delta.x, -delta.y)
|
||||
}
|
||||
Shortcut {
|
||||
enabled: map.zoomLevel < map.maximumZoomLevel
|
||||
sequence: StandardKey.ZoomIn
|
||||
onActivated: map.zoomLevel = Math.round(map.zoomLevel + 1)
|
||||
}
|
||||
Shortcut {
|
||||
enabled: map.zoomLevel > map.minimumZoomLevel
|
||||
sequence: StandardKey.ZoomOut
|
||||
onActivated: map.zoomLevel = Math.round(map.zoomLevel - 1)
|
||||
}
|
||||
|
||||
// node markers
|
||||
|
||||
Ext.Markers {
|
||||
id: markers
|
||||
objectName: "markers"
|
||||
}
|
||||
}
|
||||
|
||||
// manual marker buttons
|
||||
|
||||
Ext.MapButton {
|
||||
id: hand
|
||||
objectName: "add_manual_marker"
|
||||
anchors.top: parent.top
|
||||
icon.source: "../../img/hand.png"
|
||||
visible: false
|
||||
|
||||
onClicked: {
|
||||
manualLocation = !manualLocation
|
||||
if (manualLocation) {
|
||||
if (markers.count === 0) {
|
||||
Lisp.call("loc:add-manual-marker")
|
||||
}
|
||||
myMarker = markers.itemAt(0)
|
||||
} else {
|
||||
myMarker = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ext.MapButton {
|
||||
objectName: "remove_marker"
|
||||
anchors.top: hand.bottom
|
||||
icon.source: "../../img/remove-marker.png"
|
||||
|
||||
onClicked: {
|
||||
markers.itemAt(0).visible = false
|
||||
Lisp.call("loc:remove-marker")
|
||||
visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: mapLoader
|
||||
objectName: "map_loader"
|
||||
anchors.fill: parent
|
||||
sourceComponent: mapComponent
|
||||
active: false
|
||||
}
|
||||
}
|
||||
16
examples/Qt6/meshtastic/qml/ext/group/MapButton.qml
Normal file
16
examples/Qt6/meshtastic/qml/ext/group/MapButton.qml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
RoundButton {
|
||||
z: 1
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 5
|
||||
icon.color: "#eee"
|
||||
icon.width: 28
|
||||
width: 38
|
||||
height: width
|
||||
radius: width / 2
|
||||
palette.button: "#555"
|
||||
focusPolicy: Qt.NoFocus
|
||||
}
|
||||
61
examples/Qt6/meshtastic/qml/ext/group/Markers.qml
Normal file
61
examples/Qt6/meshtastic/qml/ext/group/Markers.qml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import QtQuick
|
||||
import QtLocation
|
||||
|
||||
Repeater {
|
||||
model: Lisp.call("loc:position-count")
|
||||
|
||||
MapQuickItem {
|
||||
anchorPoint.x: image.width / 2
|
||||
anchorPoint.y: image.height
|
||||
visible: false
|
||||
|
||||
property alias radioName: radioName.text
|
||||
property alias customName: customName.text
|
||||
|
||||
sourceItem: Image {
|
||||
id: image
|
||||
width: 25
|
||||
height: width
|
||||
source: "../../img/marker.png"
|
||||
|
||||
Rectangle {
|
||||
x: -(width - image.width) / 2
|
||||
y: image.height + 5
|
||||
width: customName.width + 42
|
||||
height: 20
|
||||
color: (index === 0) ? "#ff3d00" : "darkcyan"
|
||||
radius: height / 2
|
||||
|
||||
Rectangle {
|
||||
x: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 38
|
||||
height: 16
|
||||
color: "#f0f0f0"
|
||||
radius: height / 2
|
||||
|
||||
Text {
|
||||
id: radioName
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: 12
|
||||
font.family: fontText.name
|
||||
font.weight: Font.DemiBold
|
||||
color: "black"
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: customName
|
||||
x: 44
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: paintedWidth ? (paintedWidth + 10) : 0
|
||||
font.pixelSize: 14
|
||||
font.family: fontText.name
|
||||
font.weight: Font.DemiBold
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
42
examples/Qt6/meshtastic/qml/ext/messages/EmojiView.qml
Normal file
42
examples/Qt6/meshtastic/qml/ext/messages/EmojiView.qml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
ScrollView {
|
||||
width: parent.width
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
property alias model: grid.model
|
||||
|
||||
GridView {
|
||||
id: grid
|
||||
anchors.fill: parent
|
||||
cellWidth: emojis.itemSize
|
||||
cellHeight: emojis.itemSize
|
||||
leftMargin: 2
|
||||
topMargin: 2
|
||||
clip: true
|
||||
highlightFollowsCurrentItem: false
|
||||
focus: true
|
||||
|
||||
delegate: Text {
|
||||
width: emojis.itemSize
|
||||
height: emojis.itemSize
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: emojis.itemSize - 4
|
||||
text: modelData
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: (mouse) => {
|
||||
Lisp.call("app:emoji-clicked",
|
||||
grid.itemAtIndex(grid.indexAt(mouse.x, mouse.y + grid.contentY)).text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
examples/Qt6/meshtastic/qml/ext/messages/Emojis.qml
Normal file
42
examples/Qt6/meshtastic/qml/ext/messages/Emojis.qml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import "." as Msg
|
||||
|
||||
Rectangle {
|
||||
id: emojis
|
||||
objectName: "emojis"
|
||||
height: 7 * itemSize + 2 * column.spacing + 8
|
||||
radius: 12
|
||||
color: "white"
|
||||
border.width: 1
|
||||
border.color: "gray"
|
||||
|
||||
property int itemSize: 42
|
||||
|
||||
Column {
|
||||
id: column
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
Msg.EmojiView {
|
||||
objectName: "recent_emojis"
|
||||
height: itemSize + 1
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
|
||||
|
||||
model: ["🙂","🤣","👍"]
|
||||
}
|
||||
|
||||
Msg.EmojiView {
|
||||
height: itemSize * 3 + 1
|
||||
|
||||
model: ["😃","😄","😁","😆","😅","😂","🤣","🥲","🥹","😊","😇","🙂","🙃","😉","😌","😍","🥰","😘","😗","😙","😚","😋","😛","😝","😜","🤪","🤨","🧐","🤓","😎","🥸","🤩","🥳","😏","😒","😞","😔","😟","😕","🙁","😣","😖","😫","😩","🥺","😢","😭","😮💨","😤","😠","😡","🤬","🤯","😳","🥵","🥶","😱","😨","😰","😥","😓","🫣","🤗","🫡","🤔","🫢","🤭","🤫","🤥","😶","😶🌫️","😐","😑","😬","🫨","🫠","🙄","😯","😦","😧","😮","😲","🥱","😴","🤤","😪","😵","😵💫","🫥","🤐","🥴","🤢","🤮","🤧","😷","🤒","🤕","🤑","🤠","😈","👿","👹","👺","🤡","💩","👻","💀","👽","👾","🤖","🎃","😺","😸","😹","😻","😼","😽","🙀","😿","😾"]
|
||||
}
|
||||
|
||||
Msg.EmojiView {
|
||||
height: itemSize * 3 + 1
|
||||
|
||||
model: ["👋","🤚","🖐","✋","🖖","👌","🤌","🤏","🤞","🫰","🤟","🤘","🤙","🫵","🫱","🫲","🫸","🫷","🫳","🫴","👈","👉","👆","🖕","👇","👍","👎","✊","👊","🤛","🤜","👏","🫶","🙌","👐","🤲","🤝","🙏","💅","🤳","💪","🦾","🦵","🦿","🦶","👣","👂","🦻","👃","🫀","🫁","🧠","🦷","🦴","👀","👁","👅","👄","🫦","💋","🩸"]
|
||||
}
|
||||
}
|
||||
}
|
||||
331
examples/Qt6/meshtastic/qml/ext/messages/Messages.qml
Normal file
331
examples/Qt6/meshtastic/qml/ext/messages/Messages.qml
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import "." as Msg
|
||||
|
||||
Rectangle {
|
||||
id: main
|
||||
color: hourglass.visible ? "#d2eecc" : "#e5d8bd"
|
||||
|
||||
ListView {
|
||||
id: view
|
||||
objectName: "message_view"
|
||||
anchors.topMargin: rectFind.height + 4
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: rectEdit.height + 3
|
||||
anchors.margins: 5
|
||||
model: messages
|
||||
clip: true
|
||||
visible: false
|
||||
|
||||
property int fontSize: 18
|
||||
|
||||
delegate: SwipeDelegate {
|
||||
id: swipeDelegate
|
||||
width: view.width
|
||||
height: delegate.height
|
||||
clip: true
|
||||
|
||||
onPressAndHold: Lisp.call("msg:message-press-and-hold", model.text)
|
||||
onDoubleClicked: Lisp.call("msg:swipe-to-left")
|
||||
|
||||
background: Item {
|
||||
id: delegate
|
||||
width: Math.max(text.paintedWidth, rowSender.width + view.fontSize / 4 * text.padding)
|
||||
+ 2 * text.padding + view.fontSize / 4
|
||||
height: model.hidden ? 0 : (text.contentHeight + 2 * text.padding + sender.contentHeight + 8)
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height - 4
|
||||
color: model.me ? "#f2f2f2" : "#ffffcc"
|
||||
radius: 12
|
||||
|
||||
Row {
|
||||
id: rowSender
|
||||
padding: text.padding
|
||||
spacing: padding - 2
|
||||
|
||||
AnimatedImage {
|
||||
id: semaphore
|
||||
anchors.verticalCenter: sender.verticalCenter
|
||||
anchors.verticalCenterOffset: -0.5
|
||||
width: view.fontSize / 2 - 1
|
||||
height: width
|
||||
playing: false
|
||||
source: "../../img/semaphore.gif"
|
||||
currentFrame: model.ackState ? parseInt(model.ackState.substr(2), 16) : 0 // see 'qml:hex'
|
||||
visible: model.me
|
||||
}
|
||||
|
||||
Text {
|
||||
id: sender
|
||||
font.pixelSize: 2/3 * view.fontSize
|
||||
font.family: fontText.name
|
||||
color: "#8B0000"
|
||||
text: model.senderName ? model.senderName : model.sender
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: timestamp
|
||||
x: delegate.width - contentWidth - text.padding
|
||||
y: text.padding
|
||||
font.pixelSize: 2/3 * view.fontSize
|
||||
font.family: fontText.name
|
||||
color: "#505050"
|
||||
text: model.hour
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: Lisp.call("msg:show-date", model.timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: text
|
||||
y: sender.contentHeight
|
||||
width: main.width - 10
|
||||
padding: 5
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: view.fontSize
|
||||
font.family: fontText.name
|
||||
color: "#303030"
|
||||
textFormat: Text.StyledText // for 'paintedWidth' to always work
|
||||
text: model.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView.onRemove: SequentialAnimation {
|
||||
PropertyAction {
|
||||
target: swipeDelegate
|
||||
property: "ListView.delayRemove"
|
||||
value: true
|
||||
}
|
||||
NumberAnimation {
|
||||
target: swipeDelegate
|
||||
property: "height"
|
||||
to: 0
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
PropertyAction {
|
||||
target: swipeDelegate
|
||||
property: "ListView.delayRemove"
|
||||
value: false
|
||||
}
|
||||
}
|
||||
|
||||
swipe.left: Rectangle {
|
||||
y: 2
|
||||
width: 35
|
||||
height: parent.height - 2 * y
|
||||
color: "#dd4141"
|
||||
radius: 12
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: 12
|
||||
height: width
|
||||
source: "../../img/delete.png"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: {
|
||||
var mid = model.mid
|
||||
view.model.remove(index)
|
||||
Lisp.call("db:delete-message", mid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: messages
|
||||
objectName: "messages"
|
||||
|
||||
// hack to define all model key _types_
|
||||
ListElement {
|
||||
receiver: ""; sender: ""; senderName: ""; timestamp: ""; hour: "";
|
||||
text: ""; text2: ""; mid: ""; ackState: ""; me: true; hidden: false
|
||||
}
|
||||
|
||||
function addMessage(message) { append(message) }
|
||||
|
||||
function changeState(state, mid) {
|
||||
for (var i = count - 1; i >= 0; i--) {
|
||||
if (get(i).mid === mid) {
|
||||
setProperty(i, "ackState", state)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function find(term) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
var text = get(i).text
|
||||
var highlighted = Lisp.call("msg:highlight-term", text, term)
|
||||
if (highlighted) {
|
||||
if (!get(i).text2) {
|
||||
setProperty(i, "text2", text)
|
||||
}
|
||||
setProperty(i, "text", highlighted)
|
||||
}
|
||||
setProperty(i, "hidden", !highlighted)
|
||||
}
|
||||
view.positionViewAtBeginning()
|
||||
}
|
||||
|
||||
function clearFind() {
|
||||
for (var i = 0; i < count; i++) {
|
||||
var text2 = get(i).text2
|
||||
if (text2) {
|
||||
setProperty(i, "text", text2)
|
||||
setProperty(i, "text2", "")
|
||||
}
|
||||
setProperty(i, "hidden", false)
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: remove(0) // see hack above
|
||||
}
|
||||
|
||||
// find text
|
||||
|
||||
TextField {
|
||||
id: findText
|
||||
objectName: "find_text"
|
||||
y: 1
|
||||
width: parent.width
|
||||
height: visible ? (edit.paintedHeight + 14) : 0
|
||||
font.pixelSize: view.fontSize
|
||||
font.family: fontText.name
|
||||
selectionColor: "#228ae3"
|
||||
selectedTextColor: "white"
|
||||
placeholderText: qsTr("search")
|
||||
visible: false
|
||||
|
||||
background: Rectangle {
|
||||
id: rectFind
|
||||
color: "white"
|
||||
border.width: 3
|
||||
border.color: findText.focus ? "dodgerblue" : "#c0c0c0"
|
||||
radius: 12
|
||||
}
|
||||
|
||||
onEditingFinished: Lisp.call("msg:find-text", text)
|
||||
}
|
||||
|
||||
// send text
|
||||
|
||||
Rectangle {
|
||||
id: rectEdit
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 1
|
||||
width: parent.width
|
||||
height: edit.paintedHeight + 14
|
||||
color: "white"
|
||||
border.width: 3
|
||||
border.color: edit.focus ? (edit.tooLong ? "#ff5f57" : "dodgerblue") : "#c0c0c0"
|
||||
radius: 12
|
||||
|
||||
TextArea {
|
||||
id: edit
|
||||
objectName: "edit"
|
||||
anchors.fill: parent
|
||||
textFormat: TextEdit.PlainText
|
||||
font.pixelSize: view.fontSize
|
||||
font.family: fontText.name
|
||||
selectionColor: "#228ae3"
|
||||
selectedTextColor: "white"
|
||||
wrapMode: TextEdit.Wrap
|
||||
textMargin: 0
|
||||
placeholderText: qsTr("message")
|
||||
|
||||
property bool tooLong: false
|
||||
|
||||
onLengthChanged: if (length > 150) Lisp.call("msg:check-utf8-length", text)
|
||||
Keys.onEscapePressed: emojis.visible = false
|
||||
|
||||
Image {
|
||||
y: 8
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 7
|
||||
width: edit.font.pixelSize + 1
|
||||
height: width
|
||||
source: "../../img/emoji.png"
|
||||
opacity: 0.55
|
||||
visible: edit.focus && (Qt.platform.os !== "android") && (Qt.platform.os !== "ios")
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: emojis.visible = !emojis.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: send
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.top
|
||||
anchors.margins: 3
|
||||
width: 38
|
||||
height: width
|
||||
source: "../../img/send.png"
|
||||
visible: edit.focus && !edit.tooLong
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
edit.focus = Qt.NoFocus
|
||||
Lisp.call("lora:send-message", edit.text)
|
||||
edit.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: broadcast
|
||||
anchors.right: send.left
|
||||
anchors.bottom: parent.top
|
||||
anchors.margins: 3
|
||||
width: 38
|
||||
height: width
|
||||
opacity: 0.7
|
||||
source: "../../img/broadcast.png"
|
||||
visible: send.visible && animation.running
|
||||
|
||||
SequentialAnimation {
|
||||
id: animation
|
||||
loops: Animation.Infinite
|
||||
running: rootItem.broadcast
|
||||
|
||||
ScaleAnimator {
|
||||
target: broadcast
|
||||
from: 0.8; to: 1.0
|
||||
duration: 500
|
||||
easing.type: Easing.InOutSine
|
||||
}
|
||||
|
||||
ScaleAnimator {
|
||||
target: broadcast
|
||||
from: 1.0; to: 0.8
|
||||
duration: 500
|
||||
easing.type: Easing.InOutSine
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Msg.Emojis {
|
||||
id: emojis
|
||||
anchors.bottom: rectEdit.top
|
||||
anchors.bottomMargin: -1
|
||||
width: main.width
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
38
examples/Qt6/meshtastic/qml/ext/radios/BatteryLevel.qml
Normal file
38
examples/Qt6/meshtastic/qml/ext/radios/BatteryLevel.qml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 12
|
||||
height: 25
|
||||
color: (percent() > 15) ? "#f0f0f0" : "yellow"
|
||||
radius: 2
|
||||
border.width: 1
|
||||
border.color: "#808080"
|
||||
|
||||
property string voltage
|
||||
property string level
|
||||
|
||||
function percent() { return parseInt(level, 10) }
|
||||
|
||||
Rectangle {
|
||||
x: 1
|
||||
width: parent.width - 2
|
||||
height: (parent.height - 2) * percent() / 100
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 1
|
||||
color: (percent() > 15) ? "#28c940" : "#ff5f57"
|
||||
}
|
||||
|
||||
Text {
|
||||
x: -4 - paintedWidth
|
||||
height: parent.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font.pixelSize: 11
|
||||
font.family: fontText.name
|
||||
font.weight: Font.DemiBold
|
||||
color: "white"
|
||||
text: voltage + "\n" + level
|
||||
}
|
||||
}
|
||||
|
||||
129
examples/Qt6/meshtastic/qml/ext/radios/Radios.qml
Normal file
129
examples/Qt6/meshtastic/qml/ext/radios/Radios.qml
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import "." as Rad
|
||||
import "../common/" as Com
|
||||
|
||||
Rectangle {
|
||||
id: rect
|
||||
color: "#b3cde3"
|
||||
|
||||
Row {
|
||||
id: rowRegion
|
||||
padding: 9
|
||||
spacing: 9
|
||||
|
||||
Com.ComboBox {
|
||||
id: region
|
||||
objectName: "region"
|
||||
width: 110
|
||||
font.pixelSize: 16
|
||||
font.family: fontText.name
|
||||
|
||||
onActivated: Lisp.call("lora:change-region", currentIndex ? currentText : "")
|
||||
}
|
||||
|
||||
Text {
|
||||
height: region.height
|
||||
font.pixelSize: 16
|
||||
font.family: fontText.name
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: qsTr("region")
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: view
|
||||
anchors.topMargin: rowRegion.height
|
||||
anchors.fill: parent
|
||||
anchors.margins: 9
|
||||
spacing: 9
|
||||
clip: true
|
||||
delegate: radioDelegate
|
||||
model: radios
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: radios
|
||||
objectName: "radios"
|
||||
|
||||
// hack to define all model key _types_
|
||||
ListElement {
|
||||
name: ""; ini: false; hwModel: ""; voltage: ""; batteryLevel: ""; current: false
|
||||
}
|
||||
|
||||
function addRadio(radio) {
|
||||
// prevent multiple entries on device discovery problems
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (get(i).name === radio.name) {
|
||||
return
|
||||
}
|
||||
}
|
||||
append(radio)
|
||||
if (radio.current) {
|
||||
view.currentIndex = view.count - 1
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: remove(0) // see hack above
|
||||
}
|
||||
|
||||
Component {
|
||||
id: radioDelegate
|
||||
|
||||
Rectangle {
|
||||
id: delegate
|
||||
width: Math.min(265, view.width)
|
||||
height: 35
|
||||
color: (index === view.currentIndex) ? "firebrick" : (model.ini ? "#808080" : "steelblue")
|
||||
radius: height / 2
|
||||
|
||||
Rectangle {
|
||||
x: 10
|
||||
width: 42
|
||||
height: 15
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: "#f0f0f0"
|
||||
radius: height / 2
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: 12
|
||||
font.family: fontText.name
|
||||
font.weight: Font.DemiBold
|
||||
color: "black"
|
||||
text: model.name
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
x: 58
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.pixelSize: 18
|
||||
font.family: fontText.name
|
||||
font.weight: Font.DemiBold
|
||||
color: "white"
|
||||
text: model.hwModel
|
||||
}
|
||||
|
||||
Rad.BatteryLevel {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 14
|
||||
voltage: model.voltage
|
||||
level: model.batteryLevel
|
||||
visible: !model.ini
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: {
|
||||
if (index > 0) { // current radio is 0
|
||||
view.currentIndex = index
|
||||
Lisp.call("radios:change-radio", model.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
220
examples/Qt6/meshtastic/qml/main.qml
Normal file
220
examples/Qt6/meshtastic/qml/main.qml
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Window
|
||||
import QtPositioning
|
||||
import "ext/common/" as Com
|
||||
import "ext/dialogs/" as Dlg
|
||||
|
||||
Item {
|
||||
id: rootItem
|
||||
objectName: "main"
|
||||
width: 350
|
||||
height: 550
|
||||
|
||||
property double headerHeight: 48
|
||||
property bool mobile: Lisp.call("mobile-p")
|
||||
property bool broadcast: false
|
||||
|
||||
function showKeyboard(show) {
|
||||
show ? Qt.inputMethod.show() : Qt.inputMethod.hide()
|
||||
}
|
||||
|
||||
Com.MainView { id: view }
|
||||
|
||||
Com.Menu {
|
||||
id: menu
|
||||
objectName: "menu"
|
||||
|
||||
function show() { popup(0, headerHeight) }
|
||||
|
||||
Com.MenuItem {
|
||||
objectName: "help"
|
||||
text: qsTr("Help")
|
||||
onTriggered: help.active ? help.item.enabled = !help.item.enabled : help.active = true
|
||||
}
|
||||
|
||||
Com.MenuItem {
|
||||
text: qsTr("Update group/nodes")
|
||||
onTriggered: Lisp.call("lora:get-node-config")
|
||||
enabled: (view.pageIndex === 0)
|
||||
}
|
||||
|
||||
Com.MenuItem {
|
||||
text: qsTr("Channel name...")
|
||||
onTriggered: Lisp.call("lora:edit-channel-name")
|
||||
enabled: (view.pageIndex === 0)
|
||||
}
|
||||
|
||||
Com.MenuItem {
|
||||
text: qsTr("Message font size...")
|
||||
onTriggered: Lisp.call("msg:font-size-dialog")
|
||||
enabled: (view.pageIndex === 1)
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
|
||||
Com.MenuItem {
|
||||
objectName: "share_location"
|
||||
text: qsTr("Share my location")
|
||||
checkable: true
|
||||
onTriggered: Lisp.call("loc:share-my-location", checked)
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
|
||||
Com.Menu {
|
||||
id: connection
|
||||
title: qsTr("Connection")
|
||||
enabled: (view.pageIndex === 2)
|
||||
|
||||
function changed(name) { Lisp.call("radios:connection-changed", name) }
|
||||
|
||||
Com.MenuItem {
|
||||
objectName: "BLE"
|
||||
text: "BLE"
|
||||
autoExclusive: true
|
||||
checkable: true
|
||||
checked: true
|
||||
onTriggered: connection.changed(objectName)
|
||||
}
|
||||
Com.MenuItem {
|
||||
objectName: "USB"
|
||||
text: "USB"
|
||||
autoExclusive: true
|
||||
checkable: true
|
||||
onTriggered: connection.changed(objectName)
|
||||
}
|
||||
Com.MenuItem {
|
||||
objectName: "WIFI"
|
||||
text: "WiFi"
|
||||
autoExclusive: true
|
||||
checkable: true
|
||||
onTriggered: connection.changed(objectName)
|
||||
Component.onCompleted: if (Qt.platform.os === "ios") { palette.windowText = "crimson" }
|
||||
}
|
||||
}
|
||||
|
||||
Com.MenuItem {
|
||||
text: qsTr("Reset node DB")
|
||||
onTriggered: Lisp.call("lora:reset-node-db")
|
||||
enabled: (view.pageIndex === 0)
|
||||
}
|
||||
|
||||
Com.MenuItem {
|
||||
text: qsTr("Export message DB (Lisp)")
|
||||
onTriggered: Lisp.call("db:export-to-list")
|
||||
enabled: (view.pageIndex === 1)
|
||||
}
|
||||
|
||||
Com.MenuItem {
|
||||
text: qsTr("Make backup")
|
||||
onTriggered: Lisp.call("app:make-backup")
|
||||
enabled: !mobile
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
source: "img/logo.png"
|
||||
x: 2
|
||||
y: 2
|
||||
width: headerHeight
|
||||
height: width
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: menu.show()
|
||||
}
|
||||
}
|
||||
|
||||
Image { // location icon ('Group')
|
||||
objectName: "location"
|
||||
source: "img/location.png"
|
||||
width: headerHeight
|
||||
height: width
|
||||
anchors.right: parent.right
|
||||
visible: (view.pageIndex === 0)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: Lisp.call("loc:show-map-clicked")
|
||||
}
|
||||
}
|
||||
|
||||
Image { // find icon ('Messages')
|
||||
objectName: "find"
|
||||
source: "img/find.png"
|
||||
width: headerHeight
|
||||
height: width
|
||||
anchors.right: parent.right
|
||||
visible: (view.pageIndex === 1)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: Lisp.call("msg:find-clicked")
|
||||
}
|
||||
}
|
||||
|
||||
Com.Hourglass { // animation while loading app
|
||||
id: hourglass
|
||||
}
|
||||
|
||||
AnimatedImage {
|
||||
objectName: "busy"
|
||||
anchors.centerIn: parent
|
||||
width: 34
|
||||
height: width
|
||||
z: 10
|
||||
source: "img/busy.gif"
|
||||
visible: playing
|
||||
playing: false
|
||||
}
|
||||
|
||||
// GPS
|
||||
|
||||
PositionSource {
|
||||
objectName: "position_source"
|
||||
updateInterval: 2000
|
||||
active: false
|
||||
|
||||
property double lat: 0
|
||||
property double lon: 0
|
||||
property double alt: 0
|
||||
property string time: "0" // no 'long' in JS
|
||||
|
||||
onPositionChanged: {
|
||||
if (position.latitudeValid && position.longitudeValid) {
|
||||
var coor = position.coordinate;
|
||||
lat = coor.latitude
|
||||
lon = coor.longitude
|
||||
alt = position.altitudeValid ? coor.altitude : 0
|
||||
if (position.timestamp) {
|
||||
var stime = String(position.timestamp.getTime())
|
||||
time = stime.substring(0, stime.length - 3)
|
||||
} else {
|
||||
time = "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function lastPosition() {
|
||||
return [lat, lon, alt, time]
|
||||
}
|
||||
}
|
||||
|
||||
Com.Toast {}
|
||||
|
||||
Dlg.Dialogs {}
|
||||
|
||||
Loader {
|
||||
id: help
|
||||
y: headerHeight
|
||||
width: parent.width
|
||||
height: parent.height - headerHeight
|
||||
source: "ext/common/Help.qml"
|
||||
active: false
|
||||
}
|
||||
|
||||
FontLoader { id: fontText; source: "fonts/Ubuntu.ttf" }
|
||||
FontLoader { id: fontText2; source: "fonts/Ubuntu-Medium.ttf" }
|
||||
}
|
||||
2
examples/Qt6/planets/.gitignore
vendored
Normal file
2
examples/Qt6/planets/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
193
examples/Qt6/planets/qml/main.qml
Normal file
193
examples/Qt6/planets/qml/main.qml
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: main
|
||||
objectName: "main"
|
||||
width: 300
|
||||
height: 500
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#101010"
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: view
|
||||
objectName: "view"
|
||||
anchors.fill: parent
|
||||
delegate: planetInfo
|
||||
model: planets
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: planets
|
||||
objectName: "planets"
|
||||
|
||||
// example of inline item
|
||||
//ListElement { name: "Earth"; shape: "img/earth.png"; map: "img/earth-map.jpg"; info: "..." }
|
||||
|
||||
function addPlanet(planet) { append(planet) }
|
||||
}
|
||||
|
||||
property int itemHeight: 44
|
||||
|
||||
Component {
|
||||
id: planetInfo
|
||||
|
||||
Item {
|
||||
id: wrapper
|
||||
width: view.width
|
||||
height: itemHeight
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
height: itemHeight
|
||||
color: "#303060"
|
||||
border.color: Qt.lighter(color, 1.2)
|
||||
|
||||
Text {
|
||||
x: 15
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 4
|
||||
font.pixelSize: parent.height - 22
|
||||
color: "#f0f0f0"
|
||||
text: model.name // see Lisp keyword name
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: image
|
||||
width: itemHeight - 4
|
||||
height: width
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 2
|
||||
anchors.topMargin: 2
|
||||
color: "#101010"
|
||||
|
||||
Column {
|
||||
id: imageColumn
|
||||
anchors.fill: parent
|
||||
|
||||
Image {
|
||||
id: shapeImage
|
||||
height: parent.height - mapImage.height
|
||||
width: parent.width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: model.shape // see Lisp keyword name
|
||||
}
|
||||
|
||||
Image {
|
||||
id: mapImage
|
||||
width: parent.width
|
||||
height: 0
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: model.map // see Lisp keyword name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: parent.state = "expanded"
|
||||
}
|
||||
|
||||
Item {
|
||||
id: infoView
|
||||
anchors.top: image.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
opacity: 0
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#303060"
|
||||
border.color: "#101010"
|
||||
border.width: 1
|
||||
|
||||
Flickable {
|
||||
id: flick
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
contentWidth: edit.paintedWidth
|
||||
contentHeight: edit.paintedHeight
|
||||
clip: true
|
||||
|
||||
function ensureVisible(r) {
|
||||
if (contentX >= r.x)
|
||||
contentX = r.x;
|
||||
else if (contentX+width <= r.x + r.width)
|
||||
contentX = r.x + r.width-width;
|
||||
if (contentY >= r.y)
|
||||
contentY = r.y;
|
||||
else if (contentY+height <= r.y + r.height)
|
||||
contentY = r.y + r.height-height;
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: edit
|
||||
width: flick.width
|
||||
color: "#f0f0f0"
|
||||
font.pixelSize: 16
|
||||
readOnly: true
|
||||
focus: true
|
||||
wrapMode: TextEdit.Wrap
|
||||
onCursorRectangleChanged: flick.ensureVisible(cursorRectangle)
|
||||
text: model.info // see Lisp keyword name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: closeButton
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 2
|
||||
anchors.topMargin: 2
|
||||
width: itemHeight - 4
|
||||
height: width
|
||||
color: "transparent"
|
||||
border.color: "#f0f0f0"
|
||||
opacity: 0
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
color: "#f0f0f0"
|
||||
font.bold: true
|
||||
text: "X"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: wrapper.state = ""
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "expanded"
|
||||
|
||||
PropertyChanges { target: wrapper; height: view.height }
|
||||
PropertyChanges { target: image; width: view.width; height: view.height * 2/3; anchors.rightMargin: 0; anchors.topMargin: itemHeight }
|
||||
PropertyChanges { target: mapImage; height: view.height * 1/3 }
|
||||
PropertyChanges { target: infoView; opacity: 1 }
|
||||
PropertyChanges { target: closeButton; opacity: 1 }
|
||||
PropertyChanges { target: wrapper.ListView.view; contentY: wrapper.y; interactive: false }
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
NumberAnimation {
|
||||
duration: 250
|
||||
properties: "height,width,anchors.rightMargin,anchors.topMargin,opacity,contentY"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
1
examples/Qt6/readme.md
Normal file
1
examples/Qt6/readme.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
This only contains files that differ from the Qt5 build.
|
||||
2
examples/Qt6/sokoban/.gitignore
vendored
Normal file
2
examples/Qt6/sokoban/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
16
examples/Qt6/sokoban/qml/ext/ArrowButton.qml
Normal file
16
examples/Qt6/sokoban/qml/ext/ArrowButton.qml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
Button {
|
||||
width: main.small ? 37 : 50
|
||||
height: width
|
||||
flat: true
|
||||
focusPolicy: Qt.NoFocus
|
||||
font.family: fontAwesome.name
|
||||
font.pixelSize: 1.2 * width
|
||||
opacity: 0.2
|
||||
scale: 1.2
|
||||
|
||||
onPressed: Lisp.call(this, "qsoko:button-pressed")
|
||||
}
|
||||
13
examples/Qt6/sokoban/qml/ext/Button.qml
Normal file
13
examples/Qt6/sokoban/qml/ext/Button.qml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
Button {
|
||||
width: main.small ? 32 : 50
|
||||
height: width
|
||||
font.family: fontAwesome.name
|
||||
font.pixelSize: width - 6
|
||||
opacity: 0.8
|
||||
|
||||
onPressed: Lisp.call(this, "qsoko:button-pressed")
|
||||
}
|
||||
21
examples/Qt6/sokoban/qml/ext/Dynamic.qml
Normal file
21
examples/Qt6/sokoban/qml/ext/Dynamic.qml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
objectName: "dynamic"
|
||||
|
||||
property Component box: Qt.createComponent("dynamic/Box.qml")
|
||||
property Component box2: Qt.createComponent("dynamic/Box2.qml")
|
||||
property Component player: Qt.createComponent("dynamic/Player.qml")
|
||||
property Component fixed: Qt.createComponent("dynamic/Fixed.qml")
|
||||
|
||||
function createItem(name) {
|
||||
switch (name) {
|
||||
case "object": return box.createObject()
|
||||
case "object2": return box2.createObject()
|
||||
case "player":
|
||||
case "player2": return player.createObject()
|
||||
case "wall":
|
||||
case "goal": return fixed.createObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
6
examples/Qt6/sokoban/qml/ext/NumberAnimation.qml
Normal file
6
examples/Qt6/sokoban/qml/ext/NumberAnimation.qml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import QtQuick
|
||||
|
||||
NumberAnimation {
|
||||
onRunningChanged: Lisp.call("qsoko:animation-change", running)
|
||||
}
|
||||
|
||||
6
examples/Qt6/sokoban/qml/ext/RotationAnimation.qml
Normal file
6
examples/Qt6/sokoban/qml/ext/RotationAnimation.qml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import QtQuick
|
||||
|
||||
RotationAnimation {
|
||||
onRunningChanged: Lisp.call("qsoko:animation-change", running)
|
||||
}
|
||||
|
||||
5
examples/Qt6/sokoban/qml/ext/ScaleAnimator.qml
Normal file
5
examples/Qt6/sokoban/qml/ext/ScaleAnimator.qml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import QtQuick
|
||||
|
||||
ScaleAnimator {
|
||||
onRunningChanged: Lisp.call("qsoko:animation-change", running)
|
||||
}
|
||||
6
examples/Qt6/sokoban/qml/ext/SequentialAnimation.qml
Normal file
6
examples/Qt6/sokoban/qml/ext/SequentialAnimation.qml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import QtQuick
|
||||
|
||||
SequentialAnimation {
|
||||
onRunningChanged: Lisp.call("qsoko:animation-change", running)
|
||||
}
|
||||
|
||||
18
examples/Qt6/sokoban/qml/ext/dynamic/Box.qml
Normal file
18
examples/Qt6/sokoban/qml/ext/dynamic/Box.qml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import QtQuick
|
||||
import "../" as Ext
|
||||
|
||||
Image {
|
||||
Behavior on x {
|
||||
Ext.NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.InQuart
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
Ext.NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.InQuart
|
||||
}
|
||||
}
|
||||
}
|
||||
48
examples/Qt6/sokoban/qml/ext/dynamic/Box2.qml
Normal file
48
examples/Qt6/sokoban/qml/ext/dynamic/Box2.qml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import QtQuick
|
||||
import "../" as Ext
|
||||
|
||||
Image {
|
||||
id: box2
|
||||
|
||||
Behavior on x {
|
||||
Ext.NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.InQuart
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
Ext.NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.InQuart
|
||||
}
|
||||
}
|
||||
|
||||
// final animation
|
||||
|
||||
Ext.SequentialAnimation {
|
||||
objectName: "wiggle_box"
|
||||
loops: 3
|
||||
|
||||
RotationAnimation {
|
||||
target: box2
|
||||
property: "rotation"
|
||||
from: 0; to: 30
|
||||
duration: 150
|
||||
}
|
||||
|
||||
RotationAnimation {
|
||||
target: box2
|
||||
property: "rotation"
|
||||
from: 30; to: -30
|
||||
duration: 300
|
||||
}
|
||||
|
||||
RotationAnimation {
|
||||
target: box2
|
||||
property: "rotation"
|
||||
from: -30; to: 0
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
4
examples/Qt6/sokoban/qml/ext/dynamic/Fixed.qml
Normal file
4
examples/Qt6/sokoban/qml/ext/dynamic/Fixed.qml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import QtQuick
|
||||
|
||||
Image {
|
||||
}
|
||||
30
examples/Qt6/sokoban/qml/ext/dynamic/Player.qml
Normal file
30
examples/Qt6/sokoban/qml/ext/dynamic/Player.qml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import QtQuick
|
||||
import "../" as Ext
|
||||
|
||||
Image {
|
||||
id: player
|
||||
|
||||
Behavior on x {
|
||||
Ext.NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.InOutSine
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
Ext.NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.InOutSine
|
||||
}
|
||||
}
|
||||
|
||||
// final animation
|
||||
|
||||
Ext.RotationAnimation {
|
||||
objectName: "rotate_player"
|
||||
target: player
|
||||
property: "rotation"
|
||||
from: 0; to: 360
|
||||
duration: 600
|
||||
}
|
||||
}
|
||||
157
examples/Qt6/sokoban/qml/main.qml
Normal file
157
examples/Qt6/sokoban/qml/main.qml
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Window
|
||||
import "ext/" as Ext
|
||||
|
||||
Rectangle {
|
||||
id: main
|
||||
width: Screen.desktopAvailableWidth
|
||||
height: Screen.desktopAvailableHeight
|
||||
color: Qt.darker("lightsteelblue", 1.25)
|
||||
|
||||
property bool small: (Math.max(width, height) < 1000)
|
||||
|
||||
function isLandscape() { return (Screen.primaryOrientation === Qt.LandscapeOrientation) }
|
||||
|
||||
Ext.Dynamic {}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
// adapt 'level' and 'board' scale to screen size
|
||||
scale: isLandscape()
|
||||
? ((Screen.desktopAvailableHeight - 10) / board.height)
|
||||
: ((Screen.desktopAvailableWidth - 10) / (board.width + 2 * level.width))
|
||||
|
||||
Slider {
|
||||
id: level
|
||||
objectName: "level"
|
||||
height: board.height
|
||||
orientation: Qt.Vertical
|
||||
stepSize: 1.0
|
||||
|
||||
onValueChanged: Lisp.call("qsoko:set-maze")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: board
|
||||
objectName: "board"
|
||||
width: 512; height: 512
|
||||
color: "lightsteelblue"
|
||||
}
|
||||
|
||||
// dummy to have it exactly centered
|
||||
Item {
|
||||
width: level.width
|
||||
height: level.height
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttons1
|
||||
objectName: "buttons1"
|
||||
spacing: main.small ? 10 : 15
|
||||
padding: 10
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Ext.Button {
|
||||
objectName: "previous"
|
||||
text: "\uf100"
|
||||
}
|
||||
Ext.Button {
|
||||
objectName: "next"
|
||||
text: "\uf101"
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttons2
|
||||
objectName: "buttons2"
|
||||
spacing: main.small ? 10 : 15
|
||||
padding: 10
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Ext.Button {
|
||||
objectName: "undo"
|
||||
text: "\uf112"
|
||||
}
|
||||
Ext.Button {
|
||||
objectName: "restart"
|
||||
text: "\uf0e2"
|
||||
}
|
||||
Ext.Button {
|
||||
objectName: "solve"
|
||||
text: "\uf17b"
|
||||
}
|
||||
}
|
||||
|
||||
// container for arrow buttons
|
||||
Item {
|
||||
id: arrows
|
||||
y: buttons1.y - height - (main.small ? 25 : 50)
|
||||
width: up.width * 3
|
||||
height: up.height * 3
|
||||
anchors.margins: 10
|
||||
anchors.horizontalCenter: buttons2.horizontalCenter
|
||||
|
||||
Ext.ArrowButton {
|
||||
id: up
|
||||
objectName: "up"
|
||||
text: "\uf139"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Ext.ArrowButton {
|
||||
objectName: "left"
|
||||
text: "\uf137"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Ext.ArrowButton {
|
||||
objectName: "right"
|
||||
text: "\uf138"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
Ext.ArrowButton {
|
||||
objectName: "down"
|
||||
text: "\uf13a"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
// level change animations
|
||||
|
||||
Ext.ScaleAnimator {
|
||||
objectName: "zoom_board_out"
|
||||
target: board
|
||||
from: 1.0
|
||||
to: 0.0
|
||||
duration: 250
|
||||
}
|
||||
|
||||
Ext.ScaleAnimator {
|
||||
objectName: "zoom_board_in"
|
||||
target: board
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
duration: 250
|
||||
}
|
||||
|
||||
// etc
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_Back) {
|
||||
event.accepted = true
|
||||
Lisp.call("qml:qquit")
|
||||
}
|
||||
}
|
||||
|
||||
FontLoader {
|
||||
id: fontAwesome
|
||||
source: "fonts/fontawesome-webfont.ttf"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue