Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
9eecab1
пусто
kornerr Jun 20, 2026
2d6726d
add hello list stub
kornerr Jun 21, 2026
42f5bd7
create stub hello list
kornerr Jun 21, 2026
798b48e
append
kornerr Jun 22, 2026
483b999
kd 3.2
kornerr Jun 22, 2026
7ea99dd
display items
kornerr Jun 22, 2026
a8012c8
stub mac
kornerr Jun 23, 2026
3a62c8d
rootE
kornerr Jun 23, 2026
1208e51
Item cpp stub
kornerr Jun 23, 2026
1a20387
make stub arr
kornerr Jun 24, 2026
3d9cd61
conv
kornerr Jun 24, 2026
4d51ea8
get sizw
kornerr Jun 24, 2026
c383dab
print items
kornerr Jun 24, 2026
65d13d2
displauy items
kornerr Jun 24, 2026
1432dac
populate ui with item titles
kornerr Jun 24, 2026
0f5b87d
somewhere
kornerr Jun 26, 2026
8bfe0e7
closer
kornerr Jun 26, 2026
cb25c49
switch to kd cpparr branch
kornerr Jun 27, 2026
521a48f
rm dup
kornerr Jun 27, 2026
7c733d8
correct up
kornerr Jun 27, 2026
e2ece54
gen items field
kornerr Jun 27, 2026
5e3efb9
move Items ctor to h
kornerr Jun 28, 2026
dd9c942
gen Items
kornerr Jun 29, 2026
425dae6
clarify
kornerr Jun 29, 2026
d2f66c1
gen Item hdr
kornerr Jul 4, 2026
b405e33
clarfiy
kornerr Jul 5, 2026
ab3dbe7
gen elem src
kornerr Jul 5, 2026
b161a83
clarify
kornerr Jul 6, 2026
a4dfc64
better list ui
kornerr Jul 6, 2026
54ef5e7
root view item
kornerr Jul 6, 2026
71a57f7
some
kornerr Jul 10, 2026
9a6f987
gemini helped
kornerr Jul 10, 2026
ef6028d
no crash
kornerr Jul 10, 2026
08e7f0f
fix any
kornerr Jul 10, 2026
827574e
more space
kornerr Jul 10, 2026
5b67a22
better bottom spacing
kornerr Jul 10, 2026
d646623
add test tags to helloworld
kornerr Jul 13, 2026
95665d7
Qt ids
kornerr Jul 15, 2026
83a39b2
create ios
kornerr Jul 15, 2026
747cf05
ios stub more
kornerr Jul 15, 2026
7989351
symlink xcf
kornerr Jul 15, 2026
5b220fa
build
kornerr Jul 15, 2026
bf9a7a7
add ids to ios
kornerr Jul 15, 2026
3a1a766
some
kornerr Jul 25, 2026
2f1dc9f
agents
kornerr Jul 25, 2026
d4bbdf5
some
kornerr Jul 26, 2026
8fafeb8
rm
kornerr Jul 26, 2026
89ad144
add ios stub for list
kornerr Jul 26, 2026
ffe6b60
some
kornerr Jul 26, 2026
00a5df6
up scripts
kornerr Jul 26, 2026
06cfdb2
indent
kornerr Jul 26, 2026
0febb64
rm
kornerr Jul 26, 2026
8a5c637
fix cap
kornerr Jul 26, 2026
d059aef
sample
kornerr Jul 26, 2026
764ebdf
not yet list
kornerr Jul 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ do-test*
.DS_Store
emb64.kt
.gradle
*.hprof
.idea
ignore.*
kd.kt
Expand All @@ -14,3 +15,5 @@ local.properties
ver-windows-x64/src/*.h
ver-windows-x64/src/*.cpp
ver-windows-x64/src/*.qml
*.xcodeproj
*.zip
118 changes: 118 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Kotlin Dialect Example Repository

Multiplatform example projects built with [Kotlin Dialect](https://github.com/OGStudio/kotlin-dialect) (KD) — a custom state-machine/reducer framework.

## Projects

| Project | Description | Targets |
|---------|-------------|---------|
| `helloworld/` | Minimal KD example (single greeting text) | Android, iOS, macOS, Linux, Windows |
| `hellolist/` | TODO list example with Item struct | Android, macOS |
| `sample_gb/` | Git Budget reference app (symlink) | Android, iOS, macOS |

## Architecture: Kotlin Dialect

- **Contexts** (`*Context` data classes in `ignore.kd.kt`) hold all app state
- **should-functions** (reducers in `*Should.kt`) check `c.recentField` against `F.*` constants, return mutated context
- **`F` object** (in `ignore.kd.kt`) contains string constants for all field names
- `KDController` manages the event queue: `set(fieldName, value)` → processes all registered functions → fires callbacks

## Component approach to file hierarchy

Source of truth lives in `components/` directory. Platform-specific directories symlink back to it.

### Component structure

Each component has platform subdirectories:

```
components/<name>/
├── sdk/ # Cross-platform Kotlin (reducers, proto, constants)
├── android/ # Android-specific (Compose UI, VM bindings, effects)
├── ios/ # iOS-specific (SwiftUI views, VM bindings, effects)
├── desktop/ # Desktop-specific (C++/Qt, QML views)
│ └── view/ # QML views
└── <platform>/view/ # Platform views go inside view/ subdir
```

### Symlink convention

All `ver-*` and `sdk-*` directories contain symlinks pointing back to `components/`:

- `ver-android/.../<component>/*.kt` → `components/<component>/{sdk,android}/*`
- `ver-ios/src/<component>/*.swift` → `components/<component>/ios/*`
- `ver-ios/src/<component>/view/*.swift` → `components/<component>/ios/view/*`
- `ver-mac-x64/src/<component>/*` → `components/<component>/desktop/*`
- `sdk-ios/hw/src/commonMain/.../*.kt` → `components/<component>/sdk/*`
- `sdk-ios/gradle*` → `../ver-android/gradle*` (shared Gradle wrapper)
- `ver-ios/app/local/<module>/xcframework.zip` → `sdk-ios/.../xcframework.zip` (built artifact)

**Edit files in `components/`** — editing symlink targets will appear to work but won't show in `git status` for the canonical path.

Files starting with `ignore` are not tracked in git. `ignore` files should not be edited because they come from `kd.yml` after generation.

## Conventions

- Package: `org.opengamestudio`
- Module names: `hw` (helloworld), `hl` (hellolist)
- KD branch: `cpparr`
- Kotlin: 2.2.0

## File naming

| Pattern | Location | Purpose |
|---------|----------|---------|
| `*Should.kt` | `components/*/sdk/` | Reducers |
| `*Proto.kt` | `components/*/sdk/` | Controller setup, exposes `ctrl()` and `set()` |
| `*Const.kt` | `components/*/sdk/` | Constants |
| `*Fun.kt` | `components/*/sdk/` | Utility functions |
| `other.kt` | `components/other/sdk/` | Shared utilities (`setupComponentDebugging`) |
| `*Effect.kt` | `components/*/android/` or `ios/` | Platform-specific effects |
| `*.swift` | `components/*/ios/` | iOS effects, component wiring |
| `view/*.swift` | `components/*/ios/view/` | SwiftUI views |
| `*.kt` | `components/*/android/` | Android effects, component wiring |
| `view/*.kt` | `components/*/android/view/` | Compose views |
| `ignore.kd.*` | Generated everywhere | **Do not edit** — generated from `kd.yml` |

## Code generation

`kd.yml` defines data structs and contexts. Regenerate after changes:

```
./util/build-ios # Full iOS build (cloneKD + genKD + XCFramework + xcodegen)
./util/build-android # Android (cloneKD + genKD only)
./util/build-mac-x64 # macOS desktop (cloneKD + genKD + SDK + CMake)
```

Or run KD generation standalone:
```
node $KD_DIR/dist/app.js --file=kd.yml
```

## Build pipelines

### iOS
1. `cloneKD` — clones kotlin-dialect repo (branch: `cpparr`)
2. `genKD` — generates all `ignore.kd.*` files from `kd.yml`
3. `buildIOSSDK` — builds XCFramework via Gradle, zips it, symlinks to `ver-ios/app/local/<module>/`
4. `genIOSProject` — runs `xcodegen` in `ver-ios/app/` to generate `.xcodeproj`

### Desktop
1. `cloneKD` → `genKD`
2. `buildDesktopSDK` — `./gradlew linkReleaseSharedNative` (produces lib<module>)
3. `buildDesktop` — CMake build (links against lib, produces Qt app)

### Android
1. `cloneKD` → `genKD`
2. Build via Android Studio or `./gradlew assembleDebug` in `ver-android/`

## iOS project structure

- `sdk-ios/` — KMP project producing XCFramework (iosArm64, iosSimulatorArm64, iosX64)
- `ver-ios/` — SwiftUI app
- `app/project.yml` — XcodeGen spec
- `app/local/<module>/Package.swift` — Swift Package wrapping xcframework
- `src/ignore.kd.swift` — Generated Swift (F struct, KDController extensions)
- `src/VM.swift` — ObservableObject for SwiftUI state
- `src/AppDelegate.swift` — UIApplicationDelegate, creates components
- `src/AppView.swift` — @main SwiftUI App
44 changes: 42 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
# Kotlin Dialect examples

This repository contains example project to showcase how Kotlin Dialect
application looks like
This repository contains example projects to showcase how applications
with Kotlin Dialect operate

# Table of contents

* [Hello world](#hw)
* [Android](#hw_android)
* [iOS](#hw_ios)
* [Linux](#hw_linux)
* [macOS](#hw_macos)
* [Windows](#hw_windows)
* [Hello list](#hl)
* [Android](#hl_android)
* [iOS](#hl_ios)
* [Linux](#hl_linux)
* [macOS](#hl_macos)
* [Windows](#hl_windows)

<a name="hw"/>

Expand Down Expand Up @@ -72,6 +79,15 @@ Instructions:
| 5 | Open `helloworld/ver-android` project | |
| 6 | Press Run | |

<a name="hw_ios"/>

### iOS

Fill local.properties with sdk path (it's gitignored)

TODO


<a name="hw_linux"/>

### Linux
Expand Down Expand Up @@ -136,3 +152,27 @@ Instructions:
| 2 | Go to `helloworld` directory | `cd helloworld` |
| 3 | Build the application | `./util/build-windows-x64` |
| 4 | Launch the application | `./util/launch-windows-x64`|

<a name="hl"/>

## Hello list

`hellolist` example depicts how to add items to a list

TODO Structure

<a name="hl_android"/>

### Android

TODO Video

TODO Instructions

<a name="hl_macos"/>

### macOS

TODO Video

TODO Instructions
32 changes: 32 additions & 0 deletions hellolist/components/other/android/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.opengamestudio

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.ui.*
import org.opengamestudio.ui.theme.MyApplicationTheme

class MainActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()

VM.androidContext = this
// Launch components once VM has Android Context
RootComponent.setup()

setContent {
MyApplicationTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
RootView(
modifier = Modifier.padding(innerPadding),
VM
)
}
}
}
}
}
11 changes: 11 additions & 0 deletions hellolist/components/other/android/VM.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.opengamestudio

import android.content.Context
import androidx.compose.runtime.*

object VM {
var androidContext: Context? = null

var rootIsVisible = mutableStateOf(false)
var rootItems = mutableStateListOf<Item>()
}
36 changes: 36 additions & 0 deletions hellolist/components/other/desktop/App.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickStyle>

#include "ignore.kd.h"
#include "root.h"
#include "VM.h"

int main(int argc, char *argv[]) {
// Create Qt application
QQuickStyle::setStyle("Fusion");
QApplication app(argc, argv);
QQmlApplicationEngine engine;
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection
);

API api;
FObj fobj;
// Create and launch components
RootComponent r;
r.setup();

// Configure and load QML
engine.rootContext()->setContextProperty("api", &api);
engine.rootContext()->setContextProperty("F", &fobj);
engine.rootContext()->setContextProperty("vm", &VM::singleton());
engine.loadFromModule("hellolist", "AppView");

return app.exec();
}
10 changes: 10 additions & 0 deletions hellolist/components/other/desktop/KT.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef HL_KT_H
#define HL_KT_H

#include "libhl_api.h"
#define KT libhl_symbols()->kotlin.root.org.opengamestudio
#define KTLibRef(NAME) libhl_kref_kotlin_##NAME
#define KTRef(NAME) libhl_kref_org_opengamestudio_##NAME
#define KTSym libhl_symbols()

#endif // HL_KT_H
49 changes: 49 additions & 0 deletions hellolist/components/other/desktop/VM.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include "KT.h"
#include "VM.h"

VM::VM() : QObject() {
_rootIsVisible = false;
}

VM::~VM() {
qDeleteAll(_rootItems);
}

bool VM::rootIsVisible() const {
return _rootIsVisible;
}

QQmlListProperty<Item> VM::rootItems() const {
return QQmlListProperty<Item>(
const_cast<VM*>(this),
nullptr,
&VM::rootItemsCount,
&VM::rootItemsAt
);
}

Item *VM::rootItemsAt(
QQmlListProperty<Item> *list,
qsizetype index
) {
auto *vm = qobject_cast<VM*>(list->object);
return vm ? vm->_rootItems.at(index) : nullptr;
}

qsizetype VM::rootItemsCount(QQmlListProperty<Item> *list) {
auto *vm = qobject_cast<VM*>(list->object);
return vm ? vm->_rootItems.size() : 0;
}

void VM::rootSetIsVisible(bool value) {
_rootIsVisible = value;
emit rootDidChangeIsVisible(value);
}

void VM::rootSetItems(QList<Item*> items) {
for (auto *item : _rootItems) {
item->deleteLater();
}
_rootItems = items;
emit rootDidChangeItems();
}
Loading