Add a qt_viewer example that uses QWidget

This commit is contained in:
Olivier Goffart 2021-03-23 17:09:20 +01:00
parent 90d3953d42
commit 1dba04721a
12 changed files with 249 additions and 1 deletions

View file

@ -18,4 +18,5 @@ add_subdirectory(examples/printerdemo/cpp_interpreted/)
add_subdirectory(examples/todo/cpp/) add_subdirectory(examples/todo/cpp/)
add_subdirectory(examples/gallery/) add_subdirectory(examples/gallery/)
add_subdirectory(examples/memory/) add_subdirectory(examples/memory/)
add_subdirectory(examples/qt_viewer/)

View file

@ -15,6 +15,12 @@ LICENSE END */
#include <optional> #include <optional>
#define SIXTYFPS_QT_INTEGRATION // In the future, should be defined by cmake only if this is enabled
#ifdef SIXTYFPS_QT_INTEGRATION
class QWidget;
#endif
namespace sixtyfps::cbindgen_private { namespace sixtyfps::cbindgen_private {
// This has to stay opaque, but VRc don't compile if it is just forward declared // This has to stay opaque, but VRc don't compile if it is just forward declared
struct ErasedComponentBox : vtable::Dyn struct ErasedComponentBox : vtable::Dyn
@ -501,6 +507,17 @@ public:
cbindgen_private::sixtyfps_run_event_loop(); cbindgen_private::sixtyfps_run_event_loop();
hide(); hide();
} }
#ifdef SIXTYFPS_QT_INTEGRATION
/// Return a QWidget for this instance.
/// This function is only available if the qt graphical backend was compiled in, and
/// it may return nullptr if the Qt backend is not used at runtime.
QWidget *qwidget() const {
cbindgen_private::ComponentWindowOpaque win;
cbindgen_private::sixtyfps_interpreter_component_instance_window(inner(), &win);
return reinterpret_cast<QWidget *>(cbindgen_private::sixtyfps_qt_get_widget(
reinterpret_cast<cbindgen_private::ComponentWindow *>(&win)));
}
#endif
bool set_property(std::string_view name, const Value &value) const bool set_property(std::string_view name, const Value &value) const
{ {

View file

@ -0,0 +1,24 @@
# LICENSE BEGIN
# This file is part of the SixtyFPS Project -- https://sixtyfps.io
# Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
# Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
#
# SPDX-License-Identifier: GPL-3.0-only
# This file is also available under commercial licensing terms.
# Please contact info@sixtyfps.io for more information.
# LICENSE END
cmake_minimum_required(VERSION 3.14)
project(qt_viewer LANGUAGES CXX)
if (NOT TARGET SixtyFPS::SixtyFPS)
find_package(SixtyFPS REQUIRED)
endif()
find_package(Qt5 COMPONENTS Core Widgets)
if (Qt5Widgets_FOUND)
set(CMAKE_AUTOUIC ON)
add_executable(qt_viewer qt_viewer.cpp)
target_link_libraries(qt_viewer PRIVATE SixtyFPS::SixtyFPS Qt5::Core Qt5::Widgets)
endif(Qt5Widgets_FOUND)

View file

@ -0,0 +1,7 @@
# qt_viewer
This is an example that shows how to embed a dynamically loaded .60 into a Qt (QWidgets) application
The trick is that it uses the C++ `sixtyfps::interpreter::ComponentInstance::qwidget` and embed
that widget in a Qt application.

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Interface</class>
<widget class="QWidget" name="Interface">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>512</width>
<height>524</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="load_button">
<property name="text">
<string>Load</string>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="7">
<widget class="QFrame" name="my_content">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Property name</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLineEdit" name="prop_name"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Value</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLineEdit" name="prop_value"/>
</item>
<item row="5" column="0">
<widget class="QPushButton" name="set_button">
<property name="text">
<string>Set</string>
</property>
</widget>
</item>
<item row="6" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>233</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,73 @@
/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
SPDX-License-Identifier: GPL-3.0-only
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
#include <QtWidgets/QtWidgets>
#include <sixtyfps_interpreter.h>
#include "ui_interface.h"
struct LoadedFile {
sixtyfps::ComponentHandle<sixtyfps::interpreter::ComponentInstance> instance;
QWidget *widget;
};
void show_diagnostics(QWidget *root, const sixtyfps::SharedVector< sixtyfps::interpreter::Diagnostic > &diags) {
QString text;
for (auto diagnostic : diags) {
text += (diagnostic.level == sixtyfps::interpreter::DiagnosticLevel::Warning
? QApplication::translate("qt_viewer", "warning: %1\n")
: QApplication::translate("qt_viewer", "error: %1\n")
).arg(QString::fromUtf8(diagnostic.message.data()));
text += QApplication::translate("qt_viewer", "location: %1").arg(QString::fromUtf8(diagnostic.source_file.data()));
if (diagnostic.line > 0)
text += ":" + QString::number(diagnostic.line);
if (diagnostic.column > 0)
text += ":" + QString::number(diagnostic.column);
text += "\n";
}
QMessageBox::critical(root, QApplication::translate("qt_viewer", "Compilation error"), text, QMessageBox::StandardButton::Ok);
}
int main(int argc, char **argv) {
QApplication app(argc, argv);
std::unique_ptr<LoadedFile> loaded_file;
QWidget main;
Ui::Interface ui;
ui.setupUi(&main);
QHBoxLayout layout(ui.my_content);
QObject::connect(ui.load_button, &QPushButton::clicked, [&] {
QString fileName = QFileDialog::getOpenFileName(
&main, QApplication::translate("qt_viewer", "Open SixtyFPS File"), {},
QApplication::translate("qt_viewer", "SixtyFPS File (*.60)"));
if (fileName.isEmpty())
return;
loaded_file.reset();
sixtyfps::interpreter::ComponentCompiler compiler;
auto def = compiler.build_from_path(fileName.toUtf8().data());
if (!def) {
show_diagnostics(&main, compiler.diagnostics());
return;
}
auto instance = def->create();
QWidget *wid = instance->qwidget();
wid->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
layout.addWidget(wid);
loaded_file = std::make_unique<LoadedFile>(LoadedFile{ instance, wid });
});
main.show();
return app.exec();
}

View file

@ -65,6 +65,9 @@ pub trait PlatformWindow {
&self, &self,
source: Pin<&crate::properties::Property<ImageReference>>, source: Pin<&crate::properties::Property<ImageReference>>,
) -> crate::graphics::Size; ) -> crate::graphics::Size;
/// Return self as any so the backend can upcast
fn as_any(&self) -> &dyn core::any::Any;
} }
/// Structure that represent a Window in the runtime /// Structure that represent a Window in the runtime

View file

@ -420,6 +420,23 @@ pub extern "C" fn sixtyfps_interpreter_component_instance_show(
} }
} }
/// Return a window for the component
///
/// The out pointer must be uninitialized and must be destroyed with
/// sixtyfps_component_window_drop after usage
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_interpreter_component_instance_window(
inst: &ErasedComponentBox,
out: *mut sixtyfps_corelib::window::ffi::ComponentWindowOpaque,
) {
use sixtyfps_corelib::window::ComponentWindow;
assert_eq!(
core::mem::size_of::<ComponentWindow>(),
core::mem::size_of::<sixtyfps_corelib::window::ffi::ComponentWindowOpaque>()
);
core::ptr::write(out as *mut ComponentWindow, inst.window())
}
/// Instantiate an instance from a definition. /// Instantiate an instance from a definition.
/// ///
/// The `out` must be uninitialized and is going to be initialized after the call /// The `out` must be uninitialized and is going to be initialized after the call

View file

@ -502,6 +502,10 @@ impl PlatformWindow for GraphicsWindow {
} }
} }
} }
fn as_any(&self) -> &dyn std::any::Any {
self
}
} }
struct MappedWindow { struct MappedWindow {

View file

@ -39,7 +39,8 @@ pub fn use_modules() -> usize {
} }
#[cfg(not(no_qt))] #[cfg(not(no_qt))]
{ {
(&widgets::NativeButtonVTable) as *const _ as usize qt_window::ffi::sixtyfps_qt_get_widget as usize
+ (&widgets::NativeButtonVTable) as *const _ as usize
} }
} }

View file

@ -1010,6 +1010,10 @@ impl PlatformWindow for QtWindow {
}) })
.unwrap_or_default() .unwrap_or_default()
} }
fn as_any(&self) -> &dyn std::any::Any {
self
}
} }
fn get_font(request: FontRequest) -> QFont { fn get_font(request: FontRequest) -> QFont {
@ -1199,3 +1203,19 @@ fn qt_key_to_string(key: key_generated::Qt_Key, event_text: String) -> SharedStr
} }
.into() .into()
} }
pub(crate) mod ffi {
use std::any::Any;
use std::ffi::c_void;
use super::QtWindow;
#[no_mangle]
pub extern "C" fn sixtyfps_qt_get_widget(
window: &sixtyfps_corelib::window::ComponentWindow,
) -> *mut c_void {
Any::downcast_ref(window.0.as_any()).map_or(std::ptr::null_mut(), |win: &QtWindow| {
win.widget_ptr().cast::<c_void>().as_ptr()
})
}
}

View file

@ -130,6 +130,9 @@ cpp! {{
void ensure_initialized() void ensure_initialized()
{ {
static auto app [[maybe_unused]] = []{ static auto app [[maybe_unused]] = []{
if (qApp) {
return qApp;
}
QCoreApplication::setAttribute(Qt::AA_PluginApplication, true); QCoreApplication::setAttribute(Qt::AA_PluginApplication, true);
static int argc = 1; static int argc = 1;
static char argv[] = "sixtyfps"; static char argv[] = "sixtyfps";