forked from quickshell/quickshell
		
	ui: add native reload popup
This commit is contained in:
		
							parent
							
								
									5c1d600e84
								
							
						
					
					
						commit
						8124a63ee4
					
				
					 13 changed files with 475 additions and 4 deletions
				
			
		| 
						 | 
					@ -10,6 +10,7 @@ add_subdirectory(ipc)
 | 
				
			||||||
add_subdirectory(window)
 | 
					add_subdirectory(window)
 | 
				
			||||||
add_subdirectory(io)
 | 
					add_subdirectory(io)
 | 
				
			||||||
add_subdirectory(widgets)
 | 
					add_subdirectory(widgets)
 | 
				
			||||||
 | 
					add_subdirectory(ui)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (CRASH_REPORTER)
 | 
					if (CRASH_REPORTER)
 | 
				
			||||||
	add_subdirectory(crash)
 | 
						add_subdirectory(crash)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,6 +45,8 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
 | 
				
			||||||
	QsEnginePlugin::runConstructGeneration(*this);
 | 
						QsEnginePlugin::runConstructGeneration(*this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EngineGeneration::EngineGeneration(): EngineGeneration(QDir(), QmlScanner()) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EngineGeneration::~EngineGeneration() {
 | 
					EngineGeneration::~EngineGeneration() {
 | 
				
			||||||
	if (this->engine != nullptr) {
 | 
						if (this->engine != nullptr) {
 | 
				
			||||||
		qFatal() << this << "destroyed without calling destroy()";
 | 
							qFatal() << this << "destroyed without calling destroy()";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,6 +28,7 @@ class EngineGeneration: public QObject {
 | 
				
			||||||
	Q_OBJECT;
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
 | 
						explicit EngineGeneration();
 | 
				
			||||||
	explicit EngineGeneration(const QDir& rootPath, QmlScanner scanner);
 | 
						explicit EngineGeneration(const QDir& rootPath, QmlScanner scanner);
 | 
				
			||||||
	~EngineGeneration() override;
 | 
						~EngineGeneration() override;
 | 
				
			||||||
	Q_DISABLE_COPY_MOVE(EngineGeneration);
 | 
						Q_DISABLE_COPY_MOVE(EngineGeneration);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -173,6 +173,14 @@ public:
 | 
				
			||||||
	Q_INVOKABLE [[nodiscard]] QString statePath(const QString& path) const;
 | 
						Q_INVOKABLE [[nodiscard]] QString statePath(const QString& path) const;
 | 
				
			||||||
	/// Equivalent to `${Quickshell.cacheDir}/${path}`
 | 
						/// Equivalent to `${Quickshell.cacheDir}/${path}`
 | 
				
			||||||
	Q_INVOKABLE [[nodiscard]] QString cachePath(const QString& path) const;
 | 
						Q_INVOKABLE [[nodiscard]] QString cachePath(const QString& path) const;
 | 
				
			||||||
 | 
						/// When called from @@reloadCompleted() or @@reloadFailed(), prevents the
 | 
				
			||||||
 | 
						/// default reload popup from displaying.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// The popup can also be blocked by setting `QS_NO_RELOAD_POPUP=1`.
 | 
				
			||||||
 | 
						Q_INVOKABLE void inhibitReloadPopup() { this->mInhibitReloadPopup = true; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
 | 
				
			||||||
 | 
						[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] QString shellRoot() const;
 | 
						[[nodiscard]] QString shellRoot() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -212,6 +220,8 @@ private slots:
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	QuickshellGlobal(QObject* parent = nullptr);
 | 
						QuickshellGlobal(QObject* parent = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool mInhibitReloadPopup = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static qsizetype screensCount(QQmlListProperty<QuickshellScreenInfo>* prop);
 | 
						static qsizetype screensCount(QQmlListProperty<QuickshellScreenInfo>* prop);
 | 
				
			||||||
	static QuickshellScreenInfo* screenAt(QQmlListProperty<QuickshellScreenInfo>* prop, qsizetype i);
 | 
						static QuickshellScreenInfo* screenAt(QQmlListProperty<QuickshellScreenInfo>* prop, qsizetype i);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,8 +12,10 @@
 | 
				
			||||||
#include <qtmetamacros.h>
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
#include <qurl.h>
 | 
					#include <qurl.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../ui/reload_popup.hpp"
 | 
				
			||||||
#include "../window/floatingwindow.hpp"
 | 
					#include "../window/floatingwindow.hpp"
 | 
				
			||||||
#include "generation.hpp"
 | 
					#include "generation.hpp"
 | 
				
			||||||
 | 
					#include "instanceinfo.hpp"
 | 
				
			||||||
#include "qmlglobal.hpp"
 | 
					#include "qmlglobal.hpp"
 | 
				
			||||||
#include "scan.hpp"
 | 
					#include "scan.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,6 +70,18 @@ void RootWrapper::reloadGraph(bool hard) {
 | 
				
			||||||
		qWarning().noquote() << error;
 | 
							qWarning().noquote() << error;
 | 
				
			||||||
		generation->destroy();
 | 
							generation->destroy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (this->generation != nullptr) {
 | 
				
			||||||
 | 
								auto showPopup = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (this->generation->qsgInstance != nullptr) {
 | 
				
			||||||
 | 
									this->generation->qsgInstance->clearReloadPopupInhibit();
 | 
				
			||||||
 | 
									emit this->generation->qsgInstance->reloadFailed(error);
 | 
				
			||||||
 | 
									showPopup = !this->generation->qsgInstance->isReloadPopupInhibited();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (showPopup) qs::ui::ReloadPopup::spawnPopup(InstanceInfo::CURRENT.instanceId, true, error);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (this->generation != nullptr && this->generation->qsgInstance != nullptr) {
 | 
							if (this->generation != nullptr && this->generation->qsgInstance != nullptr) {
 | 
				
			||||||
			emit this->generation->qsgInstance->reloadFailed(error);
 | 
								emit this->generation->qsgInstance->reloadFailed(error);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -113,8 +127,16 @@ void RootWrapper::reloadGraph(bool hard) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->onWatchFilesChanged();
 | 
						this->onWatchFilesChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (isReload && this->generation->qsgInstance != nullptr) {
 | 
						if (isReload) {
 | 
				
			||||||
		emit this->generation->qsgInstance->reloadCompleted();
 | 
							auto showPopup = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (this->generation->qsgInstance != nullptr) {
 | 
				
			||||||
 | 
								this->generation->qsgInstance->clearReloadPopupInhibit();
 | 
				
			||||||
 | 
								emit this->generation->qsgInstance->reloadCompleted();
 | 
				
			||||||
 | 
								showPopup = !this->generation->qsgInstance->isReloadPopupInhibited();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (showPopup) qs::ui::ReloadPopup::spawnPopup(InstanceInfo::CURRENT.instanceId, false, "");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ Q_DECLARE_LOGGING_CATEGORY(logQmlScanner);
 | 
				
			||||||
// expects canonical paths
 | 
					// expects canonical paths
 | 
				
			||||||
class QmlScanner {
 | 
					class QmlScanner {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
 | 
						QmlScanner() = default;
 | 
				
			||||||
	QmlScanner(const QDir& rootPath): rootPath(rootPath) {}
 | 
						QmlScanner(const QDir& rootPath): rootPath(rootPath) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void scanDir(const QString& path);
 | 
						void scanDir(const QString& path);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
function (qs_test name)
 | 
					function (qs_test name)
 | 
				
			||||||
	add_executable(${name} ${ARGN})
 | 
						add_executable(${name} ${ARGN})
 | 
				
			||||||
	target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-core quickshell-window)
 | 
						target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-core quickshell-window quickshell-ui)
 | 
				
			||||||
	add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
 | 
						add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
 | 
				
			||||||
endfunction()
 | 
					endfunction()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/ui/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/ui/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					qt_add_library(quickshell-ui STATIC
 | 
				
			||||||
 | 
						reload_popup.cpp
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# do not install this module
 | 
				
			||||||
 | 
					qt_add_qml_module(quickshell-ui
 | 
				
			||||||
 | 
						URI Quickshell._InternalUi
 | 
				
			||||||
 | 
						VERSION 0.1
 | 
				
			||||||
 | 
						DEPENDENCIES QtQuick
 | 
				
			||||||
 | 
						QML_FILES
 | 
				
			||||||
 | 
							Tooltip.qml
 | 
				
			||||||
 | 
							ReloadPopup.qml
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qs_module_pch(quickshell-ui SET large)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_link_libraries(quickshell-ui PRIVATE Qt::Quick)
 | 
				
			||||||
 | 
					target_link_libraries(quickshell PRIVATE quickshell-uiplugin)
 | 
				
			||||||
							
								
								
									
										237
									
								
								src/ui/ReloadPopup.qml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								src/ui/ReloadPopup.qml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,237 @@
 | 
				
			||||||
 | 
					pragma ComponentBehavior: Bound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import QtQuick
 | 
				
			||||||
 | 
					import QtQuick.Layouts
 | 
				
			||||||
 | 
					import Quickshell
 | 
				
			||||||
 | 
					import Quickshell.Widgets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PanelWindow {
 | 
				
			||||||
 | 
						id: root
 | 
				
			||||||
 | 
						required property ReloadPopupInfo reloadInfo
 | 
				
			||||||
 | 
						readonly property string instanceId: root.reloadInfo.instanceId
 | 
				
			||||||
 | 
						readonly property bool failed: root.reloadInfo.failed
 | 
				
			||||||
 | 
						readonly property string errorString: root.reloadInfo.errorString
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						anchors { left: true; top: true }
 | 
				
			||||||
 | 
						margins { left: 25; top: 25 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						implicitWidth: wrapper.implicitWidth
 | 
				
			||||||
 | 
						implicitHeight: wrapper.implicitHeight
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						color: "transparent"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						focusable: failText.focus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Composite before changing opacity
 | 
				
			||||||
 | 
						SequentialAnimation on contentItem.opacity {
 | 
				
			||||||
 | 
							id: fadeOutAnim
 | 
				
			||||||
 | 
							NumberAnimation {
 | 
				
			||||||
 | 
								// avoids 0 which closes the popup
 | 
				
			||||||
 | 
								from: 0.0001; to: 1
 | 
				
			||||||
 | 
								duration: 250
 | 
				
			||||||
 | 
								easing.type: Easing.OutQuad
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							PauseAnimation { duration: root.failed ? 2000 : 500 }
 | 
				
			||||||
 | 
							NumberAnimation {
 | 
				
			||||||
 | 
								to: 0
 | 
				
			||||||
 | 
								duration: root.failed ? 3000 : 800
 | 
				
			||||||
 | 
								easing.type: Easing.InQuad
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Behavior on contentItem.opacity {
 | 
				
			||||||
 | 
							enabled: !fadeOutAnim.running
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							NumberAnimation {
 | 
				
			||||||
 | 
								duration: 250
 | 
				
			||||||
 | 
								easing.type: Easing.OutQuad
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						contentItem.onOpacityChanged: {
 | 
				
			||||||
 | 
							if (contentItem.opacity == 0) root.reloadInfo.closed()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						component PopupText: Text {
 | 
				
			||||||
 | 
							color: palette.active.text
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						component TopButton: WrapperMouseArea {
 | 
				
			||||||
 | 
							id: buttonMouse
 | 
				
			||||||
 | 
							property alias image: image.source
 | 
				
			||||||
 | 
							property bool red: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							hoverEnabled: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							WrapperRectangle {
 | 
				
			||||||
 | 
								radius: 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								color: {
 | 
				
			||||||
 | 
									if (buttonMouse.red) {
 | 
				
			||||||
 | 
										const baseColor = "#c04040";
 | 
				
			||||||
 | 
										if (buttonMouse.pressed) return Qt.tint(palette.active.button, Qt.alpha(baseColor, 0.8));
 | 
				
			||||||
 | 
										if (buttonMouse.containsMouse) return baseColor;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										if (buttonMouse.pressed) return Qt.tint(palette.active.button, Qt.alpha(palette.active.accent, 0.3));
 | 
				
			||||||
 | 
										if (buttonMouse.containsMouse) return Qt.tint(palette.active.button, Qt.alpha(palette.active.accent, 0.5));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return palette.active.button;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								border.color: {
 | 
				
			||||||
 | 
									if (buttonMouse.red) {
 | 
				
			||||||
 | 
										const baseColor = "#c04040";
 | 
				
			||||||
 | 
										if (buttonMouse.pressed) return Qt.tint(palette.active.light, Qt.alpha(baseColor, 0.8));
 | 
				
			||||||
 | 
										if (buttonMouse.containsMouse) return baseColor;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										if (buttonMouse.pressed) return Qt.tint(palette.active.light, Qt.alpha(palette.active.accent, 0.7));
 | 
				
			||||||
 | 
										if (buttonMouse.containsMouse) return palette.active.accent;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return palette.active.light;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Behavior on color { ColorAnimation { duration: 100 } }
 | 
				
			||||||
 | 
								Behavior on border.color { ColorAnimation { duration: 100 } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								IconImage { id: image; implicitSize: 22 }
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						WrapperRectangle {
 | 
				
			||||||
 | 
							id: wrapper
 | 
				
			||||||
 | 
							anchors.fill: parent
 | 
				
			||||||
 | 
							color: palette.active.window
 | 
				
			||||||
 | 
							border.color: root.failed ? "#b53030" : palette.active.accent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							radius: 10
 | 
				
			||||||
 | 
							margin: 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							HoverHandler {
 | 
				
			||||||
 | 
								onHoveredChanged: {
 | 
				
			||||||
 | 
									if (hovered && fadeOutAnim.running) {
 | 
				
			||||||
 | 
										fadeOutAnim.stop();
 | 
				
			||||||
 | 
										root.contentItem.opacity = 1;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ColumnLayout {
 | 
				
			||||||
 | 
								RowLayout {
 | 
				
			||||||
 | 
									PopupText {
 | 
				
			||||||
 | 
										font.pixelSize: 20
 | 
				
			||||||
 | 
										fontSizeMode: Text.VerticalFit
 | 
				
			||||||
 | 
										text: `Quickshell: ${root.failed ? "Config reload failed" : "Config reloaded"}`
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									Item { Layout.fillWidth: true }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									TopButton {
 | 
				
			||||||
 | 
										id: copyButton
 | 
				
			||||||
 | 
										visible: root.failed
 | 
				
			||||||
 | 
										image: Quickshell.iconPath("edit-copy")
 | 
				
			||||||
 | 
										onClicked: {
 | 
				
			||||||
 | 
											Quickshell.clipboardText = root.errorString;
 | 
				
			||||||
 | 
											copyTooltip.showAction();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									Tooltip {
 | 
				
			||||||
 | 
										id: copyTooltip
 | 
				
			||||||
 | 
										anchorItem: copyButton
 | 
				
			||||||
 | 
										show: copyButton.containsMouse
 | 
				
			||||||
 | 
										text: "Copy error message"
 | 
				
			||||||
 | 
										actionText: "Copied to clipboard"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									TopButton {
 | 
				
			||||||
 | 
										image: Quickshell.iconPath("window-close")
 | 
				
			||||||
 | 
										red: true
 | 
				
			||||||
 | 
										onClicked: {
 | 
				
			||||||
 | 
											fadeOutAnim.stop()
 | 
				
			||||||
 | 
											root.contentItem.opacity = 0
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								WrapperRectangle {
 | 
				
			||||||
 | 
									visible: root.failed
 | 
				
			||||||
 | 
									color: palette.active.base
 | 
				
			||||||
 | 
									margin: 10
 | 
				
			||||||
 | 
									radius: 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									TextEdit {
 | 
				
			||||||
 | 
										id: failText
 | 
				
			||||||
 | 
										text: root.errorString
 | 
				
			||||||
 | 
										color: palette.active.text
 | 
				
			||||||
 | 
										selectionColor: palette.active.highlight
 | 
				
			||||||
 | 
										selectedTextColor: palette.active.highlightedText
 | 
				
			||||||
 | 
										readOnly: true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								RowLayout {
 | 
				
			||||||
 | 
									PopupText { text: "Run" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									WrapperMouseArea {
 | 
				
			||||||
 | 
										id: logButton
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										Layout.topMargin: -logWrapper.margin
 | 
				
			||||||
 | 
										Layout.bottomMargin: -logWrapper.margin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										hoverEnabled: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										onPressed: {
 | 
				
			||||||
 | 
											Quickshell.clipboardText = logText.text;
 | 
				
			||||||
 | 
											logCopyTooltip.showAction();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										WrapperRectangle {
 | 
				
			||||||
 | 
											id: logWrapper
 | 
				
			||||||
 | 
											margin: 2
 | 
				
			||||||
 | 
											radius: 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											color: {
 | 
				
			||||||
 | 
												if (logButton.pressed) return Qt.tint(palette.active.base, Qt.alpha(palette.active.accent, 0.1));
 | 
				
			||||||
 | 
												if (logButton.containsMouse) return Qt.tint(palette.active.base, Qt.alpha(palette.active.accent, 0.2));
 | 
				
			||||||
 | 
												return palette.active.base;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											border.color: {
 | 
				
			||||||
 | 
												if (logButton.pressed) return Qt.tint(palette.active.button, Qt.alpha(palette.active.accent, 0.3));
 | 
				
			||||||
 | 
												if (logButton.containsMouse) return Qt.tint(palette.active.button, Qt.alpha(palette.active.accent, 0.5));
 | 
				
			||||||
 | 
												return palette.active.button;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											Behavior on color { ColorAnimation { duration: 100 } }
 | 
				
			||||||
 | 
											Behavior on border.color { ColorAnimation { duration: 100 } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											RowLayout {
 | 
				
			||||||
 | 
												PopupText {
 | 
				
			||||||
 | 
													id: logText
 | 
				
			||||||
 | 
													text: `qs log -i ${root.instanceId}`
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												IconImage {
 | 
				
			||||||
 | 
													Layout.fillHeight: true
 | 
				
			||||||
 | 
													implicitWidth: height
 | 
				
			||||||
 | 
													source: Quickshell.iconPath("edit-copy")
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										Tooltip {
 | 
				
			||||||
 | 
											id: logCopyTooltip
 | 
				
			||||||
 | 
											anchorItem: logWrapper
 | 
				
			||||||
 | 
											show: logButton.containsMouse
 | 
				
			||||||
 | 
											text: "Copy command"
 | 
				
			||||||
 | 
											actionText: "Copied to clipboard"
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									PopupText { text: "to view the log." }
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										82
									
								
								src/ui/Tooltip.qml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/ui/Tooltip.qml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,82 @@
 | 
				
			||||||
 | 
					import QtQuick
 | 
				
			||||||
 | 
					import Quickshell
 | 
				
			||||||
 | 
					import Quickshell.Widgets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PopupWindow {
 | 
				
			||||||
 | 
						id: popup
 | 
				
			||||||
 | 
						required property Item anchorItem
 | 
				
			||||||
 | 
						required property string text
 | 
				
			||||||
 | 
						property string actionText
 | 
				
			||||||
 | 
						property bool show: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						function showAction() {
 | 
				
			||||||
 | 
							mShowAction = true;
 | 
				
			||||||
 | 
							showInternal = true;
 | 
				
			||||||
 | 
							hangTimer.restart();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We should be using a bottom center anchor but support for them is bad compositor side.
 | 
				
			||||||
 | 
						anchor {
 | 
				
			||||||
 | 
							window: anchorItem.QsWindow.window
 | 
				
			||||||
 | 
							adjustment: PopupAdjustment.None
 | 
				
			||||||
 | 
							gravity: Edges.Bottom | Edges.Right
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							onAnchoring: {
 | 
				
			||||||
 | 
								const pos = anchorItem.QsWindow.contentItem.mapFromItem(
 | 
				
			||||||
 | 
									anchorItem,
 | 
				
			||||||
 | 
									anchorItem.width / 2 - popup.width / 2,
 | 
				
			||||||
 | 
									anchorItem.height + 5
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								anchor.rect.x = pos.x;
 | 
				
			||||||
 | 
								anchor.rect.y = pos.y;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						property bool showInternal: false
 | 
				
			||||||
 | 
						property bool mShowAction: false
 | 
				
			||||||
 | 
						property real opacity: showInternal ? 1 : 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						onShowChanged: hangTimer.restart()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Timer {
 | 
				
			||||||
 | 
							id: hangTimer
 | 
				
			||||||
 | 
							interval: 400
 | 
				
			||||||
 | 
							onTriggered: popup.showInternal = popup.show
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Behavior on opacity {
 | 
				
			||||||
 | 
							NumberAnimation {
 | 
				
			||||||
 | 
								duration: 200
 | 
				
			||||||
 | 
								easing.type: popup.showInternal ? Easing.InQuart : Easing.OutQuart
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						color: "transparent"
 | 
				
			||||||
 | 
						visible: opacity != 0
 | 
				
			||||||
 | 
						onVisibleChanged: if (!visible) mShowAction = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						implicitWidth: content.implicitWidth
 | 
				
			||||||
 | 
						implicitHeight: content.implicitHeight
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						WrapperRectangle {
 | 
				
			||||||
 | 
							id: content
 | 
				
			||||||
 | 
							opacity: popup.opacity
 | 
				
			||||||
 | 
							color: palette.active.toolTipBase
 | 
				
			||||||
 | 
							border.color: palette.active.light
 | 
				
			||||||
 | 
							margin: 5
 | 
				
			||||||
 | 
							radius: 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							transform: Scale {
 | 
				
			||||||
 | 
								origin.x: content.width / 2
 | 
				
			||||||
 | 
								origin.y: 0
 | 
				
			||||||
 | 
								xScale: 0.6 + popup.opacity * 0.4
 | 
				
			||||||
 | 
								yScale: xScale
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Text {
 | 
				
			||||||
 | 
								text: popup.mShowAction ? popup.actionText : popup.text
 | 
				
			||||||
 | 
								color: palette.active.toolTipText
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								src/ui/reload_popup.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/ui/reload_popup.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					#include "reload_popup.hpp"
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlcomponent.h>
 | 
				
			||||||
 | 
					#include <qtenvironmentvariables.h>
 | 
				
			||||||
 | 
					#include <qtimer.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../core/generation.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::ui {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ReloadPopup::ReloadPopup(QString instanceId, bool failed, QString errorString)
 | 
				
			||||||
 | 
					    : generation(new EngineGeneration())
 | 
				
			||||||
 | 
					    , instanceId(std::move(instanceId))
 | 
				
			||||||
 | 
					    , failed(failed)
 | 
				
			||||||
 | 
					    , errorString(std::move(errorString)) {
 | 
				
			||||||
 | 
						auto component = QQmlComponent(
 | 
				
			||||||
 | 
						    this->generation->engine,
 | 
				
			||||||
 | 
						    "qrc:/qt/qml/Quickshell/_InternalUi/ReloadPopup.qml",
 | 
				
			||||||
 | 
						    this
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->popup = component.createWithInitialProperties({{"reloadInfo", QVariant::fromValue(this)}});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!popup) {
 | 
				
			||||||
 | 
							qCritical() << "Failed to open reload popup:" << component.errorString();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->generation->onReload(nullptr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ReloadPopup::closed() {
 | 
				
			||||||
 | 
						if (ReloadPopup::activePopup == this) ReloadPopup::activePopup = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->deleting) {
 | 
				
			||||||
 | 
							this->deleting = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							QTimer::singleShot(0, [this]() {
 | 
				
			||||||
 | 
								this->popup->deleteLater();
 | 
				
			||||||
 | 
								this->generation->destroy();
 | 
				
			||||||
 | 
								this->deleteLater();
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ReloadPopup::spawnPopup(QString instanceId, bool failed, QString errorString) {
 | 
				
			||||||
 | 
						if (qEnvironmentVariableIsSet("QS_NO_RELOAD_POPUP")) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ReloadPopup::activePopup) ReloadPopup::activePopup->closed();
 | 
				
			||||||
 | 
						ReloadPopup::activePopup = new ReloadPopup(std::move(instanceId), failed, std::move(errorString));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ReloadPopup* ReloadPopup::activePopup = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::ui
 | 
				
			||||||
							
								
								
									
										39
									
								
								src/ui/reload_popup.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/ui/reload_popup.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlengine.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../core/generation.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::ui {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ReloadPopup: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_NAMED_ELEMENT(ReloadPopupInfo);
 | 
				
			||||||
 | 
						QML_UNCREATABLE("")
 | 
				
			||||||
 | 
						Q_PROPERTY(QString instanceId MEMBER instanceId CONSTANT);
 | 
				
			||||||
 | 
						Q_PROPERTY(bool failed MEMBER failed CONSTANT);
 | 
				
			||||||
 | 
						Q_PROPERTY(QString errorString MEMBER errorString CONSTANT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						Q_INVOKABLE void closed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static void spawnPopup(QString instanceId, bool failed, QString errorString);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						ReloadPopup(QString instanceId, bool failed, QString errorString);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						EngineGeneration* generation;
 | 
				
			||||||
 | 
						QObject* popup = nullptr;
 | 
				
			||||||
 | 
						QString instanceId;
 | 
				
			||||||
 | 
						bool failed = false;
 | 
				
			||||||
 | 
						bool deleting = false;
 | 
				
			||||||
 | 
						QString errorString;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static ReloadPopup* activePopup;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::ui
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
function (qs_test name)
 | 
					function (qs_test name)
 | 
				
			||||||
	add_executable(${name} ${ARGN})
 | 
						add_executable(${name} ${ARGN})
 | 
				
			||||||
	target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-window quickshell-core)
 | 
						target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-window quickshell-core quickshell-ui)
 | 
				
			||||||
	add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
 | 
						add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
 | 
				
			||||||
endfunction()
 | 
					endfunction()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue