nandi/cosmicnimpublic Fork 0
v0.2.0
Commits
Clone
git clone https://git.rickub.com/nandi/cosmicnim.git
git clone ssh://git@rickub.com/nandi/cosmicnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Fetch releases anonymously from the right host 8dafda6 · on v0.2.0 · nandithebull · 8h ago
README.md · 78 lines · 2.5 KBmarkdown
Blame HistoryOpen raw

cosmicnim

libcosmic behind a small C ABI, driven from Nim.

libcosmic's API is generics, traits and closures, so it has no C ABI of its own —
building libcosmic itself as a cdylib exports nothing. The cosmic_ffi crate
therefore defines the C surface: it depends on libcosmic as a normal Rust
crate and exports a handful of functions.

cosmic_ffi/   Rust cdylib -> libcosmic_ffi.so + cosmic_ffi.h
nim/          Nim bindings (cosmic.nim) and the demo app (app.nim)
justfile      build / run / fetch tasks

The contract

The host owns the application state and the shape of the window. Once per
frame libcosmic calls back through on_view with an opaque builder, and the
host describes its widget tree by calling the builder functions. Widget memory
never crosses the boundary — the host only ever issues calls, and the builder
handle is dead the moment on_view returns.

Interaction runs the other way. A button carries an id the host chose; pressing
it calls on_press with that id, the host mutates its own state, and the next
on_view reflects it.

typedef void (*cosmic_on_view)(void *ctx, CosmicBuilder *b);
typedef void (*cosmic_on_press)(void *ctx, int32_t id);
int32_t cosmic_run(const CosmicConfig *config);   /* blocks until closed */

Because the tree is rebuilt every frame, it can depend on state: a button
disappears, or goes inert, simply by not being described that way this time
round. See cosmic_ffi.h for the full list of containers, leaves and
attributes.

cosmic_run must be called from the main thread, and ctx is only ever
touched from that thread.

From Nim

Containers are block templates, so the source has the same shape as the window:

proc onView(ctx: pointer; b: Builder) {.cdecl.} =
  let c = cast[ptr Counter](ctx)
  b.container:
    b.fill(); b.alignCenter(); b.spacing(space(SpaceM))
    b.text($c.value, TextTitle1)
    b.row:
      b.spacing(space(SpaceS))
      b.button("−", IdDec)
      b.button("Reset", IdReset, ButtonDestructive, enabled = c.value != 0)
      b.button("+", IdInc, ButtonSuggested)

Running it

The demo needs nim/libcosmic_ffi.so. Take it from a release:

just fetch          # newest v* tag on the remote
just fetch v0.1.2   # or a specific one
just run

or build it yourself — the cold build is long, which is what the release CI is
for:

just build
just run

just check type-checks the Nim without needing the library at all, since the
binding loads it lazily.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# cosmicnim

[libcosmic](https://github.com/pop-os/libcosmic) behind a small C ABI, driven from Nim.

libcosmic's API is generics, traits and closures, so it has no C ABI of its own —
building libcosmic itself as a `cdylib` exports nothing. The `cosmic_ffi` crate
therefore *defines* the C surface: it depends on libcosmic as a normal Rust
crate and exports a handful of functions.

```
cosmic_ffi/   Rust cdylib -> libcosmic_ffi.so + cosmic_ffi.h
nim/          Nim bindings (cosmic.nim) and the demo app (app.nim)
justfile      build / run / fetch tasks
```

## The contract

The host owns the application state *and* the shape of the window. Once per
frame libcosmic calls back through `on_view` with an opaque builder, and the
host describes its widget tree by calling the builder functions. Widget memory
never crosses the boundary — the host only ever issues calls, and the builder
handle is dead the moment `on_view` returns.

Interaction runs the other way. A button carries an id the host chose; pressing
it calls `on_press` with that id, the host mutates its own state, and the next
`on_view` reflects it.

```c
typedef void (*cosmic_on_view)(void *ctx, CosmicBuilder *b);
typedef void (*cosmic_on_press)(void *ctx, int32_t id);
int32_t cosmic_run(const CosmicConfig *config);   /* blocks until closed */
```

Because the tree is rebuilt every frame, it can depend on state: a button
disappears, or goes inert, simply by not being described that way this time
round. See `cosmic_ffi.h` for the full list of containers, leaves and
attributes.

`cosmic_run` must be called from the main thread, and `ctx` is only ever
touched from that thread.

## From Nim

Containers are block templates, so the source has the same shape as the window:

```nim
proc onView(ctx: pointer; b: Builder) {.cdecl.} =
  let c = cast[ptr Counter](ctx)
  b.container:
    b.fill(); b.alignCenter(); b.spacing(space(SpaceM))
    b.text($c.value, TextTitle1)
    b.row:
      b.spacing(space(SpaceS))
      b.button("", IdDec)
      b.button("Reset", IdReset, ButtonDestructive, enabled = c.value != 0)
      b.button("+", IdInc, ButtonSuggested)
```

## Running it

The demo needs `nim/libcosmic_ffi.so`. Take it from a release:

```bash
just fetch          # newest v* tag on the remote
just fetch v0.1.2   # or a specific one
just run
```

or build it yourself — the cold build is long, which is what the release CI is
for:

```bash
just build
just run
```

`just check` type-checks the Nim without needing the library at all, since the
binding loads it lazily.