From 1dba04721a2d79bb7bb5719026d72e61ceec109d Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Tue, 23 Mar 2021 17:09:20 +0100 Subject: [PATCH] Add a qt_viewer example that uses QWidget --- CMakeLists.txt | 1 + .../include/sixtyfps_interpreter.h | 17 ++++ examples/qt_viewer/CMakeLists.txt | 24 ++++++ examples/qt_viewer/README.md | 7 ++ examples/qt_viewer/interface.ui | 78 +++++++++++++++++++ examples/qt_viewer/qt_viewer.cpp | 73 +++++++++++++++++ sixtyfps_runtime/corelib/window.rs | 3 + sixtyfps_runtime/interpreter/ffi.rs | 17 ++++ .../rendering_backends/gl/graphics_window.rs | 4 + sixtyfps_runtime/rendering_backends/qt/lib.rs | 3 +- .../rendering_backends/qt/qt_window.rs | 20 +++++ .../rendering_backends/qt/widgets.rs | 3 + 12 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 examples/qt_viewer/CMakeLists.txt create mode 100644 examples/qt_viewer/README.md create mode 100644 examples/qt_viewer/interface.ui create mode 100644 examples/qt_viewer/qt_viewer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b7caf3abf..6a312e134 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,4 +18,5 @@ add_subdirectory(examples/printerdemo/cpp_interpreted/) add_subdirectory(examples/todo/cpp/) add_subdirectory(examples/gallery/) add_subdirectory(examples/memory/) +add_subdirectory(examples/qt_viewer/) diff --git a/api/sixtyfps-cpp/include/sixtyfps_interpreter.h b/api/sixtyfps-cpp/include/sixtyfps_interpreter.h index aee228823..bca8fd6d3 100644 --- a/api/sixtyfps-cpp/include/sixtyfps_interpreter.h +++ b/api/sixtyfps-cpp/include/sixtyfps_interpreter.h @@ -15,6 +15,12 @@ LICENSE END */ #include +#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 { // This has to stay opaque, but VRc don't compile if it is just forward declared struct ErasedComponentBox : vtable::Dyn @@ -501,6 +507,17 @@ public: cbindgen_private::sixtyfps_run_event_loop(); 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(cbindgen_private::sixtyfps_qt_get_widget( + reinterpret_cast(&win))); + } +#endif bool set_property(std::string_view name, const Value &value) const { diff --git a/examples/qt_viewer/CMakeLists.txt b/examples/qt_viewer/CMakeLists.txt new file mode 100644 index 000000000..b1a69ddf4 --- /dev/null +++ b/examples/qt_viewer/CMakeLists.txt @@ -0,0 +1,24 @@ +# LICENSE BEGIN +# This file is part of the SixtyFPS Project -- https://sixtyfps.io +# Copyright (c) 2020 Olivier Goffart +# Copyright (c) 2020 Simon Hausmann +# +# 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) + diff --git a/examples/qt_viewer/README.md b/examples/qt_viewer/README.md new file mode 100644 index 000000000..70a9b8451 --- /dev/null +++ b/examples/qt_viewer/README.md @@ -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. + diff --git a/examples/qt_viewer/interface.ui b/examples/qt_viewer/interface.ui new file mode 100644 index 000000000..416ba07b1 --- /dev/null +++ b/examples/qt_viewer/interface.ui @@ -0,0 +1,78 @@ + + + Interface + + + + 0 + 0 + 512 + 524 + + + + Form + + + + + + Load + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Property name + + + + + + + + + + Value + + + + + + + + + + Set + + + + + + + Qt::Vertical + + + + 20 + 233 + + + + + + + + + diff --git a/examples/qt_viewer/qt_viewer.cpp b/examples/qt_viewer/qt_viewer.cpp new file mode 100644 index 000000000..d5410c534 --- /dev/null +++ b/examples/qt_viewer/qt_viewer.cpp @@ -0,0 +1,73 @@ +/* LICENSE BEGIN + This file is part of the SixtyFPS Project -- https://sixtyfps.io + Copyright (c) 2020 Olivier Goffart + Copyright (c) 2020 Simon Hausmann + + 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 +#include + +#include "ui_interface.h" + + +struct LoadedFile { + sixtyfps::ComponentHandle 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 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{ instance, wid }); + }); + main.show(); + return app.exec(); +} + diff --git a/sixtyfps_runtime/corelib/window.rs b/sixtyfps_runtime/corelib/window.rs index 92966a3a2..280bb6630 100644 --- a/sixtyfps_runtime/corelib/window.rs +++ b/sixtyfps_runtime/corelib/window.rs @@ -65,6 +65,9 @@ pub trait PlatformWindow { &self, source: Pin<&crate::properties::Property>, ) -> 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 diff --git a/sixtyfps_runtime/interpreter/ffi.rs b/sixtyfps_runtime/interpreter/ffi.rs index b54cda2e3..37b12a0f9 100644 --- a/sixtyfps_runtime/interpreter/ffi.rs +++ b/sixtyfps_runtime/interpreter/ffi.rs @@ -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::(), + core::mem::size_of::() + ); + core::ptr::write(out as *mut ComponentWindow, inst.window()) +} + /// Instantiate an instance from a definition. /// /// The `out` must be uninitialized and is going to be initialized after the call diff --git a/sixtyfps_runtime/rendering_backends/gl/graphics_window.rs b/sixtyfps_runtime/rendering_backends/gl/graphics_window.rs index ad32f8ff6..09c272d0a 100644 --- a/sixtyfps_runtime/rendering_backends/gl/graphics_window.rs +++ b/sixtyfps_runtime/rendering_backends/gl/graphics_window.rs @@ -502,6 +502,10 @@ impl PlatformWindow for GraphicsWindow { } } } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } struct MappedWindow { diff --git a/sixtyfps_runtime/rendering_backends/qt/lib.rs b/sixtyfps_runtime/rendering_backends/qt/lib.rs index 588f3245f..9e7df3aa2 100644 --- a/sixtyfps_runtime/rendering_backends/qt/lib.rs +++ b/sixtyfps_runtime/rendering_backends/qt/lib.rs @@ -39,7 +39,8 @@ pub fn use_modules() -> usize { } #[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 } } diff --git a/sixtyfps_runtime/rendering_backends/qt/qt_window.rs b/sixtyfps_runtime/rendering_backends/qt/qt_window.rs index 8582760bb..e485a128b 100644 --- a/sixtyfps_runtime/rendering_backends/qt/qt_window.rs +++ b/sixtyfps_runtime/rendering_backends/qt/qt_window.rs @@ -1010,6 +1010,10 @@ impl PlatformWindow for QtWindow { }) .unwrap_or_default() } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } 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() } + +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::().as_ptr() + }) + } +} diff --git a/sixtyfps_runtime/rendering_backends/qt/widgets.rs b/sixtyfps_runtime/rendering_backends/qt/widgets.rs index d7ec20c93..7ab208814 100644 --- a/sixtyfps_runtime/rendering_backends/qt/widgets.rs +++ b/sixtyfps_runtime/rendering_backends/qt/widgets.rs @@ -130,6 +130,9 @@ cpp! {{ void ensure_initialized() { static auto app [[maybe_unused]] = []{ + if (qApp) { + return qApp; + } QCoreApplication::setAttribute(Qt::AA_PluginApplication, true); static int argc = 1; static char argv[] = "sixtyfps";