Files
logos-tutorial/tutorial-cpp-ui-app.md
T

643 lines
24 KiB
Markdown
Raw Normal View History

# Tutorial Part 3: Building a C++ UI Module (Process-Isolated)
2026-03-19 19:42:15 +01:00
This is Part 3 of the Logos module tutorial series. In [Part 2](tutorial-qml-ui-app.md) you built a QML-only UI plugin. Now you'll build a **ui_qml module with a C++ backend** — the backend runs in a separate `ui-host` process while the QML view loads in the host app (basecamp / standalone).
2026-03-19 19:42:15 +01:00
**What you'll build:** A `calc_ui_cpp` module with:
2026-03-19 19:42:15 +01:00
- A `.rep` file defining the remote interface (slots + properties)
- A C++ backend plugin that inherits from the generated `SimpleSource` base class
- A QML view that calls the backend via a typed replica using `logos.watch()`
- Process isolation: backend crashes can't bring down the host app
2026-03-19 19:42:15 +01:00
**Why C++ backend over QML-only?**
2026-03-19 19:42:15 +01:00
2026-04-21 10:45:21 +01:00
| | QML-only (Part 2) | C++ backend (Part 3) |
| ----------------- | ----------------------------------------------------------------- | --------------------------------------- |
| Compilation | None | CMake + Qt |
| Process isolation | No (QML runs in-process) | Yes (C++ in separate `ui-host` process) |
| Backend calls | `logos.callModule()` / `logos.callModuleAsync()` to other modules | `LogosModules` typed SDK in C++ |
| Type safety | Args travel as `QVariant` | C++ types preserved |
| QML ↔ backend | Direct bridge | Qt Remote Objects (typed replica) |
| `.rep` file | Not needed | Required — defines the remote interface |
2026-03-19 19:42:15 +01:00
**Prerequisites:**
- Completed [Part 1](tutorial-wrapping-c-library.md) — you have a working `calc_module` with the shared library built (`.so` on Linux, `.dylib` on macOS in `logos-calc-module/lib/`)
2026-03-19 19:42:15 +01:00
- Nix with flakes enabled
---
## Architecture
2026-03-19 19:42:15 +01:00
```
logos-basecamp / logos-standalone-app
┌─────────────────────────────────────────────┐
│ │
│ QML View (Main.qml) │
│ readonly property var backend: │
│ logos.module("calc_ui_cpp") │
│ logos.watch(backend.add(1,2))│
│ │ │
│ │ Qt Remote Objects (socket) │
└──────────┼──────────────────────────────────┘
ui-host process (separate)
┌──────────┼──────────────────────────────────┐
│ ▼ │
│ CalcUiCppPlugin (backend) │
│ : CalcUiCppSimpleSource │
│ : CalcUiCppViewPluginBase │
│ int add(int a, int b) { │
│ return m_logos->calc_module.add(a,b); │
│ } │
│ │ │
│ │ LogosModules typed SDK │
│ ▼ │
│ calc_module (loaded in ui-host) │
└─────────────────────────────────────────────┘
2026-03-19 19:42:15 +01:00
```
The `.rep` file declares the interface. At build time, Qt's `repc` compiler generates:
2026-04-21 10:45:21 +01:00
- **`CalcUiCppSimpleSource`** — base class the backend inherits from
- **`CalcUiCppReplica`** — typed replica the QML view uses
- **`calc_ui_cpp_replica_factory`** — separate plugin that the host loads to create typed replicas
2026-03-19 19:42:15 +01:00
---
## Step 1: Scaffold
```bash
mkdir logos-calc-ui-cpp && cd logos-calc-ui-cpp
nix flake init -t github:logos-co/logos-module-builder/tutorial-v2#ui-qml-backend
2026-03-19 19:42:15 +01:00
git init && git add -A
```
This creates the template. We'll customize it for our calculator.
2026-03-19 19:42:15 +01:00
---
## Step 2: metadata.json
2026-03-19 19:42:15 +01:00
```json
{
"name": "calc_ui_cpp",
"version": "1.0.0",
"type": "ui_qml",
"category": "tools",
"description": "Calculator C++ UI — QML view with process-isolated backend",
2026-03-19 19:42:15 +01:00
"main": "calc_ui_cpp_plugin",
"view": "qml/Main.qml",
"icon": "icons/calc.png",
2026-03-19 19:42:15 +01:00
"dependencies": ["calc_module"],
"nix": {
"packages": { "build": [], "runtime": [] },
"external_libraries": [],
"cmake": { "find_packages": [], "extra_sources": [] }
}
2026-03-19 19:42:15 +01:00
}
```
Key fields:
2026-04-21 10:45:21 +01:00
- `"type": "ui_qml"` — tells the builder this is a QML view module
- `"main": "calc_ui_cpp_plugin"` — the backend Qt plugin library (without extension)
- `"view": "qml/Main.qml"` — the QML entry point
- `"dependencies": ["calc_module"]` — core modules the backend calls
2026-03-19 19:42:15 +01:00
---
## Step 3: The `.rep` File
Create `src/calc_ui_cpp.rep`:
```rep
class CalcUiCpp
{
PROP(QString status READWRITE)
SLOT(int add(int a, int b))
SLOT(int multiply(int a, int b))
SLOT(int factorial(int n))
SLOT(int fibonacci(int n))
SLOT(QString libVersion())
}
```
This is the **single source of truth** for the remote interface. `repc` generates:
2026-04-21 10:45:21 +01:00
- `rep_calc_ui_cpp_source.h``CalcUiCppSimpleSource` with virtual slots the backend overrides
- `rep_calc_ui_cpp_replica.h``CalcUiCppReplica` with typed methods and auto-synced properties
**PROP** values auto-sync from backend to QML replica. **SLOT** return values are delivered as `QRemoteObjectPendingReply` — use `logos.watch()` in QML to get them as JS Promises.
---
## Step 3.1: Update the interface header
The scaffolded template may create an interface file like `src/ui_example_interface.h`. Rename it to match this tutorial and make sure the class/IID names are updated, or the plugin metadata wiring will break.
```bash
# If your scaffold created ui_example files, rename the interface header:
mv src/ui_example_interface.h src/calc_ui_cpp_interface.h
```
Set `src/calc_ui_cpp_interface.h` to:
```cpp
#pragma once
#include <QObject>
#include <QString>
#include "interface.h"
class CalcUiCppInterface : public PluginInterface
{
public:
virtual ~CalcUiCppInterface() = default;
};
#define CalcUiCppInterface_iid "org.logos.CalcUiCppInterface"
Q_DECLARE_INTERFACE(CalcUiCppInterface, CalcUiCppInterface_iid)
```
Your plugin header should then include `calc_ui_cpp_interface.h` and use:
- `Q_PLUGIN_METADATA(IID CalcUiCppInterface_iid FILE "metadata.json")`
- `Q_INTERFACES(CalcUiCppInterface)`
If the interface filename or IID symbol doesn't match, you'll typically get build errors (missing header/symbol) or plugin-load failures at runtime.
---
## Step 4: CMakeLists.txt
2026-03-19 19:42:15 +01:00
```cmake
cmake_minimum_required(VERSION 3.14)
project(CalcUiCppPlugin LANGUAGES CXX)
if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
else()
message(FATAL_ERROR "LogosModule.cmake not found. Set LOGOS_MODULE_BUILDER_ROOT.")
endif()
logos_module(
NAME calc_ui_cpp
REP_FILE src/calc_ui_cpp.rep
2026-03-19 19:42:15 +01:00
SOURCES
src/calc_ui_cpp_interface.h
src/calc_ui_cpp_plugin.h
src/calc_ui_cpp_plugin.cpp
)
```
`REP_FILE` tells `logos_module()` to:
2026-04-21 10:45:21 +01:00
1. Run `repc` to generate source/replica headers
2. Generate `LogosViewPluginBase` (typed remoting base class)
3. Build a separate `calc_ui_cpp_replica_factory` shared library
2026-03-19 19:42:15 +01:00
---
## Step 5: C++ Backend Plugin
### `src/calc_ui_cpp_plugin.h`
2026-03-19 19:42:15 +01:00
```cpp
2026-04-01 17:57:24 +02:00
#pragma once
#include <QString>
#include <QVariantList>
#include "calc_ui_cpp_interface.h"
#include "LogosViewPluginBase.h"
#include "rep_calc_ui_cpp_source.h"
2026-04-01 17:57:24 +02:00
class LogosAPI;
class LogosModules;
2026-04-01 17:57:24 +02:00
class CalcUiCppPlugin : public CalcUiCppSimpleSource,
public CalcUiCppInterface,
public CalcUiCppViewPluginBase
2026-03-25 10:37:15 +01:00
{
Q_OBJECT
Q_PLUGIN_METADATA(IID CalcUiCppInterface_iid FILE "metadata.json")
Q_INTERFACES(CalcUiCppInterface)
2026-03-25 10:37:15 +01:00
public:
explicit CalcUiCppPlugin(QObject* parent = nullptr);
~CalcUiCppPlugin() override;
QString name() const override { return "calc_ui_cpp"; }
QString version() const override { return "1.0.0"; }
Q_INVOKABLE void initLogos(LogosAPI* api);
// Slots from .rep — override the generated virtuals
int add(int a, int b) override;
int multiply(int a, int b) override;
int factorial(int n) override;
int fibonacci(int n) override;
QString libVersion() override;
2026-03-25 10:37:15 +01:00
signals:
void eventResponse(const QString& eventName, const QVariantList& args);
private:
LogosAPI* m_logosAPI = nullptr;
LogosModules* m_logos = nullptr;
2026-03-25 10:37:15 +01:00
};
```
Three base classes:
2026-04-21 10:45:21 +01:00
- **`CalcUiCppSimpleSource`** — generated from `.rep`, provides the typed source for Qt Remote Objects
- **`CalcUiCppInterface`** — standard Logos plugin interface (`name()`, `version()`)
- **`CalcUiCppViewPluginBase`** — generated, provides `setBackend()` and `enableRemoting()`
2026-03-25 10:37:15 +01:00
### `src/calc_ui_cpp_plugin.cpp`
2026-03-19 19:42:15 +01:00
```cpp
#include "calc_ui_cpp_plugin.h"
#include "logos_api.h"
#include "logos_sdk.h"
2026-03-19 19:42:15 +01:00
CalcUiCppPlugin::CalcUiCppPlugin(QObject* parent)
: CalcUiCppSimpleSource(parent) {}
2026-03-19 19:42:15 +01:00
CalcUiCppPlugin::~CalcUiCppPlugin() { delete m_logos; }
2026-03-19 19:42:15 +01:00
void CalcUiCppPlugin::initLogos(LogosAPI* api)
2026-03-19 19:42:15 +01:00
{
m_logosAPI = api;
m_logos = new LogosModules(api);
// Register this object as the Remote Objects source
setBackend(this);
}
2026-03-19 19:42:15 +01:00
int CalcUiCppPlugin::add(int a, int b)
{
return m_logos->calc_module.add(a, b);
}
2026-03-19 19:42:15 +01:00
int CalcUiCppPlugin::multiply(int a, int b)
{
return m_logos->calc_module.multiply(a, b);
}
2026-03-19 19:42:15 +01:00
int CalcUiCppPlugin::factorial(int n)
{
return m_logos->calc_module.factorial(n);
}
2026-03-19 19:42:15 +01:00
int CalcUiCppPlugin::fibonacci(int n)
{
return m_logos->calc_module.fibonacci(n);
}
QString CalcUiCppPlugin::libVersion()
{
return m_logos->calc_module.libVersion();
}
2026-03-19 19:42:15 +01:00
```
Key points:
2026-04-21 10:45:21 +01:00
- Constructor calls `CalcUiCppSimpleSource(parent)` — not `QObject(parent)`
- `initLogos()` calls `setBackend(this)` to register with the Remote Objects host
- Slots return values directly — they travel back to the QML replica via Qt Remote Objects
- `m_logos->calc_module.add(a, b)` uses the generated typed SDK (type-safe, no QVariant)
2026-03-19 19:42:15 +01:00
---
## Step 6: QML View
2026-03-19 19:42:15 +01:00
Create `src/qml/Main.qml`:
2026-03-19 19:42:15 +01:00
```qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
2026-03-19 19:42:15 +01:00
Item {
id: root
property string result: ""
property string errorText: ""
// Typed replica of the backend running in ui-host
readonly property var backend: logos.module("calc_ui_cpp")
// "status" property from the .rep — auto-synced via Qt Remote Objects
readonly property string status: backend ? backend.status : ""
function callCalc(method, args) {
if (!backend) {
root.errorText = "Backend not available"
return
}
root.errorText = ""
root.result = "..."
// logos.watch() wraps the pending reply in a JS Promise
2026-04-21 10:46:34 +01:00
logos.watch(backend[method].apply(backend, args),
function(value) { root.result = String(value) },
function(error) { root.errorText = String(error) }
)
}
2026-03-19 19:42:15 +01:00
ColumnLayout {
anchors.fill: parent
anchors.margins: 24
spacing: 16
Text {
text: "Calculator (C++ backend)"
2026-03-19 19:42:15 +01:00
font.pixelSize: 20
color: "#ffffff"
}
RowLayout {
spacing: 12
TextField {
id: inputA; placeholderText: "a"
Layout.preferredWidth: 80
validator: IntValidator {}
}
TextField {
id: inputB; placeholderText: "b"
Layout.preferredWidth: 80
validator: IntValidator {}
}
2026-03-19 19:42:15 +01:00
Button {
text: "Add"
onClicked: root.callCalc("add", [parseInt(inputA.text) || 0,
parseInt(inputB.text) || 0])
2026-03-19 19:42:15 +01:00
}
Button {
text: "Multiply"
onClicked: root.callCalc("multiply", [parseInt(inputA.text) || 0,
parseInt(inputB.text) || 0])
2026-03-19 19:42:15 +01:00
}
}
Rectangle {
Layout.fillWidth: true; height: 56
color: root.errorText ? "#3d1a1a" : "#1a2d1a"
2026-03-19 19:42:15 +01:00
radius: 8
Text {
anchors.centerIn: parent
text: root.errorText || root.result || "Press a button"
color: root.errorText ? "#f85149" : "#56d364"
2026-03-19 19:42:15 +01:00
font.pixelSize: 15
}
}
Text {
text: "Backend status: " + root.status
color: "#8b949e"; font.pixelSize: 13
}
2026-03-19 19:42:15 +01:00
}
}
```
Key patterns:
2026-04-21 10:45:21 +01:00
- `logos.module("calc_ui_cpp")` — gets the typed replica (auto-synced properties)
- `backend.status` — PROP from `.rep`, updates automatically
2026-04-21 10:46:34 +01:00
- `logos.watch(backend.add(1, 2), ...)` — SLOT return value as JS Promise
2026-04-21 10:45:21 +01:00
- ``— required for`logos.watch()`
2026-03-19 19:42:15 +01:00
---
## Step 6.5: Use the Logos Design System in your QML
The QML you load above runs inside the host (`logos-basecamp` / `logos-standalone-app`), which already has `logos-design-system` on the QML import path. Use its themed components rather than rolling your own visuals — your module gets the polished look automatically as the design system evolves.
```qml
import Logos.Theme
import Logos.Controls
import Logos.Icons // optional shared icon assets
LogosButton {
text: qsTr("Add")
onClicked: root.callCalc("add", [parseInt(inputA.text) || 0,
parseInt(inputB.text) || 0])
}
LogosTextField {
id: inputA
placeholderText: qsTr("a")
}
Rectangle {
color: Theme.palette.backgroundSecondary
radius: Theme.spacing.radiusSmall
LogosText { text: qsTr("Result"); color: Theme.palette.text }
}
```
**Discover what's available** by running the storybook:
```bash
cd repos/logos-design-system && nix run
```
The sidebar splits components into:
- **Controls** — designed per Figma, production-ready (`LogosButton`, `LogosBadge`, `LogosCheckbox`, `LogosComboBox`, `LogosIconButton`, `LogosPaginator`, `LogosSearchBar`, `LogosTabBar`, `LogosTable`, `LogosText`, `LogosTextField`, `LogosToolTip`, …).
- **Controls (not designed)** — placeholders with stable APIs but unstyled visuals (`LogosDialog`, `LogosDrawer`, `LogosScrollView`, `LogosSpinner`, `LogosTextArea`, `LogosSwitch`, …). You can ship with them; they'll get the polished look applied later without you having to change your QML.
**Theme tokens** (use these instead of hex literals or magic font sizes):
- `Theme.palette.*``background`, `backgroundSecondary`, `surface`, `text`, `textSecondary`, `border`, `primary`, `success`, `warning`, `error`, `info`, `hover`, `pressed`, …
- `Theme.spacing.*``tiny`, `small`, `medium`, `large`, `xlarge`, `xxlarge`, `radiusSmall`, `radiusMedium`, `radiusLarge`
- `Theme.typography.*``pageTitleText` (36), `titleText` (30), `panelTitleText` (24), `subtitleText` (16), `primaryText` (14), `secondaryText` (12); `weightRegular` / `weightMedium` / `weightBold`; `publicSans`
- `Logos.Icons.LogosIcons.*``arrowLeft`, `arrowRight`, `refresh`, `install`, `trash`, `more`, `search`, …
**Feedback and contributions**
Feel free to report bugs, file feature requests, or contribute components / theme tokens upstream — all welcome at `logos-co/logos-design-system`. The same fix lifts every consumer, so upstreaming is the most impactful path. If you can sketch the public API you'd like to use in a feature request, it makes review and implementation much faster.
---
## Step 7: flake.nix
2026-03-19 19:42:15 +01:00
```nix
{
description = "Calculator C++ UI plugin — QML view with process-isolated backend";
2026-03-19 19:42:15 +01:00
inputs = {
logos-module-builder.url = "github:logos-co/logos-module-builder/tutorial-v2";
# Option A: point to a remote repo (for CI or when calc_module is published)
calc_module.url = "github:logos-co/logos-tutorial/tutorial-v2?dir=logos-calc-module";
# Option B: point to your local checkout (for local development)
# calc_module.url = "path:../logos-calc-module";
2026-03-19 19:42:15 +01:00
};
outputs = inputs@{ logos-module-builder, ... }:
logos-module-builder.lib.mkLogosQmlModule {
src = ./.;
configFile = ./metadata.json;
flakeInputs = inputs;
};
2026-03-19 19:42:15 +01:00
}
```
The `calc_module` input attribute name must match the dependency name in `metadata.json`. The URL can be:
2026-04-21 10:45:21 +01:00
- **`github:`** — fetches from a remote GitHub repo. Use for CI or when `calc_module` is published.
- **`path:`** — points to a local directory on disk (e.g., `path:../logos-calc-module`). Use during local development.
2026-05-28 13:03:51 -03:00
> **Important:** `calc_module` bundles a small C library (`libcalc`) compiled **from source** during its own Nix build — there's no prebuilt `.so`/`.dylib` to stage. Building `calc_module` (which happens automatically when this app pulls it in) compiles it for you. See [Part 1, Step 1.5](tutorial-wrapping-c-library.md#15-add-a-makefile-to-build-the-library).
`mkLogosQmlModule` handles everything: compiles the C++ backend (because `main` is set), bundles the QML view, generates LGX packages, and wires up `nix run`.
2026-03-19 19:42:15 +01:00
---
## Step 8: Build and Run
2026-03-19 19:42:15 +01:00
2026-05-28 13:03:51 -03:00
First, make sure your local `calc_module` builds cleanly — its C library is compiled from source during the Nix build (from `lib/libcalc.c` via `lib/Makefile`), so there's nothing to pre-build by hand (see [Part 1, Step 1.5](tutorial-wrapping-c-library.md#15-add-a-makefile-to-build-the-library)):
```bash
2026-05-28 13:03:51 -03:00
cd ../logos-calc-module && git add -A && nix build && cd -
```
Then build and run. Choose the approach that matches your `flake.nix` setup:
2026-03-19 19:42:15 +01:00
```bash
git add -A
# If flake.nix uses path:../logos-calc-module — just run directly:
nix run
# If flake.nix uses github: — override to use your local checkout:
nix run --override-input calc_module path:../logos-calc-module
# Or from the workspace:
./scripts/ws run logos-calc-ui-cpp --local logos-calc-ui-cpp logos-calc-module
2026-03-19 19:42:15 +01:00
```
### Live reloading QML with `DEV_QML_PATH`
For QML iteration, point `DEV_QML_PATH` at the directory that contains your view entry's **basename** (from `metadata.json` `"view"`). This tutorial sets `"view": "qml/Main.qml"`, so the directory must contain `Main.qml` (here: `qml/` at the repo root):
```bash
DEV_QML_PATH=$PWD/qml nix run .
```
When `DEV_QML_PATH` is set, `logos-standalone-app` loads QML from your source tree at runtime instead of the installed copy — so edits to `Main.qml` (and any QML under that tree) are picked up on the next relaunch without you having to re-sync files.
**Important — what this does *not* skip.** `nix run` always re-evaluates the flake and rehashes the source tree before launching. By default `src = ./.` includes every tracked file, including `*.qml` — so:
- **Any source change, including QML edits, rebuilds the plugin** before the app starts. `DEV_QML_PATH` only kicks in *after* the build is done; it doesn't shortcut the rebuild itself.
- **C++ / `.rep` / `metadata.json` / CMake changes** rebuild as normal.
- The flake-evaluation overhead on each `nix run` is fixed and unavoidable while invoking through nix.
For the absolute fastest loop (no nix involvement after the first build), do the build once and run the resulting binary directly:
```bash
# Build once — populates result/ in the nix store
nix build .
# Subsequent runs: invoke the bundled standalone wrapper directly,
# skipping nix entirely. DEV_QML_PATH still redirects QML loading.
DEV_QML_PATH=$PWD/qml ./result/bin/run-logos-standalone-ui
```
(Adjust the binary name to whatever `ls result/bin/` shows on your build.)
> **Naming:** Only `DEV_QML_PATH` is honored by `logos-standalone-app`. See `repos/logos-standalone-app/README.md`.
> This does not work with `logos-basecamp` — Basecamp loads QML plugins from its own install tree, so source edits are not picked up until you rebuild and reinstall the `.lgx`.
2026-03-19 19:42:15 +01:00
---
## Step 9: How the Pieces Connect
2026-03-19 19:42:15 +01:00
1. `nix build` → compiles the C++ plugin + replica factory, bundles QML view
2. `nix run` → launches `logos-standalone-app` which:
- Loads `calc_module` (dependency)
- Spawns a `ui-host` child process with `calc_ui_cpp_plugin.so`
- `ui-host` calls `initLogos()``setBackend(this)``enableRemoting(host)`
- Backend is now accessible over a local socket
3. Host app loads `calc_ui_cpp_replica_factory.dylib` → creates a typed replica
4. QML gets the replica via `logos.module("calc_ui_cpp")`
5. `backend.add(1, 2)` → Qt Remote Objects sends call to ui-host → backend runs → returns result
6. `backend.status` auto-syncs whenever the backend calls `setStatus(...)`
2026-03-19 19:42:15 +01:00
2026-03-25 10:37:15 +01:00
---
## Step 10: UI Integration Tests (Optional)
Add automated UI tests using the [logos-qt-mcp](https://github.com/logos-co/logos-qt-mcp) test framework. Just create `.mjs` files in `tests/` and `logos-module-builder` auto-wires `nix build .#integration-test`.
Create `tests/ui-tests.mjs`:
```javascript
import { resolve } from "node:path";
// CI sets LOGOS_QT_MCP automatically; for interactive use: nix build .#test-framework -o result-mcp
2026-04-21 10:45:21 +01:00
const root =
process.env.LOGOS_QT_MCP ||
new URL("../result-mcp", import.meta.url).pathname;
const { test, run } = await import(
resolve(root, "test-framework/framework.mjs")
);
test("calc_ui_cpp: loads and shows title", async (app) => {
await app.waitFor(
2026-04-21 10:45:21 +01:00
async () => {
await app.expectTexts(["UI Example (C++ backend)"]);
},
{ timeout: 15000, interval: 500, description: "UI to load" },
);
});
test("calc_ui_cpp: shows connection status", async (app) => {
await app.expectTexts(["Connecting to backend..."]);
});
test("calc_ui_cpp: add button visible", async (app) => {
await app.expectTexts(["Add"]);
});
run();
```
```bash
git add tests/
# Hermetic CI test
nix build .#integration-test -L
# Interactive
nix build .#test-framework -o result-mcp
nix run . # app with inspector on :3768
node tests/ui-tests.mjs # in another terminal
```
---
## Comparison: .rep Interface Patterns
2026-03-25 10:37:15 +01:00
2026-04-21 10:45:21 +01:00
| Pattern | .rep declaration | Backend C++ | QML usage |
| ---------------- | ------------------------------------ | ------------------------------------------- | ---------------------------------------------------------------------- |
2026-04-21 10:46:34 +01:00
| **Return value** | `SLOT(int add(int a, int b))` | `int add(...) override { return ...; }` | `logos.watch(backend.add(1,2), cb)` |
2026-04-21 10:45:21 +01:00
| **Property** | `PROP(QString status READWRITE)` | `setStatus("Ready")` (inherited) | `backend.status` (auto-syncs) |
| **Signal** | `SIGNAL(errorOccurred(QString msg))` | `emit errorOccurred("fail")` | `Connections { target: backend; function onErrorOccurred(msg) {...} }` |
| **Model** | (use Q_PROPERTY on backend) | `Q_PROPERTY(QAbstractItemModel* items ...)` | `logos.model("calc_ui_cpp", "items")` |
2026-03-19 19:42:15 +01:00
---
2026-03-19 19:42:15 +01:00
## Next Steps
- Add more `.rep` properties/signals for richer UI state
- Use `logos.model()` for list views backed by `QAbstractItemModel`
- Package as `.lgx` for distribution: `nix build .#lgx`
- **Use the Logos Design System** in your QML — see [Step 6.5](#step-65-use-the-logos-design-system-in-your-qml). Browse components in the storybook (`cd repos/logos-design-system && nix run`); file issues at `logos-co/logos-design-system` (bugs on designed components, *feature* type for new components / variants / theme tokens).
- See [logos-package-manager-ui](https://github.com/logos-co/logos-package-manager-ui) for a production example