Write your first plugin
An Easy Writer plugin is a native shared library the app discovers at startup and loads. On load it declares its ABI level and identity and, through a host-supplied PluginContext, registers what it adds: colour themes, AI commands, and — from ABI 2 — whole new dockable panels and menu commands that read and edit the open project. A plugin links ew::core + ew::app, so a panel can use the same model and engines the app itself uses. Everything reachable that way is documented in this reference; this guide builds a panel that reads the codex and the relationship graph.
The shape of a plugin
The concrete plugin is a QObject that also implements the pure-vtable ew::app::Plugin interface. The Q_PLUGIN_METADATA / Q_INTERFACES macros wire it up so QPluginLoader can find and cast it, while the interface itself stays a plain vtable that is safe across the DLL/ABI boundary.
#include <ew/app/Plugin.h>
#include <ew/app/PluginAppContext.h>
#include <ew/app/PluginContext.h>
#include <ew/app/PluginContributions.h>
#include <QObject>
#include <QWidget>
class MyPlugin : public QObject, public ew::app::Plugin {
Q_OBJECT
Q_PLUGIN_METADATA(IID EW_PLUGIN_IID)
Q_INTERFACES(ew::app::Plugin)
public:
int abiVersion() const override { return ew::app::kPluginAbiVersion; }
ew::app::PluginMetadata metadata() const override {
return {.id = QStringLiteral("games.acme.graph"),
.name = QStringLiteral("Graph Explorer"),
.version = QStringLiteral("1.0.0"),
.author = QStringLiteral("Acme"),
.description = QStringLiteral("A panel that explores the relationship graph.")};
}
void registerContributions(ew::app::PluginContext& context) override {
context.addPanel({.id = QStringLiteral("games.acme.graph.panel"),
.title = QStringLiteral("Graph Explorer"),
.makeWidget = [](ew::app::PluginAppContext& app) -> QWidget* {
return makeGraphPanel(app);
}});
}
};
#include "MyPlugin.moc"
abiVersion()returnskPluginAbiVersion— the host rejects the plugin unless this matches the app's ABI, so a plugin built against a different version is cleanly ignored (its reason recorded in Preferences ▸ Plugins) rather than mis-called.metadata()returns aPluginMetadata— the identity shown in Preferences and used in load diagnostics.registerContributions()receives thePluginContext. BesidesaddTheme(aTheme) andaddPromptCommand(aPromptCommand), ABI 2 addsaddPanel(aPluginPanel) andaddMenuCommand(aPluginMenuCommand).
Reaching the data: PluginAppContext
A PluginPanel's makeWidget factory is called once, on the GUI thread, with a live PluginAppContext. That is the handle to the running app:
project()— the openew::core::Project, to read and traverse the whole model.commands()— theCommandStack; push a command here and your edit becomes one undo step, exactly like a UI edit.mainWindow()— the top-levelQWidget, for parenting dialogs.
Read the project, build a graph
makeGraphPanel reads the codex and turns the relationship graph into a laid-out set of points, using the same engine the built-in Relationship dock uses.
#include <ew/app/PluginAppContext.h>
#include <ew/app/RelationshipGraph.h>
#include <ew/app/RelationshipLayout.h>
#include <ew/core/Entity.h>
#include <ew/core/Project.h>
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
QWidget* makeGraphPanel(ew::app::PluginAppContext& app) {
ew::core::Project& project = app.project();
// Read: count the codex entities (project.ids() enumerates every content object).
int entities = 0;
for (const ew::core::ContentId id : project.ids())
if (dynamic_cast<const ew::core::Entity*>(project.find(id)) != nullptr)
++entities;
// Build + lay out the relationship graph (nodes = entities, edges = Reference fields).
const ew::app::RelationshipGraph graph = ew::app::relationshipGraph(project);
const std::map<ew::core::ContentId, QPointF> positions =
ew::app::forceDirectedLayout(graph);
// graph.nodes: {id, name, categoryId, category} graph.edges: {from, to, label}
// Also available: neighborhood(), filterByCategories(), shortestRelationshipPath(), radialLayout().
auto* widget = new QWidget;
auto* layout = new QVBoxLayout(widget);
layout->addWidget(new QLabel(
QObject::tr("%1 entities, %2 links").arg(entities).arg(graph.edges.size()), widget));
// Draw the graph yourself: e.g. a QGraphicsView with a node at each positions[node.id].
return widget;
}
You render the graph with whatever Qt widgets you like (a QGraphicsView reading positions is the usual choice) — the data, layout, and queries all come from project() and the ew::app engines.
Edit the project, undoably
Push a Command onto commands() and it joins the app's Ctrl+Z history like any edit made in the UI:
#include <ew/core/AddObjectCommand.h>
#include <ew/core/Entity.h>
app.commands().push(std::make_unique<ew::core::AddObjectCommand>(
app.project(),
std::make_unique<ew::core::Entity>(ew::core::ContentId::generate(), ew::core::CategoryId(),
QObject::tr("New entity"))));
There is a command for every kind of edit (see the ~100 *Command types under ew::core); reach for SetFieldValueCommand, SetEntityAliasesCommand, AddObjectCommand, and so on rather than mutating the model directly, so every change stays undoable.
Menu commands
For an action that does not need a panel, addMenuCommand contributes a PluginMenuCommand whose invoke(PluginAppContext&) runs on the GUI thread with the same live access:
context.addMenuCommand(
{.id = QStringLiteral("games.acme.graph.count"),
.title = QStringLiteral("Graph Explorer: Count Entities"),
.invoke = [](ew::app::PluginAppContext& app) {
QMessageBox::information(app.mainWindow(), QObject::tr("Graph Explorer"),
QObject::tr("The project has %1 objects.")
.arg(app.project().objectCount()));
}});
Try it
A working reference plugin ships in the repository at plugins/example/ — it contributes a theme, an AI command, a panel that reads and edits the codex, and a menu command. Build it (next: Building & installing), drop the resulting library in the app's plugins/ folder, and restart: the panel appears as a dock, and Preferences ▸ Plugins lists the plugin and any that failed to load (with the reason), never crashing the app.
From here, the rest of the C++ developer API is every public type and function in ew::core and ew::app — all of it a documented, stable contract you can build on.