import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtQuick.Shapes 1.1 import QtGraphicalEffects 1.12 import NERvGear 1.0 as NVG import NERvGear.Controls 1.0 import NERvGear.Templates 1.0 as T import NERvGear.Preferences 1.0 as P import "qrc:/qml/com.gpbeta.data.weather" as Private import "utils.js" as Utils WidgetTemplate { id: widget title: qsTr("Ordinal Scale Weather Widget") editing: styleDialog.active version: "1.0.0" defaultValues: { "Display Location": "", "Update Interval": { "Value": 1, "Unit": 1 }, "Background Color": "#ffa502", "Background Opacity": 60, "Area Opacity Difference": 17, "Icon Color": "#fefefe", "Temperature Text Settings": { "Font Color": "#f5f5f5", "Font Size": 90, "Font Name": fonts.length - 1, "Font Weight": 1, "X Offset": 33, "Y Offset": 32 }, "Area Text Settings": { "Font Color": "#f5f5f5", "Font Size": 60, "Font Name": fonts.length - 1, "Font Weight": 1, "X Offset": 0, "Y Offset": -56, "Border Margin": 40 } } readonly property var configs: widget.settings.styles readonly property real w: widget.width readonly property real h: 0.46*widget.width readonly property var fonts: Qt.fontFamilies() readonly property var fontweight: [Font.Light, Font.Normal, Font.Bold] readonly property var sfontweight: [qsTr("Light"), qsTr("Normal"), qsTr("Bold")] readonly property NVG.DataSource dataSource: NVG.DataSource { configuration: {"mode":0,"unit":"","interval":60000*configs["Update Interval"]["Value"]*(1+59*configs["Update Interval"]["Unit"]),"update":configs["Address"],"source":"nvg://weather.data.gpbeta.com/data#raw","value":"current"} } NVG.DataSourceRawOutput { id: output source: dataSource } Item { id: main width: w height: h anchors.centerIn: parent layer.enabled: true layer.effect: OpacityMask{ maskSource: Rectangle { width: w height: h color: "black" radius: h/15 } } Rectangle { id: weather_box width: w height: h anchors.left: parent.left anchors.top: parent.top opacity: configs["Background Opacity"]/100 color: configs["Background Color"] } Item { id: sdialog width: h/8 height: h/8 anchors.left: parent.left anchors.leftMargin: h/16 anchors.topMargin: h/14 anchors.top: parent.top Shape { anchors.fill: parent ShapePath { strokeWidth: parent.height/120 strokeColor: configs["Icon Color"] startX: h/44; startY: h/800 PathLine { x: h*0.10227272727272727; y: h/800 } } ShapePath { strokeWidth: parent.height/120 strokeColor: configs["Icon Color"] startX: h/44; startY: h/800 + h/32 PathLine { x: h*0.10227272727272727; y: h/800 + h/32 } } ShapePath { strokeWidth: parent.height/120 strokeColor: configs["Icon Color"] startX: h/44; startY: h/800 + h/16 PathLine { x: h*0.10227272727272727; y: h/800 + h/16 } } } MouseArea { anchors.fill: parent enabled: !styleDialog.active onClicked: { styleDialog.active = true; } } } Item { id: weather width: 0.655*w height: h anchors.left: parent.left anchors.top: parent.top Image { id: weather_mask anchors.centerIn: weather autoTransform: true visible: false source: "../Images/Weather/" + Utils.weather_codes[output.result?.iconCode ?? "44"] + ".png" } Rectangle { id: wcolor width: h/1.1 height: h/1.1 anchors.centerIn: weather visible: false color: configs["Icon Color"] } OpacityMask { anchors.fill: wcolor source: wcolor maskSource: weather_mask } } Rectangle { id: temper_box width: 0.345*w height: h anchors.right: parent.right anchors.top: parent.top opacity: configs["Background Opacity"]*configs["Area Opacity Difference"]/10000 color: "black" } Text { id: area anchors.centerIn: temper_box anchors.horizontalCenterOffset: temper_box.width*configs["Area Text Settings"]["X Offset"]/200 anchors.verticalCenterOffset: h*configs["Area Text Settings"]["Y Offset"]/200 color: configs["Area Text Settings"]["Font Color"] text: configs["Display Location"] font.pixelSize: w*0.0009*configs["Area Text Settings"]["Font Size"] font.family: fonts[configs["Area Text Settings"]["Font Name"]] font.weight: fontweight[configs["Area Text Settings"]["Font Weight"]] Rectangle { anchors.fill: parent anchors.margins: -area.font.pixelSize*configs["Area Text Settings"]["Border Margin"]/100 color: "transparent" border.color: configs["Area Text Settings"]["Font Color"] border.width: area.font.pixelSize/15 radius: area.font.pixelSize/3.5 visible: Boolean(area.text) } } Text { id: temperature anchors.centerIn: temper_box anchors.horizontalCenterOffset: temper_box.width*configs["Temperature Text Settings"]["X Offset"]/200 anchors.verticalCenterOffset: h*configs["Temperature Text Settings"]["Y Offset"]/200 color: configs["Temperature Text Settings"]["Font Color"] text: (output.result?.temperature ?? "--") + "°" font.pixelSize: w*0.002*configs["Temperature Text Settings"]["Font Size"] font.family: fonts[configs["Temperature Text Settings"]["Font Name"]] font.weight: fontweight[configs["Temperature Text Settings"]["Font Weight"]] } } menu: Menu { Action { text: qsTr("Settings") + "..." onTriggered: styleDialog.active = true } } Loader { id: styleDialog active: false sourceComponent: NVG.Window { id: window title: qsTr("Settings") visible: true minimumWidth: 450 minimumHeight: 550 width: minimumWidth height: minimumHeight transientParent: widget.NVG.View.window property var configuration Page { anchors.fill: parent header: TitleBar { text: qsTr("Ordinal Scale Weather Widget") standardButtons: Dialog.Save | Dialog.Reset onAccepted: { configuration = rootPreference.save(); widget.settings.styles = configuration; styleDialog.active = false; } onReset: { rootPreference.load({"Address": widget.settings.styles?.Address}); let cfg = rootPreference.save(); widget.settings.styles = cfg; } } ColumnLayout { id: root anchors.fill: parent anchors.margins: 16 anchors.topMargin: 0 Flickable { Layout.fillWidth: true Layout.fillHeight: true clip: true contentWidth: preferenceLayout.implicitWidth contentHeight: preferenceLayout.implicitHeight ColumnLayout { id: preferenceLayout width: root.width P.PreferenceGroup { id: rootPreference Layout.fillWidth: true label: qsTr("Configuration") onPreferenceEdited: { widget.settings.styles = rootPreference.save(); } P.DialogPreference { id: rootPref label: qsTr("Location") name: "Address" displayValue: widget.settings.styles?.Address ? widget.settings.styles.Address.address : qsTr("Click to set location.") load: function (newValue) { errorLabel.visible = false; queryField.text = ""; radioRepeater.model = newValue ? [ newValue ] : undefined; if (radioRepeater.count) radioColumn.children[0].checked = true; } save: function () { return radioGroup.checkedButton ? radioGroup.checkedButton.location : undefined; } function searchLocation() { if (!queryField.text) return; rootPref.enabled = false; errorLabel.visible = false; busyIndicator.running = true; Private.Manager.searchLocation(queryField.text) .then(function (result) { radioRepeater.model = result; if (radioRepeater.count) radioColumn.children[0].checked = true; }, function (err) { console.warn(err); errorLabel.visible = true; radioRepeater.model = undefined; }) .then(function () { rootPref.enabled = true; busyIndicator.running = false; }); } RowLayout { TextField { id: queryField Layout.fillWidth: true placeholderText: qsTr("Search: country, city, district...") onAccepted: rootPref.searchLocation() } ToolButton { icon.name: "regular:\uf002" onClicked: rootPref.searchLocation() } } Item { implicitWidth: Math.max(busyIndicator.implicitWidth, radioColumn.implicitWidth) implicitHeight: Math.max(busyIndicator.implicitHeight, radioColumn.implicitHeight) BusyIndicator { id: busyIndicator anchors.centerIn: parent running: false } Label { id: errorLabel anchors.centerIn: parent enabled: false visible: false text: qsTr("Location not found") } ButtonGroup { id: radioGroup buttons: radioColumn.children } Column { id: radioColumn width: parent.width Repeater { id: radioRepeater RadioButton { readonly property var location: modelData text: modelData.address width: radioColumn.width } } } } } P.TextFieldPreference { name: "Display Location" label: qsTr("Display Location") message: qsTr("The location to display in widget.") defaultValue: defaultValues["Display Location"] } P.DialogPreference { name: "Update Interval" label: qsTr("Update Interval") live: true displayValue: _cfg_update_interval_value.value + " " + [qsTr("Minutes"), qsTr("Hours")][_cfg_update_interval_unit.value] P.SpinPreference { id: _cfg_update_interval_value name: "Value" from: 1 to: 1440 editable: true defaultValue: defaultValues["Update Interval"]["Value"] } P.SelectPreference { id: _cfg_update_interval_unit name: "Unit" label: qsTr("Unit") defaultValue: defaultValues["Update Interval"]["Unit"] model: [qsTr("Minutes"), qsTr("Hours")] } } P.ColorPreference { name: "Background Color" label: qsTr("Background Color") defaultValue: defaultValues["Background Color"] } P.SliderPreference { name: "Background Opacity" label: qsTr("Background Opacity") from: 0 to: 100 stepSize: 1 defaultValue: defaultValues["Background Opacity"] displayValue: value + "%" } P.SliderPreference { name: "Area Opacity Difference" label: qsTr("Area Opacity Difference") from: 0 to: 100 stepSize: 1 defaultValue: defaultValues["Area Opacity Difference"] displayValue: value + "%" } P.ColorPreference { name: "Icon Color" label: qsTr("Icon Color") defaultValue: defaultValues["Icon Color"] } P.Separator {} P.DialogPreference { name: "Temperature Text Settings" label: qsTr("Temperature Text Settings") icon.name: "solid:\uf1fc" live: true P.ColorPreference { name: "Font Color" label: qsTr("Font Color") defaultValue: defaultValues["Temperature Text Settings"]["Font Color"] } P.SliderPreference { name: "Font Size" label: qsTr("Font Size") from: 1 to: 100 stepSize: 1 defaultValue: defaultValues["Temperature Text Settings"]["Font Size"] displayValue: value + "%" } P.SelectPreference { name: "Font Name" label: qsTr("Font Style") defaultValue: defaultValues["Temperature Text Settings"]["Font Name"] model: fonts } P.SelectPreference { name: "Font Weight" label: qsTr("Font Weight") defaultValue: defaultValues["Temperature Text Settings"]["Font Weight"] model: sfontweight } P.SliderPreference { name: "X Offset" label: qsTr("X Offset") from: -100 to: 100 stepSize: 1 defaultValue: defaultValues["Temperature Text Settings"]["X Offset"] displayValue: value + "%" } P.SliderPreference { name: "Y Offset" label: qsTr("Y Offset") from: -100 to: 100 stepSize: 1 defaultValue: defaultValues["Temperature Text Settings"]["Y Offset"] displayValue: value + "%" } } P.DialogPreference { name: "Area Text Settings" label: qsTr("Area Text Settings") icon.name: "solid:\uf1fc" live: true P.ColorPreference { name: "Font Color" label: qsTr("Font Color") defaultValue: defaultValues["Area Text Settings"]["Font Color"] } P.SliderPreference { name: "Font Size" label: qsTr("Font Size") from: 1 to: 100 stepSize: 1 defaultValue: defaultValues["Area Text Settings"]["Font Size"] displayValue: value + "%" } P.SelectPreference { name: "Font Name" label: qsTr("Font Style") defaultValue: defaultValues["Area Text Settings"]["Font Name"] model: fonts } P.SelectPreference { name: "Font Weight" label: qsTr("Font Weight") defaultValue: defaultValues["Area Text Settings"]["Font Weight"] model: sfontweight } P.SliderPreference { name: "X Offset" label: qsTr("X Offset") from: -100 to: 100 stepSize: 1 defaultValue: defaultValues["Area Text Settings"]["X Offset"] displayValue: value + "%" } P.SliderPreference { name: "Y Offset" label: qsTr("Y Offset") from: -100 to: 100 stepSize: 1 defaultValue: defaultValues["Area Text Settings"]["Y Offset"] displayValue: value + "%" } P.SliderPreference { name: "Border Margin" label: qsTr("Border Margin") from: 0 to: 100 stepSize: 1 defaultValue: defaultValues["Area Text Settings"]["Border Margin"] displayValue: value + "%" } } Component.onCompleted: { if(!widget.settings.styles) { configuration = rootPreference.save(); widget.settings.styles = configuration; } rootPreference.load(widget.settings.styles); configuration = widget.settings.styles; } } } } } } onClosing: { widget.settings.styles = configuration; styleDialog.active = false; } } } }