Overte C++ Documentation
InteractiveWindow.h
1 //
2 // InteractiveWindow.h
3 // libraries/ui/src
4 //
5 // Created by Thijs Wenker on 2018-06-25
6 // Copyright 2018 High Fidelity, Inc.
7 // Copyright 2023 Overte e.V.
8 //
9 // Distributed under the Apache License, Version 2.0.
10 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
11 // SPDX-License-Identifier: Apache-2.0
12 //
13 
14 #pragma once
15 
16 #ifndef hifi_InteractiveWindow_h
17 #define hifi_InteractiveWindow_h
18 
19 #include <QtCore/QObject>
20 #include <QtCore/QPointer>
21 #include <QQmlEngine>
22 #include <ui/QmlWrapper.h>
23 
24 #include <glm/glm.hpp>
25 #include <GLMHelpers.h>
26 #include <ScriptValue.h>
27 
28 class ScriptEngine;
29 
30 class QmlWindowProxy : public QmlWrapper {
31  Q_OBJECT
32 
33 public:
34  QmlWindowProxy(QObject* qmlObject, QObject* parent = nullptr);
35 
36  Q_INVOKABLE void parentNativeWindowToMainWindow();
37 
38  QObject* getQmlWindow() const { return _qmlWindow; }
39 private:
40  QObject* _qmlWindow;
41 };
42 
43 
44 class InteractiveWindowProxy : public QObject {
45  Q_OBJECT
46 public:
47  InteractiveWindowProxy(){}
48 public slots:
49 
50  void emitScriptEvent(const QVariant& scriptMessage);
51  void emitWebEvent(const QVariant& webMessage);
52 
53 signals:
54 
55  void scriptEventReceived(const QVariant& message);
56  void webEventReceived(const QVariant& message);
57 };
58 
59 namespace InteractiveWindowEnums {
60  Q_NAMESPACE
61 
62  /*@jsdoc
63  * <p>A set of flags controlling <code>InteractiveWindow</code> behavior. The value is constructed by using the
64  * <code>|</code> (bitwise OR) operator on the individual flag values.</p>
65  * <table>
66  * <thead>
67  * <tr><th>Flag Name</th><th>Value</th><th>Description</th></tr>
68  * </thead>
69  * <tbody>
70  * <tr><td>ALWAYS_ON_TOP</td><td><code>1</code></td><td>The window always displays on top.</td></tr>
71  * <tr><td>CLOSE_BUTTON_HIDES</td><td><code>2</code></td><td>The window hides instead of closing when the user clicks
72  * the "close" button.</td></tr>
73  * </tbody>
74  * </table>
75  * @typedef {number} InteractiveWindow.Flags
76  */
77  enum InteractiveWindowFlags : uint8_t {
78  AlwaysOnTop = 1 << 0,
79  CloseButtonHides = 1 << 1
80  };
81  Q_ENUM_NS(InteractiveWindowFlags);
82 
83  /*@jsdoc
84  * <p>A display mode for an <code>InteractiveWindow</code>.</p>
85  * <table>
86  * <thead>
87  * <tr><th>Value</th><th>Name</th><th>Description</th></tr>
88  * </thead>
89  * <tbody>
90  * <tr><td><code>0</code></td><td>VIRTUAL</td><td>The window is displayed inside Interface: in the desktop window in
91  * desktop mode or on the HUD surface in HMD mode.</td></tr>
92  * <tr><td><code>1</code></td><td>NATIVE</td><td>The window is displayed separately from the Interface window, as its
93  * own separate window.</td></tr>
94  * <tbody>
95  * </table>
96  * @typedef {number} InteractiveWindow.PresentationMode
97  */
98  enum InteractiveWindowPresentationMode {
99  Virtual,
100  Native
101  };
102  Q_ENUM_NS(InteractiveWindowPresentationMode);
103 
104  /*@jsdoc
105  * <p>A docking location of an <code>InteractiveWindow</code>.</p>
106  * <table>
107  * <thead>
108  * <tr><th>Value</th><th>Name</th><th>Description</th></tr>
109  * </thead>
110  * <tbody>
111  * <tr><td><code>0</code></td><td>TOP</td><td>Dock to the top edge of the Interface window.</td></tr>
112  * <tr><td><code>1</code></td><td>BOTTOM</td><td>Dock to the bottom edge of the Interface window.</td></tr>
113  * <tr><td><code>2</code></td><td>LEFT</td><td>Dock to the left edge of the Interface window.</td></tr>
114  * <tr><td><code>3</code></td><td>RIGHT</td><td>Dock to the right edge of the Interface window.</td></tr>
115  * <tbody>
116  * </table>
117  * @typedef {number} InteractiveWindow.DockArea
118  */
119  enum DockArea {
120  TOP,
121  BOTTOM,
122  LEFT,
123  RIGHT
124  };
125  Q_ENUM_NS(DockArea);
126 
127  /*@jsdoc
128  * <p>The anchor for a relative position of an <code>InteractiveWindow</code>.</p>
129  * <table>
130  * <thead>
131  * <tr><th>Value</th><th>Name</th><th>Description</th></tr>
132  * </thead>
133  * <tbody>
134  * <tr><td><code>0</code></td><td>NO_ANCHOR</td><td>Position is not relative to any part of the Interface window.</td></tr>
135  * <tr><td><code>1</code></td><td>TOP_LEFT</td><td>Position is offset from the top left of the Interface window.</td></tr>
136  * <tr><td><code>2</code></td><td>TOP_RIGHT</td><td>Position is offset from the top right of the Interface window.</td></tr>
137  * <tr><td><code>3</code></td><td>BOTTOM_RIGHT</td><td>Position offset from the bottom right of the Interface
138  * window.</td></tr>
139  * <tr><td><code>4</code></td><td>BOTTOM_LEFFT</td><td>Position is offset from the bottom left of the Interface
140  * window.</td></tr>
141  * <tbody>
142  * </table>
143  * @typedef {number} InteractiveWindow.RelativePositionAnchor
144  */
145  enum RelativePositionAnchor {
146  NO_ANCHOR,
147  TOP_LEFT,
148  TOP_RIGHT,
149  BOTTOM_RIGHT,
150  BOTTOM_LEFT
151  };
152  Q_ENUM_NS(RelativePositionAnchor);
153 }
154 
155 using namespace InteractiveWindowEnums;
156 
157 /*@jsdoc
158  * An <code>InteractiveWindow</code> can display either inside Interface or in its own window separate from the Interface
159  * window. The window content is defined by a QML file, which can optionally include a <code>WebView</code> control that embeds
160  * an HTML web page. (The <code>WebView</code> control is defined by a "WebView.qml" file included in the Interface install.)
161  *
162  * <p>Create using {@link Desktop.createWindow}.</p>
163  *
164  * @class InteractiveWindow
165  * @hideconstructor
166  *
167  * @hifi-interface
168  * @hifi-client-entity
169  * @hifi-avatar
170  *
171  * @property {string} title - The title of the window.
172  * @property {Vec2} position - The absolute position of the window, in pixels.
173  * @property {InteractiveWindow.RelativePositionAnchor} relativePositionAnchor - The anchor for the
174  * <code>relativePosition</code>, if used.
175  * @property {Vec2} relativePosition - The position of the window, relative to the <code>relativePositionAnchor</code>, in
176  * pixels. Excludes the window frame.
177  * @property {Vec2} size - The size of the window, in pixels.
178  * @property {boolean} visible - <code>true</code> if the window is visible, <code>false</code> if it isn't.
179  * @property {InteractiveWindow.PresentationMode} presentationMode - The presentation mode of the window:
180  * <code>Desktop.PresentationMode.VIRTUAL</code> to display the window inside Interface, <code>.NATIVE</code> to display it
181  * as its own separate window.
182  */
183 
184 class DockWidget;
185 class InteractiveWindow : public QObject {
186  Q_OBJECT
187 
188  Q_PROPERTY(QString title READ getTitle WRITE setTitle)
189  Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition)
190  Q_PROPERTY(RelativePositionAnchor relativePositionAnchor READ getRelativePositionAnchor WRITE setRelativePositionAnchor)
191  Q_PROPERTY(glm::vec2 relativePosition READ getRelativePosition WRITE setRelativePosition)
192  Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize)
193  Q_PROPERTY(bool visible READ isVisible WRITE setVisible)
194  Q_PROPERTY(int presentationMode READ getPresentationMode WRITE setPresentationMode)
195 
196 public:
197  InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties, bool restricted);
198  ~InteractiveWindow();
199 
200 private:
201  // define property getters and setters as private to not expose them to the JS API
202  Q_INVOKABLE QString getTitle() const;
203  Q_INVOKABLE void setTitle(const QString& title);
204 
205  Q_INVOKABLE glm::vec2 getPosition() const;
206  Q_INVOKABLE void setPosition(const glm::vec2& position);
207 
208  RelativePositionAnchor _relativePositionAnchor{ RelativePositionAnchor::NO_ANCHOR };
209  Q_INVOKABLE RelativePositionAnchor getRelativePositionAnchor() const;
210  Q_INVOKABLE void setRelativePositionAnchor(const RelativePositionAnchor& position);
211 
212  // This "relative position" is relative to the "relative position anchor" and excludes the window frame.
213  // This position will ALWAYS include the geometry of a docked widget, if one is present.
214  glm::vec2 _relativePosition{ 0.0f, 0.0f };
215  Q_INVOKABLE glm::vec2 getRelativePosition() const;
216  Q_INVOKABLE void setRelativePosition(const glm::vec2& position);
217 
218  Q_INVOKABLE void setPositionUsingRelativePositionAndAnchor(const QRect& mainWindowGeometry);
219 
220  bool _isFullScreenWindow{ false };
221  Q_INVOKABLE void repositionAndResizeFullScreenWindow();
222 
223  Q_INVOKABLE glm::vec2 getSize() const;
224  Q_INVOKABLE void setSize(const glm::vec2& size);
225 
226  Q_INVOKABLE void setVisible(bool visible);
227  Q_INVOKABLE bool isVisible() const;
228 
229  Q_INVOKABLE void setPresentationMode(int presentationMode);
230  Q_INVOKABLE int getPresentationMode() const;
231 
232  Q_INVOKABLE void parentNativeWindowToMainWindow();
233 
234 public slots:
235 
236  /*@jsdoc
237  * Sends a message to the QML page. To receive the message, the QML page must implement a function:
238  * <pre class="prettyprint"><code>function fromScript(message) {
239  * ...
240  * }</code></pre>
241  * @function InteractiveWindow.sendToQml
242  * @param {string|object} message - The message to send to the QML page.
243  * @example <caption>Send and receive messages with a QML window.</caption>
244  * // JavaScript file.
245  *
246  * var interactiveWindow = Desktop.createWindow(Script.resolvePath("InteractiveWindow.qml"), {
247  * title: "Interactive Window",
248  * size: { x: 400, y: 300 }
249  * });
250  *
251  * interactiveWindow.fromQml.connect(function (message) {
252  * print("Message received: " + message);
253  * });
254  *
255  * Script.setTimeout(function () {
256  * interactiveWindow.sendToQml("Hello world!");
257  * }, 2000);
258  *
259  * Script.scriptEnding.connect(function () {
260  * interactiveWindow.close();
261  * });
262  * @example
263  * // QML file, "InteractiveWindow.qml".
264  *
265  * import QtQuick 2.5
266  * import QtQuick.Controls 1.4
267  *
268  * Rectangle {
269  *
270  * function fromScript(message) {
271  * text.text = message;
272  * sendToScript("Hello back!");
273  * }
274  *
275  * Label {
276  * id: text
277  * anchors.centerIn: parent
278  * text: "..."
279  * }
280  * }
281  */
282  // Scripts can use this to send a message to the QML object
283  void sendToQml(const QVariant& message);
284 
285  /*@jsdoc
286  * Sends a message to an embedded HTML web page. To receive the message, the HTML page's script must connect to the
287  * <code>EventBridge</code> that is automatically provided for the script:
288  * <pre class="prettyprint"><code>EventBridge.scriptEventReceived.connect(function(message) {
289  * ...
290  * });</code></pre>
291  * @function InteractiveWindow.emitScriptEvent
292  * @param {string|object} message - The message to send to the embedded HTML web page.
293  */
294  // QmlWindow content may include WebView requiring EventBridge.
295  void emitScriptEvent(const QVariant& scriptMessage);
296 
297  /*@jsdoc
298  * @function InteractiveWindow.emitWebEvent
299  * @param {object|string} message - Message.
300  * @deprecated This function is deprecated and will be removed.
301  */
302  void emitWebEvent(const QVariant& webMessage);
303 
304  /*@jsdoc
305  * Closes the window. It can then no longer be used.
306  * @function InteractiveWindow.close
307  */
308  Q_INVOKABLE void close();
309 
310  /*@jsdoc
311  * Makes the window visible and raises it to the top.
312  * @function InteractiveWindow.show
313  */
314  Q_INVOKABLE void show();
315 
316  /*@jsdoc
317  * Raises the window to the top.
318  * @function InteractiveWindow.raise
319  */
320  Q_INVOKABLE void raise();
321 
322 signals:
323 
324  /*@jsdoc
325  * Triggered when the window is made visible or invisible, or is closed.
326  * @function InteractiveWindow.visibleChanged
327  * @returns {Signal}
328  */
329  void visibleChanged();
330 
331  /*@jsdoc
332  * Triggered when the window's position changes.
333  * @function InteractiveWindow.positionChanged
334  * @returns {Signal}
335  */
336  void positionChanged();
337 
338  /*@jsdoc
339  * Triggered when the window's size changes.
340  * @function InteractiveWindow.sizeChanged
341  * @returns {Signal}
342  */
343  void sizeChanged();
344 
345  /*@jsdoc
346  * Triggered when the window's presentation mode changes.
347  * @function InteractiveWindow.presentationModeChanged
348  * @returns {Signal}
349  */
350  void presentationModeChanged();
351 
352  /*@jsdoc
353  * Triggered when window's title changes.
354  * @function InteractiveWindow.titleChanged
355  * @returns {Signal}
356  */
357  void titleChanged();
358 
359  /*@jsdoc
360  * Triggered when the window is closed.
361  * @function InteractiveWindow.closed
362  * @returns {Signal}
363  */
364  void closed();
365 
366  /*@jsdoc
367  * Triggered when a message from the QML page is received. The QML page can send a message (string or object) by calling:
368  * <pre class="prettyprint"><code>sendToScript(message);</code></pre>
369  * @function InteractiveWindow.fromQml
370  * @param {string|object} message - The message received.
371  * @returns {Signal}
372  */
373  // Scripts can connect to this signal to receive messages from the QML object
374  void fromQml(const QVariant& message);
375 
376  /*@jsdoc
377  * @function InteractiveWindow.scriptEventReceived
378  * @param {object} message - Message.
379  * @returns {Signal}
380  * @deprecated This signal is deprecated and will be removed.
381  */
382  // InteractiveWindow content may include WebView requiring EventBridge.
383  void scriptEventReceived(const QVariant& message);
384 
385  /*@jsdoc
386  * Triggered when a message from an embedded HTML web page is received. The HTML web page can send a message by calling:
387  * <pre class="prettyprint"><code>EventBridge.emitWebEvent(message);</code></pre>
388  * @function InteractiveWindow.webEventReceived
389  * @param {string|object} message - The message received.
390  * @returns {Signal}
391  */
392  void webEventReceived(const QVariant& message);
393 
394 protected slots:
395  /*@jsdoc
396  * @function InteractiveWindow.qmlToScript
397  * @param {object} message - Message.
398  * @deprecated This method is deprecated and will be removed.
399  */
400  void qmlToScript(const QVariant& message);
401 
402  void forwardKeyPressEvent(int key, int modifiers);
403  void forwardKeyReleaseEvent(int key, int modifiers);
404  void emitMainWindowResizeEvent();
405  void onMainWindowGeometryChanged(QRect geometry);
406 
407 private:
408  std::shared_ptr<QmlWindowProxy> _qmlWindowProxy;
409  std::shared_ptr<DockWidget> _dockWidget { nullptr };
410  std::unique_ptr<InteractiveWindowProxy, std::function<void(InteractiveWindowProxy*)>> _interactiveWindowProxy;
411 };
412 
413 typedef InteractiveWindow* InteractiveWindowPointer;
414 
415 ScriptValue interactiveWindowPointerToScriptValue(ScriptEngine* engine, const InteractiveWindowPointer& in);
416 bool interactiveWindowPointerFromScriptValue(const ScriptValue& object, InteractiveWindowPointer& out);
417 
418 void registerInteractiveWindowMetaType(ScriptEngine* engine);
419 
420 Q_DECLARE_METATYPE(InteractiveWindowPointer)
421 
422 #endif // hifi_InteractiveWindow_h
Provides an engine-independent interface for a scripting engine.
Definition: ScriptEngine.h:93
[ScriptInterface] Provides an engine-independent interface for QScriptValue
Definition: ScriptValue.h:40