diff --git a/GlosSI.sln b/GlosSI.sln
index 3defd40..5b2d049 100644
--- a/GlosSI.sln
+++ b/GlosSI.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.31729.503
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GlosSITarget", "GlosSITarget\GlosSITarget.vcxproj", "{076E263E-0687-4435-836E-8F4EF6668843}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GlosSIConfig", "GlosSIConfig\GlosSIConfig.vcxproj", "{4B42920B-3CC6-475F-A5B3-441337968483}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -21,6 +23,12 @@ Global
{076E263E-0687-4435-836E-8F4EF6668843}.Release|x64.Build.0 = Release|x64
{076E263E-0687-4435-836E-8F4EF6668843}.Release|x86.ActiveCfg = Release|Win32
{076E263E-0687-4435-836E-8F4EF6668843}.Release|x86.Build.0 = Release|Win32
+ {4B42920B-3CC6-475F-A5B3-441337968483}.Debug|x64.ActiveCfg = Debug|x64
+ {4B42920B-3CC6-475F-A5B3-441337968483}.Debug|x64.Build.0 = Debug|x64
+ {4B42920B-3CC6-475F-A5B3-441337968483}.Debug|x86.ActiveCfg = Debug|x64
+ {4B42920B-3CC6-475F-A5B3-441337968483}.Release|x64.ActiveCfg = Release|x64
+ {4B42920B-3CC6-475F-A5B3-441337968483}.Release|x64.Build.0 = Release|x64
+ {4B42920B-3CC6-475F-A5B3-441337968483}.Release|x86.ActiveCfg = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/GlosSIConfig/GlosSIConfig.vcxproj b/GlosSIConfig/GlosSIConfig.vcxproj
new file mode 100644
index 0000000..4ba32a1
--- /dev/null
+++ b/GlosSIConfig/GlosSIConfig.vcxproj
@@ -0,0 +1,118 @@
+
+
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {4B42920B-3CC6-475F-A5B3-441337968483}
+ QtVS_v304
+ 10.0.19041.0
+ 10.0.19041.0
+ $(MSBuildProjectDirectory)\QtMsBuild
+
+
+
+ Application
+ v142
+
+
+ Application
+ v142
+
+
+
+
+
+
+ 6.2.0
+ quick
+ debug
+
+
+ 6.2.0
+ quick
+ release
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stdcpp20
+ /Zc:__cplusplus %(AdditionalOptions)
+
+
+
+
+ stdcpp20
+ /Zc:__cplusplus %(AdditionalOptions)
+
+
+
+
+ true
+ true
+ ProgramDatabase
+ Disabled
+ MultiThreadedDebugDLL
+
+
+ Windows
+ true
+
+
+
+
+ true
+ true
+ None
+ MaxSpeed
+ MultiThreadedDLL
+
+
+ Windows
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/GlosSIConfig/GlosSIConfig.vcxproj.filters b/GlosSIConfig/GlosSIConfig.vcxproj.filters
new file mode 100644
index 0000000..eb79931
--- /dev/null
+++ b/GlosSIConfig/GlosSIConfig.vcxproj.filters
@@ -0,0 +1,53 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+ {639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}
+ ts
+
+
+ {4abd6243-b275-40c0-8927-27cebe0e7b5a}
+
+
+
+
+ Source Files
+
+
+ Resource Files
+
+
+ Source Files
+
+
+
+
+ qml
+
+
+ qml
+
+
+
+
+ Header Files
+
+
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/GlosSIConfig/UIModel.cpp b/GlosSIConfig/UIModel.cpp
new file mode 100644
index 0000000..9032f32
--- /dev/null
+++ b/GlosSIConfig/UIModel.cpp
@@ -0,0 +1,40 @@
+#include "UIModel.h"
+
+#include
+
+UIModel::UIModel()
+{
+ auto path = std::filesystem::temp_directory_path()
+ .parent_path()
+ .parent_path()
+ .parent_path();
+
+ path /= "Roaming";
+ path /= "GlosSI";
+ if (!std::filesystem::exists(path))
+ std::filesystem::create_directories(path);
+
+ config_path_ = path;
+ config_dir_name_ = (path /= "Targets").string().data();
+}
+
+QStringList UIModel::getTargetList() const
+{
+ QDir dir(config_dir_name_);
+ auto entries = dir.entryList(QDir::Files, QDir::SortFlag::Name);
+ entries.removeIf([](const auto& entry) {
+ return entry.endsWith(".json");
+ });
+ QStringList res;
+ std::ranges::transform(entries, std::back_inserter(res), [](const auto& entry)
+ {
+ return entry.mid(0, entry.lastIndexOf(".json"));
+ });
+ res.push_back("Debug");
+ return res;
+}
+
+bool UIModel::getIsWindows() const
+{
+ return is_windows_;
+}
diff --git a/GlosSIConfig/UIModel.h b/GlosSIConfig/UIModel.h
new file mode 100644
index 0000000..af4b5b2
--- /dev/null
+++ b/GlosSIConfig/UIModel.h
@@ -0,0 +1,42 @@
+/*
+Copyright 2021 Peter Repukat - FlatspotSoftware
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#pragma once
+#include
+#include
+
+class UIModel : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(bool isWindows READ getIsWindows CONSTANT)
+
+public:
+ UIModel();
+
+ Q_INVOKABLE QStringList getTargetList() const;
+
+ bool getIsWindows() const;
+
+private:
+ std::filesystem::path config_path_;
+ QString config_dir_name_;
+#ifdef _WIN32
+ bool is_windows_ = true;
+#else
+ bool is_windows_ = false;
+#endif
+};
+
diff --git a/GlosSIConfig/main.cpp b/GlosSIConfig/main.cpp
new file mode 100644
index 0000000..5aff396
--- /dev/null
+++ b/GlosSIConfig/main.cpp
@@ -0,0 +1,144 @@
+/*
+Copyright 2021 Peter Repukat - FlatspotSoftware
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#include
+#include
+#include
+#include
+
+#ifdef _WIN32
+#define NOMINMAX
+#include
+#include
+#pragma comment(lib, "Dwmapi.lib")
+#endif
+
+#include "UIModel.h"
+#include "qml/WinEventFilter.h"
+
+#ifdef _WIN32
+// Some undocument stuff to enable aero on win10 or higher...
+enum AccentState
+{
+ ACCENT_DISABLED = 0,
+ ACCENT_ENABLE_GRADIENT = 1,
+ ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
+ ACCENT_ENABLE_BLURBEHIND = 3,
+ ACCENT_INVALID_STATE = 4
+};
+struct AccentPolicy
+{
+
+ AccentState AccentState;
+ int AccentFlags;
+ int GradientColor;
+ int AnimationId;
+};
+
+enum WindowCompositionAttribute
+{
+ // ...
+ WCA_ACCENT_POLICY = 19
+ // ...
+};
+struct WindowCompositionAttributeData
+{
+ WindowCompositionAttribute Attribute;
+ void* Data;
+ int SizeOfData;
+};
+
+typedef HRESULT(__stdcall* PSetWindowCompositionAttribute)(HWND hwnd, WindowCompositionAttributeData* pattrs);
+
+#endif
+
+
+int main(int argc, char* argv[])
+{
+#if defined(Q_OS_WIN)
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+#endif
+
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ UIModel uimodel;
+ engine.rootContext()->setContextProperty("uiModel", QVariant::fromValue(&uimodel));
+ engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
+ if (engine.rootObjects().isEmpty())
+ return -1;
+#ifdef _WIN32
+
+ // As a Qt.Frameless window sucks ass, and doesn't support Aerosnap and resizing and all that stuff is cumbersome on windows...
+ // Use good old dwm...
+
+ // First get window from QML.
+ auto window = qobject_cast(engine.rootObjects()[0]);
+ const HWND hwnd = reinterpret_cast(window->winId());
+ // Clear it's title bar
+ // NO!(!!) We wan't to keep WS_THICKFRAME, as that's what gives us a shadow, aerosnap, proper resizing, win11 round corners
+ // ...and all that good stuff!
+ auto style = GetWindowLong(hwnd, GWL_STYLE);
+ style &= ~(WS_CAPTION | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU);
+ SetWindowLong(hwnd, GWL_STYLE, style);
+
+ // Enable blurbehind (not needed?) anyway gives nice background on win7 and 8
+ DWM_BLURBEHIND bb{ .dwFlags = DWM_BB_ENABLE, .fEnable = true, .hRgnBlur = nullptr };
+ DwmEnableBlurBehindWindow(hwnd, &bb);
+
+ //// undoc stuff for aero >= Win10
+ //// leave for now... performance is crap!
+ //// for now we have to live with unblurred transparency. lol
+ //AccentPolicy accPol = { .AccentState = ACCENT_ENABLE_BLURBEHIND };
+ //WindowCompositionAttributeData data = {
+ // .Attribute = WindowCompositionAttribute::WCA_ACCENT_POLICY,
+ // .Data = &accPol,
+ // .SizeOfData = sizeof(accPol)
+ //};
+ //auto user32dll = GetModuleHandle(L"user32.dll");
+ //if (user32dll) {
+ // PSetWindowCompositionAttribute SetWindowCompositionAttribute = (
+ // reinterpret_cast(GetProcAddress(user32dll, "SetWindowCompositionAttribute"))
+ // );
+ // if (SetWindowCompositionAttribute)
+ // {
+ // SetWindowCompositionAttribute(hwnd, &data);
+ // }
+ //}
+
+ // extend the frame fully into the client area => draw all outside the window frame.
+ MARGINS margins = { -1 };
+ DwmExtendFrameIntoClientArea(hwnd, &margins);
+
+ // To Fix top window frame, install native event filter
+ // basically the Qt. equivalent of having a own WndProc
+ // for more info see WinEventFilter.h
+ auto filter = std::make_shared();
+ app.installNativeEventFilter(filter.get());
+
+ RECT rcClient;
+ GetWindowRect(hwnd, &rcClient);
+
+ // Inform the application of the frame change.
+ SetWindowPos(hwnd,
+ NULL,
+ rcClient.left, rcClient.top,
+ rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
+ SWP_FRAMECHANGED);
+
+#endif
+
+ return app.exec();
+}
diff --git a/GlosSIConfig/qml.qrc b/GlosSIConfig/qml.qrc
new file mode 100644
index 0000000..0106af5
--- /dev/null
+++ b/GlosSIConfig/qml.qrc
@@ -0,0 +1,6 @@
+
+
+ qml/main.qml
+ qml/RPane.qml
+
+
diff --git a/GlosSIConfig/qml/RPane.qml b/GlosSIConfig/qml/RPane.qml
new file mode 100644
index 0000000..eae2d70
--- /dev/null
+++ b/GlosSIConfig/qml/RPane.qml
@@ -0,0 +1,21 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.9
+import QtQuick.Controls.Material 2.9
+import QtQuick.Controls.Material.impl 2.9
+
+Pane {
+ id: control
+ property int radius: 0
+ property color color: control.Material.backgroundColor
+ property real bgOpacity: 1
+ background: Rectangle {
+ color: parent.color
+ opacity: parent.bgOpacity
+ radius: control.Material.elevation > 0 ? control.radius : 0
+
+ layer.enabled: control.enabled && control.Material.elevation > 0
+ layer.effect: ElevationEffect {
+ elevation: control.Material.elevation
+ }
+ }
+}
\ No newline at end of file
diff --git a/GlosSIConfig/qml/WinEventFilter.h b/GlosSIConfig/qml/WinEventFilter.h
new file mode 100644
index 0000000..b8a8fff
--- /dev/null
+++ b/GlosSIConfig/qml/WinEventFilter.h
@@ -0,0 +1,32 @@
+#pragma once
+#include
+#include
+#define NOMINMAX
+#include
+
+class WinEventFilter : public QAbstractNativeEventFilter
+{
+public:
+ WinEventFilter() = default;
+
+ /*
+ * When having the DWM frame fully extended into client area
+ * ever since WIN10 a 6px border is displayed on top.
+ * to remove that one has to catch the WM_NCCALCSIZE event and re-calculate the window-rect
+ * https://stackoverflow.com/a/2135120/5106063
+ * https://docs.microsoft.com/en-us/windows/win32/dwm/customframe
+ */
+ bool nativeEventFilter(const QByteArray& eventType, void* message, qintptr* result) override
+ {
+ if (QString(eventType) == "windows_generic_MSG") {
+ auto msg = static_cast(message)->message;
+ auto lParam = static_cast(message)->lParam;
+ if (msg == WM_NCCALCSIZE)
+ {
+ auto sz = reinterpret_cast(lParam);
+ sz->rgrc[0].top -= 6;
+ }
+ }
+ return false;
+ }
+};
diff --git a/GlosSIConfig/qml/main.qml b/GlosSIConfig/qml/main.qml
new file mode 100644
index 0000000..7a592b5
--- /dev/null
+++ b/GlosSIConfig/qml/main.qml
@@ -0,0 +1,197 @@
+/*
+Copyright 2021 Peter Repukat - FlatspotSoftware
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+import QtQuick 6.2
+import QtQuick.Layouts 6.2
+import QtQuick.Controls.Material 6.2
+import QtQuick.Dialogs
+
+Window {
+ id: window
+ visible: true
+ width: 1280
+ height: 719
+ Material.theme: Material.Dark
+ Material.accent: Material.color(Material.Blue, Material.Shade900)
+
+ property bool itemSelected: false;
+
+ title: qsTr("GlosSI - Config")
+
+ color: colorAlpha(Material.background, 0.98)
+
+ function toggleMaximized() {
+ if (window.visibility === Window.Maximized || window.visibility === Window.FullScreen) {
+ window.visibility = Window.Windowed
+ } else {
+ window.visibility = Window.Maximized
+ }
+ }
+
+ function colorAlpha(color, alpha) {
+ return Qt.rgba(color.r, color.g, color.b, alpha);
+ }
+
+ Rectangle {
+ id: titleBar
+ visible: uiModel.isWindows
+ color: colorAlpha(Qt.darker(Material.background, 1.1), 0.90)
+ height: visible ? 24 : 0
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ TapHandler {
+ onTapped: if (tapCount === 2) toggleMaximized()
+ gesturePolicy: TapHandler.DragThreshold
+ }
+ DragHandler {
+ grabPermissions: TapHandler.CanTakeOverFromAnything
+ onActiveChanged: if (active) { window.startSystemMove(); }
+ }
+
+ Label {
+ text: window.title
+ font.bold: true
+ anchors.leftMargin: 16
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ RowLayout {
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 0
+ ToolButton {
+ text: "🗕"
+ onClicked: window.showMinimized();
+ }
+ ToolButton {
+ text: window.visibility === Window.Maximized || window.visibility === Window.FullScreen ? "🗗" : "🗖"
+ onClicked: window.toggleMaximized()
+ }
+ ToolButton {
+ id: closbtn
+ text: "🗙"
+ onClicked: window.close()
+ background: Rectangle {
+ implicitWidth: 32
+ implicitHeight: 32
+ radius: 16
+ color: Qt.darker("red", closbtn.enabled && (closbtn.checked || closbtn.highlighted) ? 1.6 : 1.2)
+ opacity: closbtn.hovered ? 0.5 : 0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 350
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ Item {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: titleBar.bottom
+ anchors.bottom: parent.bottom
+
+ RPane {
+ id: existingTargetsPane
+ anchors.left: parent.left
+ width:window.width / 3.301 + 16
+ Component.onCompleted: console.log(width)
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ Material.elevation: 6
+ anchors.leftMargin: -16
+ radius: 16
+ color: Qt.lighter(Material.background, 1.6)
+ bgOpacity: 0.8
+
+ Item {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: 16
+ clip: true
+ ListView {
+ anchors.fill: parent
+ spacing: 0
+ model: uiModel.getTargetList();
+ delegate: Item {
+ width: parent.width
+ height: lbl.height + lbl.anchors.topMargin + lbl.anchors.bottomMargin
+ // TODO: Left size App icon
+ Label {
+ id: lbl
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.rightMargin: 48
+ anchors.topMargin: 8
+ anchors.bottomMargin: 8
+ anchors.leftMargin: 4
+ anchors.verticalCenter: parent.verticalCenter
+ text: modelData
+ font.pixelSize: 16
+ }
+ // TODO: Right side icon if in steam
+ Rectangle {
+ width: parent.width
+ height: 1
+ anchors.bottom: parent.bottom
+ color: Qt.rgba(1,1,1,0.3)
+ }
+ }
+ }
+ }
+ }
+
+ RoundButton {
+ id: addBtn
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.margins: 24
+ width: 64
+ height: 64
+ text: "+"
+ contentItem: Label {
+ anchors.centerIn: parent
+ text: addBtn.text
+ font.pixelSize: 32
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+ highlighted: true
+ onClicked: fileDialog.open();
+ }
+
+ FileDialog {
+ id: fileDialog
+ title: qsTr("Please choose a Program to Launch")
+ nameFilters: uiModel.isWindows ? ["Executable files (*.exe *.bat *.ps1)"] : []
+ onAccepted: {
+ console.log("You chose: " + fileDialog.selectedFile)
+ }
+ onRejected: {
+ console.log("Canceled")
+ }
+ }
+
+ }
+}
diff --git a/glosc.code-workspace b/glosc.code-workspace
index 18308c3..ba57d0b 100644
--- a/glosc.code-workspace
+++ b/glosc.code-workspace
@@ -1,5 +1,8 @@
{
"folders": [
+ {
+ "path": "GlosSIConfig"
+ },
{
"path": "GlosSITarget"
},