mirror of
https://github.com/logos-co/logos-dev-boost.git
synced 2026-09-01 10:31:06 +00:00
6.3 KiB
6.3 KiB
UI App Development
IComponent Pattern
UI apps are Qt plugins loaded directly by Basecamp. They implement IComponent, which is vendored locally in every project (not provided by the SDK):
interfaces/IComponent.h:
#pragma once
#include <QObject>
#include <QWidget>
#include <QtPlugin>
class LogosAPI;
class IComponent {
public:
virtual ~IComponent() = default;
virtual QWidget* createWidget(LogosAPI* logosAPI = nullptr) = 0;
virtual void destroyWidget(QWidget* widget) = 0;
};
#define IComponent_iid "com.logos.component.IComponent"
Q_DECLARE_INTERFACE(IComponent, IComponent_iid)
The plugin class inherits both QObject and IComponent, and uses Q_PLUGIN_METADATA:
class MyPlugin : public QObject, public IComponent {
Q_OBJECT
Q_INTERFACES(IComponent)
Q_PLUGIN_METADATA(IID IComponent_iid FILE "../metadata.json")
public:
QWidget* createWidget(LogosAPI* logosAPI = nullptr) override;
void destroyWidget(QWidget* widget) override;
};
CMakeLists.txt Requirements
set(CMAKE_AUTOMOC ON) # not AUTORCC
logos_module(
NAME my_app
SOURCES ...
INCLUDE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}/interfaces # required for #include <IComponent.h>
)
find_package(Qt6 REQUIRED COMPONENTS Widgets Quick QuickWidgets QuickControls2)
qt_add_resources(my_app_module_plugin ui_qml_resources
PREFIX "/"
FILES src/qml/Main.qml
)
QML is embedded via qt_add_resources (no .qrc file needed). The embedded path is qrc:/src/qml/Main.qml.
C++/QML Boundary Rules
Every piece of logic must go in the right layer:
| Concern | C++ (backend class) | QML |
|---|---|---|
| Data models, state | Q_PROPERTY on QObject |
Bind to backend.property |
| Business logic | Methods on backend class | Never — no JS business logic |
| Module calls | Via LogosAPI* |
Never |
| File I/O, networking | Always C++ | Never |
| UI layout, styling | Never | QML; use Logos.Theme inside Basecamp |
| User interactions | Q_INVOKABLE slots |
onClicked: backend.doThing() |
| Plugin lifecycle | IComponent::createWidget/destroyWidget |
N/A |
Plugin createWidget() Pattern
QWidget* MyPlugin::createWidget(LogosAPI* logosAPI) {
QQuickStyle::setStyle("Basic"); // consistent cross-platform rendering
auto* quickWidget = new QQuickWidget();
quickWidget->setMinimumSize(800, 600);
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
auto* backend = new MyBackend(logosAPI, quickWidget);
quickWidget->rootContext()->setContextProperty("backend", backend);
// Dev mode: export QML_PATH=$PWD/src/qml to load Main.qml from disk
const QString devSource = QString::fromUtf8(qgetenv("QML_PATH"));
const QUrl qmlUrl = devSource.isEmpty()
? QUrl("qrc:/src/qml/Main.qml")
: QUrl::fromLocalFile(QDir(devSource).filePath("Main.qml"));
quickWidget->setSource(qmlUrl);
if (quickWidget->status() == QQuickWidget::Error) {
qWarning() << "MyPlugin: failed to load QML from" << qmlUrl;
for (const auto& e : quickWidget->errors())
qWarning() << e.toString();
}
return quickWidget;
}
Backend Class Pattern
class MyBackend : public QObject {
Q_OBJECT
Q_PROPERTY(QVariantList items READ items NOTIFY itemsChanged)
Q_PROPERTY(int itemCount READ itemCount NOTIFY itemCountChanged)
Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusMessageChanged)
public:
explicit MyBackend(LogosAPI* api, QObject* parent = nullptr);
QVariantList items() const;
int itemCount() const;
QString statusMessage() const;
Q_INVOKABLE void addItem(const QString& text);
Q_INVOKABLE void removeItem(int index);
signals:
void itemsChanged();
void itemCountChanged();
void statusMessageChanged();
private:
LogosAPI* m_api;
QVariantList m_items;
QString m_statusMessage;
};
- Expose every QML-bound value with a
Q_PROPERTY+ NOTIFY signal statusMessageis useful for feedback displayed in a status bar- Actions go in
Q_INVOKABLEmethods - Call other modules via
LogosAPI*inside the backend
QML Conventions
- Entry point is always
src/qml/Main.qml, embedded atqrc:/src/qml/Main.qml - Root element:
Rectangle { anchors.fill: parent } - React to backend signals via
Connections { target: backend } - Inside Basecamp: use
Logos.Themefor colors,Logos.Controlsfor components - When running standalone (
nix run .): use plain QtQuick;Logos.Themeis not available - Never hardcode UI logic in JavaScript
Dev Mode
export QML_PATH=$PWD/src/qml
nix run . # loads Main.qml from disk; restart app to pick up QML changes
C++ changes always require nix build.
Standalone Test
nix build
nix run . # or: nix run .#app
mkLogosModule with "type": "ui" automatically wires up apps.default — no manual flake setup needed.
Calling Logos Modules
From C++ backend:
auto* client = m_logosAPI->getClient("storage_module");
QVariant result = client->invokeRemoteMethod("storage_module", "save", key, value);
Or with generated typed wrappers (recommended when available):
#include "logos_sdk.h" // generated at build time from metadata.json dependencies
LogosModules logos(m_logosAPI);
logos.storage_module.save(key, value);
Always declare module dependencies in metadata.json "dependencies" so they are loaded before the UI app.
metadata.json for UI Apps
{
"name": "my_app",
"type": "ui",
"version": "1.0.0",
"description": "My UI application",
"icon": "icons/my_app.png",
"category": "tools",
"main": "my_app_plugin",
"dependencies": ["storage_module"],
"nix": {
"packages": { "build": [], "runtime": ["qt6.qtdeclarative"] },
"external_libraries": [],
"cmake": { "find_packages": [], "extra_sources": [] }
}
}