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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
## Nim bindings for the cosmic_ffi cdylib.
##
## The window's shape is described from Nim, once per frame, inside an
## `onView` callback. Containers are block templates, so the widget tree in
## the source has the same shape as the widget tree on screen:
##
## ```nim
## b.container:
## b.fill(); b.alignCenter(); b.spacing(space(SpaceM))
## b.text("Count: 3", TextTitle1)
## b.row:
## b.spacing(space(SpaceS))
## b.button("−", id = 0)
## b.button("+", id = 1, style = ButtonSuggested)
## ```
import std/os
when not defined(noSignalHandler):
{.warning: "compile with -d:noSignalHandler: libcosmic runs threads Nim " &
"did not create, and Nim's signal handler allocates, so a signal " &
"delivered to one of them crashes inside the handler".}
const libCosmicFfi* {.strdefine.} = currentSourcePath().parentDir /
"libcosmic_ffi.so"
## Absolute at compile time, so the library is found wherever nimble put
## the package rather than only next to the binary. Override with
## `-d:libCosmicFfi=/some/other/libcosmic_ffi.so`.
type
Builder* = distinct pointer
## Opaque. Only valid for the duration of the `onView` call it arrived in.
OnView* = proc (ctx: pointer; b: Builder; width, height: cfloat) {.cdecl.}
## `width`/`height` are the pixels actually available — not what
## CosmicConfig asked for, which a tiling compositor will override.
OnPress* = proc (ctx: pointer; id: int32) {.cdecl.}
OnKey* = proc (ctx: pointer; key: cstring) {.cdecl.}
## Each key press: the typed character for ordinary keys ("7", "+"),
## otherwise the key's name ("Enter", "Backspace", "Escape",
## "ArrowLeft"). The string is only valid during the call.
CosmicConfig* = object
title*: cstring
onView*: OnView
onPress*: OnPress
onKey*: OnKey ## may be nil
ctx*: pointer
width*: uint32 ## 0 for a default
height*: uint32 ## 0 for a default
TextStyle* = enum
TextBody, TextTitle1, TextTitle2, TextTitle3, TextTitle4,
TextHeading, TextCaption, TextMonotext
ButtonStyle* = enum
ButtonStandard, ButtonSuggested, ButtonDestructive, ButtonText, ButtonLink
SpaceStep* = enum
SpaceNone, SpaceXxxs, SpaceXxs, SpaceXs, SpaceS, SpaceM, SpaceL, SpaceXl
const AbiVersion* = 6'u32
## The ABI these bindings are written against. Bumped whenever a signature
## changes, which is invisible to the dynamic linker: every symbol still
## resolves, and the arguments are simply read from the wrong registers.
proc cosmicAbiVersion*(): uint32
{.cdecl, importc: "cosmic_abi_version", dynlib: libCosmicFfi.}
proc rawCosmicRun(config: ptr CosmicConfig): int32
{.cdecl, importc: "cosmic_run", dynlib: libCosmicFfi.}
proc cosmicRun*(config: ptr CosmicConfig): int32 =
## Refuses to run against a library that speaks a different ABI, rather
## than letting the callbacks read whatever is in the registers.
let found = cosmicAbiVersion()
if found != AbiVersion:
raise newException(LibraryError,
"libcosmic_ffi speaks ABI " & $found & ", these bindings expect " &
$AbiVersion & " -- run `just fetch` to get the matching release")
rawCosmicRun(config)
# --- raw builder calls; prefer the wrappers below ---
proc beginColumn(b: Builder) {.cdecl, importc: "cosmic_column", dynlib: libCosmicFfi.}
proc beginRow(b: Builder) {.cdecl, importc: "cosmic_row", dynlib: libCosmicFfi.}
proc beginContainer(b: Builder) {.cdecl, importc: "cosmic_container", dynlib: libCosmicFfi.}
proc endNode(b: Builder) {.cdecl, importc: "cosmic_end", dynlib: libCosmicFfi.}
proc rawSpacing(b: Builder; px: cfloat) {.cdecl, importc: "cosmic_spacing", dynlib: libCosmicFfi.}
proc rawPadding(b: Builder; px: cfloat) {.cdecl, importc: "cosmic_padding", dynlib: libCosmicFfi.}
proc rawAlignCenter(b: Builder) {.cdecl, importc: "cosmic_align_center", dynlib: libCosmicFfi.}
proc rawFill(b: Builder) {.cdecl, importc: "cosmic_fill", dynlib: libCosmicFfi.}
proc rawText(b: Builder; style: int32; text: cstring)
{.cdecl, importc: "cosmic_text", dynlib: libCosmicFfi.}
proc rawButton(b: Builder; style: int32; label: cstring; id: int32)
{.cdecl, importc: "cosmic_button", dynlib: libCosmicFfi.}
proc rawSpace(b: Builder; w, h: cfloat)
{.cdecl, importc: "cosmic_space", dynlib: libCosmicFfi.}
proc rawSize(b: Builder; w, h: cfloat)
{.cdecl, importc: "cosmic_size", dynlib: libCosmicFfi.}
proc rawSpaceUnit(step: int32): cfloat
{.cdecl, importc: "cosmic_space_unit", dynlib: libCosmicFfi.}
# --- containers ---
template column*(b: Builder; body: untyped) =
## Stack the widgets built in `body` vertically.
beginColumn(b)
body
endNode(b)
template row*(b: Builder; body: untyped) =
## Lay the widgets built in `body` out horizontally.
beginRow(b)
body
endNode(b)
template container*(b: Builder; body: untyped) =
## Wrap the widgets built in `body`, typically to pad or centre them.
beginContainer(b)
body
endNode(b)
# --- attributes of the innermost open container ---
proc spacing*(b: Builder; px: float) = rawSpacing(b, cfloat(px))
proc padding*(b: Builder; px: float) = rawPadding(b, cfloat(px))
proc alignCenter*(b: Builder) = rawAlignCenter(b)
proc fill*(b: Builder) = rawFill(b)
# --- leaves ---
proc text*(b: Builder; s: string; style = TextBody) =
rawText(b, int32(ord(style)), s.cstring)
proc button*(b: Builder; label: string; id: int32; style = ButtonStandard;
enabled = true) =
## A button that reports `id` back through `onPress`. `enabled = false`
## draws it inert; `id` is then never delivered.
rawButton(b, int32(ord(style)), label.cstring, if enabled: id else: -1)
proc space*(b: Builder; w, h: float) = rawSpace(b, cfloat(w), cfloat(h))
const Fill* = Inf
## As a dimension, take the space on offer. Siblings that all ask for it
## share the axis equally, which is how you get an even grid.
proc size*(b: Builder; w = -1.0; h = -1.0) =
## Size the next leaf, in pixels; `Fill` expands, a negative dimension
## stays natural. Applies to one leaf only, so set it before each widget
## you want sized.
rawSize(b, cfloat(w), cfloat(h))
proc space*(step: SpaceStep): float =
## The active COSMIC theme's spacing for `step`, in pixels.
float(rawSpaceUnit(int32(ord(step))))
|