Skip to content

Commit 5048b97

Browse files
committed
ui: add native reload popup
1 parent fef840d commit 5048b97

File tree

13 files changed

+475
-4
lines changed

13 files changed

+475
-4
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_subdirectory(ipc)
1010
add_subdirectory(window)
1111
add_subdirectory(io)
1212
add_subdirectory(widgets)
13+
add_subdirectory(ui)
1314

1415
if (CRASH_REPORTER)
1516
add_subdirectory(crash)

src/core/generation.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
4545
QsEnginePlugin::runConstructGeneration(*this);
4646
}
4747

48+
EngineGeneration::EngineGeneration(): EngineGeneration(QDir(), QmlScanner()) {}
49+
4850
EngineGeneration::~EngineGeneration() {
4951
if (this->engine != nullptr) {
5052
qFatal() << this << "destroyed without calling destroy()";

src/core/generation.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class EngineGeneration: public QObject {
2828
Q_OBJECT;
2929

3030
public:
31+
explicit EngineGeneration();
3132
explicit EngineGeneration(const QDir& rootPath, QmlScanner scanner);
3233
~EngineGeneration() override;
3334
Q_DISABLE_COPY_MOVE(EngineGeneration);

src/core/qmlglobal.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ class QuickshellGlobal: public QObject {
173173
Q_INVOKABLE [[nodiscard]] QString statePath(const QString& path) const;
174174
/// Equivalent to `${Quickshell.cacheDir}/${path}`
175175
Q_INVOKABLE [[nodiscard]] QString cachePath(const QString& path) const;
176+
/// When called from @@reloadCompleted() or @@reloadFailed(), prevents the
177+
/// default reload popup from displaying.
178+
///
179+
/// The popup can also be blocked by setting `QS_NO_RELOAD_POPUP=1`.
180+
Q_INVOKABLE void inhibitReloadPopup() { this->mInhibitReloadPopup = true; }
181+
182+
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
183+
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
176184

177185
[[nodiscard]] QString shellRoot() const;
178186

@@ -212,6 +220,8 @@ private slots:
212220
private:
213221
QuickshellGlobal(QObject* parent = nullptr);
214222

223+
bool mInhibitReloadPopup = false;
224+
215225
static qsizetype screensCount(QQmlListProperty<QuickshellScreenInfo>* prop);
216226
static QuickshellScreenInfo* screenAt(QQmlListProperty<QuickshellScreenInfo>* prop, qsizetype i);
217227
};

src/core/rootwrapper.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
#include <qtmetamacros.h>
1313
#include <qurl.h>
1414

15+
#include "../ui/reload_popup.hpp"
1516
#include "../window/floatingwindow.hpp"
1617
#include "generation.hpp"
18+
#include "instanceinfo.hpp"
1719
#include "qmlglobal.hpp"
1820
#include "scan.hpp"
1921

@@ -68,6 +70,18 @@ void RootWrapper::reloadGraph(bool hard) {
6870
qWarning().noquote() << error;
6971
generation->destroy();
7072

73+
if (this->generation != nullptr) {
74+
auto showPopup = true;
75+
76+
if (this->generation->qsgInstance != nullptr) {
77+
this->generation->qsgInstance->clearReloadPopupInhibit();
78+
emit this->generation->qsgInstance->reloadFailed(error);
79+
showPopup = !this->generation->qsgInstance->isReloadPopupInhibited();
80+
}
81+
82+
if (showPopup) qs::ui::ReloadPopup::spawnPopup(InstanceInfo::CURRENT.instanceId, true, error);
83+
}
84+
7185
if (this->generation != nullptr && this->generation->qsgInstance != nullptr) {
7286
emit this->generation->qsgInstance->reloadFailed(error);
7387
}
@@ -113,8 +127,16 @@ void RootWrapper::reloadGraph(bool hard) {
113127

114128
this->onWatchFilesChanged();
115129

116-
if (isReload && this->generation->qsgInstance != nullptr) {
117-
emit this->generation->qsgInstance->reloadCompleted();
130+
if (isReload) {
131+
auto showPopup = true;
132+
133+
if (this->generation->qsgInstance != nullptr) {
134+
this->generation->qsgInstance->clearReloadPopupInhibit();
135+
emit this->generation->qsgInstance->reloadCompleted();
136+
showPopup = !this->generation->qsgInstance->isReloadPopupInhibited();
137+
}
138+
139+
if (showPopup) qs::ui::ReloadPopup::spawnPopup(InstanceInfo::CURRENT.instanceId, false, "");
118140
}
119141
}
120142

src/core/scan.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Q_DECLARE_LOGGING_CATEGORY(logQmlScanner);
1111
// expects canonical paths
1212
class QmlScanner {
1313
public:
14+
QmlScanner() = default;
1415
QmlScanner(const QDir& rootPath): rootPath(rootPath) {}
1516

1617
void scanDir(const QString& path);

src/core/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
function (qs_test name)
22
add_executable(${name} ${ARGN})
3-
target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-core quickshell-window)
3+
target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-core quickshell-window quickshell-ui)
44
add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
55
endfunction()
66

src/ui/CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
qt_add_library(quickshell-ui STATIC
2+
reload_popup.cpp
3+
)
4+
5+
# do not install this module
6+
qt_add_qml_module(quickshell-ui
7+
URI Quickshell._InternalUi
8+
VERSION 0.1
9+
DEPENDENCIES QtQuick
10+
QML_FILES
11+
Tooltip.qml
12+
ReloadPopup.qml
13+
)
14+
15+
qs_module_pch(quickshell-ui SET large)
16+
17+
target_link_libraries(quickshell-ui PRIVATE Qt::Quick)
18+
target_link_libraries(quickshell PRIVATE quickshell-uiplugin)

src/ui/ReloadPopup.qml

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
pragma ComponentBehavior: Bound
2+
3+
import QtQuick
4+
import QtQuick.Layouts
5+
import Quickshell
6+
import Quickshell.Widgets
7+
8+
PanelWindow {
9+
id: root
10+
required property ReloadPopupInfo reloadInfo
11+
readonly property string instanceId: root.reloadInfo.instanceId
12+
readonly property bool failed: root.reloadInfo.failed
13+
readonly property string errorString: root.reloadInfo.errorString
14+
15+
anchors { left: true; top: true }
16+
margins { left: 25; top: 25 }
17+
18+
implicitWidth: wrapper.implicitWidth
19+
implicitHeight: wrapper.implicitHeight
20+
21+
color: "transparent"
22+
23+
focusable: failText.focus
24+
25+
// Composite before changing opacity
26+
SequentialAnimation on contentItem.opacity {
27+
id: fadeOutAnim
28+
NumberAnimation {
29+
// avoids 0 which closes the popup
30+
from: 0.0001; to: 1
31+
duration: 250
32+
easing.type: Easing.OutQuad
33+
}
34+
PauseAnimation { duration: root.failed ? 2000 : 500 }
35+
NumberAnimation {
36+
to: 0
37+
duration: root.failed ? 3000 : 800
38+
easing.type: Easing.InQuad
39+
}
40+
}
41+
42+
Behavior on contentItem.opacity {
43+
enabled: !fadeOutAnim.running
44+
45+
NumberAnimation {
46+
duration: 250
47+
easing.type: Easing.OutQuad
48+
}
49+
}
50+
51+
contentItem.onOpacityChanged: {
52+
if (contentItem.opacity == 0) root.reloadInfo.closed()
53+
}
54+
55+
component PopupText: Text {
56+
color: palette.active.text
57+
}
58+
59+
component TopButton: WrapperMouseArea {
60+
id: buttonMouse
61+
property alias image: image.source
62+
property bool red: false
63+
64+
hoverEnabled: true
65+
66+
WrapperRectangle {
67+
radius: 5
68+
69+
color: {
70+
if (buttonMouse.red) {
71+
const baseColor = "#c04040";
72+
if (buttonMouse.pressed) return Qt.tint(palette.active.button, Qt.alpha(baseColor, 0.8));
73+
if (buttonMouse.containsMouse) return baseColor;
74+
} else {
75+
if (buttonMouse.pressed) return Qt.tint(palette.active.button, Qt.alpha(palette.active.accent, 0.3));
76+
if (buttonMouse.containsMouse) return Qt.tint(palette.active.button, Qt.alpha(palette.active.accent, 0.5));
77+
}
78+
79+
return palette.active.button;
80+
}
81+
82+
border.color: {
83+
if (buttonMouse.red) {
84+
const baseColor = "#c04040";
85+
if (buttonMouse.pressed) return Qt.tint(palette.active.light, Qt.alpha(baseColor, 0.8));
86+
if (buttonMouse.containsMouse) return baseColor;
87+
} else {
88+
if (buttonMouse.pressed) return Qt.tint(palette.active.light, Qt.alpha(palette.active.accent, 0.7));
89+
if (buttonMouse.containsMouse) return palette.active.accent;
90+
}
91+
92+
return palette.active.light;
93+
}
94+
95+
Behavior on color { ColorAnimation { duration: 100 } }
96+
Behavior on border.color { ColorAnimation { duration: 100 } }
97+
98+
IconImage { id: image; implicitSize: 22 }
99+
}
100+
}
101+
102+
WrapperRectangle {
103+
id: wrapper
104+
anchors.fill: parent
105+
color: palette.active.window
106+
border.color: root.failed ? "#b53030" : palette.active.accent
107+
108+
radius: 10
109+
margin: 10
110+
111+
HoverHandler {
112+
onHoveredChanged: {
113+
if (hovered && fadeOutAnim.running) {
114+
fadeOutAnim.stop();
115+
root.contentItem.opacity = 1;
116+
}
117+
}
118+
}
119+
120+
ColumnLayout {
121+
RowLayout {
122+
PopupText {
123+
font.pixelSize: 20
124+
fontSizeMode: Text.VerticalFit
125+
text: `Quickshell: ${root.failed ? "Config reload failed" : "Config reloaded"}`
126+
}
127+
128+
Item { Layout.fillWidth: true }
129+
130+
TopButton {
131+
id: copyButton
132+
visible: root.failed
133+
image: Quickshell.iconPath("edit-copy")
134+
onClicked: {
135+
Quickshell.clipboardText = root.errorString;
136+
copyTooltip.showAction();
137+
}
138+
}
139+
140+
Tooltip {
141+
id: copyTooltip
142+
anchorItem: copyButton
143+
show: copyButton.containsMouse
144+
text: "Copy error message"
145+
actionText: "Copied to clipboard"
146+
}
147+
148+
TopButton {
149+
image: Quickshell.iconPath("window-close")
150+
red: true
151+
onClicked: {
152+
fadeOutAnim.stop()
153+
root.contentItem.opacity = 0
154+
}
155+
}
156+
}
157+
158+
WrapperRectangle {
159+
visible: root.failed
160+
color: palette.active.base
161+
margin: 10
162+
radius: 5
163+
164+
TextEdit {
165+
id: failText
166+
text: root.errorString
167+
color: palette.active.text
168+
selectionColor: palette.active.highlight
169+
selectedTextColor: palette.active.highlightedText
170+
readOnly: true
171+
}
172+
}
173+
174+
RowLayout {
175+
PopupText { text: "Run" }
176+
177+
WrapperMouseArea {
178+
id: logButton
179+
180+
Layout.topMargin: -logWrapper.margin
181+
Layout.bottomMargin: -logWrapper.margin
182+
183+
hoverEnabled: true
184+
185+
onPressed: {
186+
Quickshell.clipboardText = logText.text;
187+
logCopyTooltip.showAction();
188+
}
189+
190+
WrapperRectangle {
191+
id: logWrapper
192+
margin: 2
193+
radius: 5
194+
195+
color: {
196+
if (logButton.pressed) return Qt.tint(palette.active.base, Qt.alpha(palette.active.accent, 0.1));
197+
if (logButton.containsMouse) return Qt.tint(palette.active.base, Qt.alpha(palette.active.accent, 0.2));
198+
return palette.active.base;
199+
}
200+
201+
border.color: {
202+
if (logButton.pressed) return Qt.tint(palette.active.button, Qt.alpha(palette.active.accent, 0.3));
203+
if (logButton.containsMouse) return Qt.tint(palette.active.button, Qt.alpha(palette.active.accent, 0.5));
204+
return palette.active.button;
205+
}
206+
207+
Behavior on color { ColorAnimation { duration: 100 } }
208+
Behavior on border.color { ColorAnimation { duration: 100 } }
209+
210+
RowLayout {
211+
PopupText {
212+
id: logText
213+
text: `qs log -i ${root.instanceId}`
214+
}
215+
216+
IconImage {
217+
Layout.fillHeight: true
218+
implicitWidth: height
219+
source: Quickshell.iconPath("edit-copy")
220+
}
221+
}
222+
}
223+
224+
Tooltip {
225+
id: logCopyTooltip
226+
anchorItem: logWrapper
227+
show: logButton.containsMouse
228+
text: "Copy command"
229+
actionText: "Copied to clipboard"
230+
}
231+
}
232+
233+
PopupText { text: "to view the log." }
234+
}
235+
}
236+
}
237+
}

0 commit comments

Comments
 (0)