Make this a nimble package, and grow the example into a gallery
The example now switches between four pages, which is the thing the old fixed contract could not express at all: each page is a different tree built from Nim state, not a widget shown or hidden. It exercises every text and button style, disabled buttons, nesting, padding, and the theme spacing scale. On packaging: cosmicnim.nimble owns fetching now, and `just fetch` is a wrapper over `nimble fetchLib`, so the fetch works for someone who installed the package without just. The tag comes from the package version rather than the newest tag on the remote — cosmicnim 0.2.0 speaks to the v0.2.0 ABI and nothing else, which is what stops a stale library loading against new bindings. The binding resolves the .so by absolute path at compile time, so an installed package finds it in the nimble store; the app.nims rpath hack is gone. cosmic.nim is renamed cosmicnim.nim to match the package, and the example moved out of srcDir so it is not installed alongside it. Verified: a program compiled outside the repo against the installed package loads the cdylib from the nimble store. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0b02cea parent: 2bd2bc6 modified
.gitignore +1 -1 | @@ -1,5 +1,5 @@ | ||
| 1 | 1 | target/ |
| 2 | -nim/app | |
| 2 | +examples/gallery | |
| 3 | 3 | nimcache/ |
| 4 | 4 | *.so |
| 5 | 5 | run.log |
| @@ -1,5 +1,5 @@ | |||
| 1 | target/ | 1 | target/ |
| 2 | -nim/app | 2 | +examples/gallery |
| 3 | nimcache/ | 3 | nimcache/ |
| 4 | *.so | 4 | *.so |
| 5 | run.log | 5 | run.log |
modified
README.md +21 -5 | @@ -8,9 +8,11 @@ therefore *defines* the C surface: it depends on libcosmic as a normal Rust | ||
| 8 | 8 | crate and exports a handful of functions. |
| 9 | 9 | |
| 10 | 10 | ``` |
| 11 | -cosmic_ffi/ Rust cdylib -> libcosmic_ffi.so + cosmic_ffi.h | |
| 12 | -nim/ Nim bindings (cosmic.nim) and the demo app (app.nim) | |
| 13 | -justfile build / run / fetch tasks | |
| 11 | +cosmic_ffi/ Rust cdylib -> libcosmic_ffi.so + cosmic_ffi.h | |
| 12 | +nim/ the Nim binding (cosmicnim.nim), and where the .so lands | |
| 13 | +examples/ gallery.nim, a demo of what the builder can express | |
| 14 | +cosmicnim.nimble package metadata and the fetchLib task | |
| 15 | +justfile build / run / fetch tasks | |
| 14 | 16 | ``` |
| 15 | 17 | |
| 16 | 18 | ## The contract |
| @@ -61,11 +63,25 @@ proc onView(ctx: pointer; b: Builder) {.cdecl.} = | ||
| 61 | 63 | The demo needs `nim/libcosmic_ffi.so`. Take it from a release: |
| 62 | 64 | |
| 63 | 65 | ```bash |
| 64 | -just fetch # newest v* tag on the remote | |
| 65 | -just fetch v0.1.2 # or a specific one | |
| 66 | +just fetch # or: nimble fetchLib | |
| 66 | 67 | just run |
| 67 | 68 | ``` |
| 68 | 69 | |
| 70 | +The package version *is* the ABI version: cosmicnim 0.2.0 fetches the | |
| 71 | +v0.2.0 release and no other, so a stale library cannot load against newer | |
| 72 | +bindings. `just fetch v0.1.2` (or `COSMICNIM_TAG=v0.1.2 nimble fetchLib`) | |
| 73 | +overrides that when you want it. | |
| 74 | + | |
| 75 | +## As a dependency | |
| 76 | + | |
| 77 | +```bash | |
| 78 | +nimble install # fetches the cdylib too, and ships it with the package | |
| 79 | +``` | |
| 80 | + | |
| 81 | +The binding resolves the library by absolute path at compile time, so an | |
| 82 | +installed package finds its `.so` in the nimble store without any rpath or | |
| 83 | +`LD_LIBRARY_PATH` fiddling. Override with `-d:libCosmicFfi=/some/path.so`. | |
| 84 | + | |
| 69 | 85 | or build it yourself — the cold build is long, which is what the release CI is |
| 70 | 86 | for: |
| 71 | 87 | |
| @@ -8,9 +8,11 @@ therefore *defines* the C surface: it depends on libcosmic as a normal Rust | |||
| 8 | crate and exports a handful of functions. | 8 | crate and exports a handful of functions. |
| 9 | 9 | ||
| 10 | ``` | 10 | ``` |
| 11 | -cosmic_ffi/ Rust cdylib -> libcosmic_ffi.so + cosmic_ffi.h | 11 | +cosmic_ffi/ Rust cdylib -> libcosmic_ffi.so + cosmic_ffi.h |
| 12 | -nim/ Nim bindings (cosmic.nim) and the demo app (app.nim) | 12 | +nim/ the Nim binding (cosmicnim.nim), and where the .so lands |
| 13 | -justfile build / run / fetch tasks | 13 | +examples/ gallery.nim, a demo of what the builder can express |
| 14 | +cosmicnim.nimble package metadata and the fetchLib task | ||
| 15 | +justfile build / run / fetch tasks | ||
| 14 | ``` | 16 | ``` |
| 15 | 17 | ||
| 16 | ## The contract | 18 | ## The contract |
| @@ -61,11 +63,25 @@ proc onView(ctx: pointer; b: Builder) {.cdecl.} = | |||
| 61 | The demo needs `nim/libcosmic_ffi.so`. Take it from a release: | 63 | The demo needs `nim/libcosmic_ffi.so`. Take it from a release: |
| 62 | 64 | ||
| 63 | ```bash | 65 | ```bash |
| 64 | -just fetch # newest v* tag on the remote | 66 | +just fetch # or: nimble fetchLib |
| 65 | -just fetch v0.1.2 # or a specific one | ||
| 66 | just run | 67 | just run |
| 67 | ``` | 68 | ``` |
| 68 | 69 | ||
| 70 | +The package version *is* the ABI version: cosmicnim 0.2.0 fetches the | ||
| 71 | +v0.2.0 release and no other, so a stale library cannot load against newer | ||
| 72 | +bindings. `just fetch v0.1.2` (or `COSMICNIM_TAG=v0.1.2 nimble fetchLib`) | ||
| 73 | +overrides that when you want it. | ||
| 74 | + | ||
| 75 | +## As a dependency | ||
| 76 | + | ||
| 77 | +```bash | ||
| 78 | +nimble install # fetches the cdylib too, and ships it with the package | ||
| 79 | +``` | ||
| 80 | + | ||
| 81 | +The binding resolves the library by absolute path at compile time, so an | ||
| 82 | +installed package finds its `.so` in the nimble store without any rpath or | ||
| 83 | +`LD_LIBRARY_PATH` fiddling. Override with `-d:libCosmicFfi=/some/path.so`. | ||
| 84 | + | ||
| 69 | or build it yourself — the cold build is long, which is what the release CI is | 85 | or build it yourself — the cold build is long, which is what the release CI is |
| 70 | for: | 86 | for: |
| 71 | 87 | ||
added
cosmicnim.nimble +47 -0 | new file mode 100644 | ||
| @@ -0,0 +1,47 @@ | ||
| 1 | +version = "0.2.0" | |
| 2 | +author = "nandi" | |
| 3 | +description = "libcosmic behind a C ABI, driven from Nim" | |
| 4 | +license = "MIT" | |
| 5 | +srcDir = "nim" | |
| 6 | +installExt = @["nim", "so"] # the module, plus the cdylib beside it | |
| 7 | + | |
| 8 | +requires "nim >= 2.0.0" | |
| 9 | + | |
| 10 | +# The package version and the cdylib's ABI are the same thing: cosmicnim | |
| 11 | +# 0.2.0 speaks to the v0.2.0 release and no other. Resolving the tag from | |
| 12 | +# `version` is what keeps a stale library from loading against new bindings. | |
| 13 | +const | |
| 14 | + BaseUrl = "https://rickub.com/nandi/cosmicnim" | |
| 15 | + Target = "x86_64-unknown-linux-gnu" | |
| 16 | + LibName = "libcosmic_ffi.so" | |
| 17 | + | |
| 18 | +proc fetchLibInto(dir: string) = | |
| 19 | + # NimScript has no `/` path operator, hence the plain concatenation. | |
| 20 | + let | |
| 21 | + tag = getEnv("COSMICNIM_TAG", "v" & version) | |
| 22 | + url = BaseUrl & "/releases/download/" & tag & | |
| 23 | + "/cosmic_ffi-" & tag & "-" & Target & ".tar.gz" | |
| 24 | + tmp = dir & "/.cosmicnim-fetch" | |
| 25 | + | |
| 26 | + echo "fetching ", tag, " from ", BaseUrl | |
| 27 | + rmDir tmp | |
| 28 | + mkDir tmp | |
| 29 | + exec "curl -fsSL -o '" & tmp & "/asset.tar.gz' '" & url & "'" | |
| 30 | + # The workflow publishes a checksum beside every tarball. | |
| 31 | + if gorgeEx("curl -fsSL -o '" & tmp & "/asset.sha256' '" & | |
| 32 | + url & ".sha256'").exitCode == 0: | |
| 33 | + exec "cd '" & tmp & "' && sed 's| .*| asset.tar.gz|' asset.sha256 | sha256sum -c -" | |
| 34 | + else: | |
| 35 | + echo "no .sha256 published alongside the asset; skipping checksum" | |
| 36 | + exec "tar xzf '" & tmp & "/asset.tar.gz' -C '" & tmp & "'" | |
| 37 | + exec "cp '" & tmp & "'/cosmic_ffi-*/" & LibName & " '" & dir & "/" & LibName & "'" | |
| 38 | + rmDir tmp | |
| 39 | + echo "installed ", dir & "/" & LibName | |
| 40 | + | |
| 41 | +task fetchLib, "Download the prebuilt cdylib matching this package version": | |
| 42 | + fetchLibInto(srcDir) | |
| 43 | + | |
| 44 | +before install: | |
| 45 | + # Ship the cdylib with the package, fetching it if it is not here yet. | |
| 46 | + if not fileExists(srcDir & "/" & LibName): | |
| 47 | + fetchLibInto(srcDir) | |
| new file mode 100644 | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | +version = "0.2.0" | ||
| 2 | +author = "nandi" | ||
| 3 | +description = "libcosmic behind a C ABI, driven from Nim" | ||
| 4 | +license = "MIT" | ||
| 5 | +srcDir = "nim" | ||
| 6 | +installExt = @["nim", "so"] # the module, plus the cdylib beside it | ||
| 7 | + | ||
| 8 | +requires "nim >= 2.0.0" | ||
| 9 | + | ||
| 10 | +# The package version and the cdylib's ABI are the same thing: cosmicnim | ||
| 11 | +# 0.2.0 speaks to the v0.2.0 release and no other. Resolving the tag from | ||
| 12 | +# `version` is what keeps a stale library from loading against new bindings. | ||
| 13 | +const | ||
| 14 | + BaseUrl = "https://rickub.com/nandi/cosmicnim" | ||
| 15 | + Target = "x86_64-unknown-linux-gnu" | ||
| 16 | + LibName = "libcosmic_ffi.so" | ||
| 17 | + | ||
| 18 | +proc fetchLibInto(dir: string) = | ||
| 19 | + # NimScript has no `/` path operator, hence the plain concatenation. | ||
| 20 | + let | ||
| 21 | + tag = getEnv("COSMICNIM_TAG", "v" & version) | ||
| 22 | + url = BaseUrl & "/releases/download/" & tag & | ||
| 23 | + "/cosmic_ffi-" & tag & "-" & Target & ".tar.gz" | ||
| 24 | + tmp = dir & "/.cosmicnim-fetch" | ||
| 25 | + | ||
| 26 | + echo "fetching ", tag, " from ", BaseUrl | ||
| 27 | + rmDir tmp | ||
| 28 | + mkDir tmp | ||
| 29 | + exec "curl -fsSL -o '" & tmp & "/asset.tar.gz' '" & url & "'" | ||
| 30 | + # The workflow publishes a checksum beside every tarball. | ||
| 31 | + if gorgeEx("curl -fsSL -o '" & tmp & "/asset.sha256' '" & | ||
| 32 | + url & ".sha256'").exitCode == 0: | ||
| 33 | + exec "cd '" & tmp & "' && sed 's| .*| asset.tar.gz|' asset.sha256 | sha256sum -c -" | ||
| 34 | + else: | ||
| 35 | + echo "no .sha256 published alongside the asset; skipping checksum" | ||
| 36 | + exec "tar xzf '" & tmp & "/asset.tar.gz' -C '" & tmp & "'" | ||
| 37 | + exec "cp '" & tmp & "'/cosmic_ffi-*/" & LibName & " '" & dir & "/" & LibName & "'" | ||
| 38 | + rmDir tmp | ||
| 39 | + echo "installed ", dir & "/" & LibName | ||
| 40 | + | ||
| 41 | +task fetchLib, "Download the prebuilt cdylib matching this package version": | ||
| 42 | + fetchLibInto(srcDir) | ||
| 43 | + | ||
| 44 | +before install: | ||
| 45 | + # Ship the cdylib with the package, fetching it if it is not here yet. | ||
| 46 | + if not fileExists(srcDir & "/" & LibName): | ||
| 47 | + fetchLibInto(srcDir) | ||
added
examples/gallery.nim +178 -0 | new file mode 100644 | ||
| @@ -0,0 +1,178 @@ | ||
| 1 | +## A small gallery of what the builder can express. | |
| 2 | +## | |
| 3 | +## Every frame the whole tree is rebuilt from Nim state, so switching pages | |
| 4 | +## is not a widget being shown or hidden — it is a different tree. The same | |
| 5 | +## goes for the disabled buttons and the history line: they are simply not | |
| 6 | +## described when the state does not call for them. | |
| 7 | + | |
| 8 | +import std/[strformat, strutils] | |
| 9 | +import cosmicnim | |
| 10 | + | |
| 11 | +type Page = enum | |
| 12 | + PageCounter = "Counter" | |
| 13 | + PageText = "Text" | |
| 14 | + PageButtons = "Buttons" | |
| 15 | + PageLayout = "Layout" | |
| 16 | + | |
| 17 | +const | |
| 18 | + # Nav takes one id per page; pages take ids from 10 up. | |
| 19 | + IdNavBase = 0'i32 | |
| 20 | + IdDec = 10'i32 | |
| 21 | + IdInc = 11'i32 | |
| 22 | + IdReset = 12'i32 | |
| 23 | + IdStepDown = 13'i32 | |
| 24 | + IdStepUp = 14'i32 | |
| 25 | + IdPokeStandard = 20'i32 | |
| 26 | + IdPokeSuggested = 21'i32 | |
| 27 | + IdPokeDestructive = 22'i32 | |
| 28 | + IdGapNarrower = 30'i32 | |
| 29 | + IdGapWider = 31'i32 | |
| 30 | + | |
| 31 | +type State = object | |
| 32 | + page: Page | |
| 33 | + value: int | |
| 34 | + step: int | |
| 35 | + history: seq[int] | |
| 36 | + poked: string | |
| 37 | + gap: int | |
| 38 | + | |
| 39 | +proc apply(s: var State; delta: int) = | |
| 40 | + s.history.add s.value | |
| 41 | + if s.history.len > 6: | |
| 42 | + s.history.delete(0) | |
| 43 | + s.value += delta | |
| 44 | + | |
| 45 | +proc onPress(ctx: pointer; id: int32) {.cdecl.} = | |
| 46 | + let s = cast[ptr State](ctx) | |
| 47 | + if id >= IdNavBase and id <= IdNavBase + int32(ord(high(Page))): | |
| 48 | + s.page = Page(id - IdNavBase) | |
| 49 | + return | |
| 50 | + case id | |
| 51 | + of IdDec: s[].apply(-s.step) | |
| 52 | + of IdInc: s[].apply(s.step) | |
| 53 | + of IdReset: s[].apply(-s.value) | |
| 54 | + of IdStepDown: s.step = max(1, s.step div 2) | |
| 55 | + of IdStepUp: s.step = min(100, s.step * 2) | |
| 56 | + of IdPokeStandard: s.poked = "standard" | |
| 57 | + of IdPokeSuggested: s.poked = "suggested" | |
| 58 | + of IdPokeDestructive: s.poked = "destructive" | |
| 59 | + of IdGapNarrower: s.gap = max(0, s.gap - 4) | |
| 60 | + of IdGapWider: s.gap = min(48, s.gap + 4) | |
| 61 | + else: discard | |
| 62 | + | |
| 63 | +proc navBar(b: Builder; s: ptr State) = | |
| 64 | + ## One button per page. The current page's button is inert, which is both | |
| 65 | + ## the highlight and the reason it cannot be pressed twice. | |
| 66 | + b.row: | |
| 67 | + b.spacing(space(SpaceXxs)) | |
| 68 | + b.alignCenter() | |
| 69 | + for p in Page: | |
| 70 | + b.button($p, IdNavBase + int32(ord(p)), | |
| 71 | + if p == s.page: ButtonSuggested else: ButtonText, | |
| 72 | + enabled = p != s.page) | |
| 73 | + | |
| 74 | +proc counterPage(b: Builder; s: ptr State) = | |
| 75 | + b.text(&"{s.value}", TextTitle1) | |
| 76 | + | |
| 77 | + b.row: | |
| 78 | + b.spacing(space(SpaceS)) | |
| 79 | + b.alignCenter() | |
| 80 | + b.button(&"−{s.step}", IdDec) | |
| 81 | + b.button("Reset", IdReset, ButtonDestructive, enabled = s.value != 0) | |
| 82 | + b.button(&"+{s.step}", IdInc, ButtonSuggested) | |
| 83 | + | |
| 84 | + b.row: | |
| 85 | + b.spacing(space(SpaceXs)) | |
| 86 | + b.alignCenter() | |
| 87 | + b.button("÷2", IdStepDown, ButtonText, enabled = s.step > 1) | |
| 88 | + b.text(&"step {s.step}", TextCaption) | |
| 89 | + b.button("×2", IdStepUp, ButtonText, enabled = s.step < 100) | |
| 90 | + | |
| 91 | + if s.history.len > 0: | |
| 92 | + b.text(s.history.join(" → ") & " → " & $s.value, TextCaption) | |
| 93 | + | |
| 94 | +proc textPage(b: Builder) = | |
| 95 | + ## Every style the binding exposes, labelled with itself. | |
| 96 | + b.column: | |
| 97 | + b.spacing(space(SpaceXxs)) | |
| 98 | + for style in TextStyle: | |
| 99 | + b.text($style, style) | |
| 100 | + | |
| 101 | +proc buttonsPage(b: Builder; s: ptr State) = | |
| 102 | + b.row: | |
| 103 | + b.spacing(space(SpaceS)) | |
| 104 | + b.alignCenter() | |
| 105 | + b.button("Standard", IdPokeStandard) | |
| 106 | + b.button("Suggested", IdPokeSuggested, ButtonSuggested) | |
| 107 | + b.button("Destructive", IdPokeDestructive, ButtonDestructive) | |
| 108 | + | |
| 109 | + b.row: | |
| 110 | + b.spacing(space(SpaceS)) | |
| 111 | + b.alignCenter() | |
| 112 | + b.button("Text", IdPokeStandard, ButtonText) | |
| 113 | + b.button("Link", IdPokeStandard, ButtonLink) | |
| 114 | + b.button("Disabled", IdPokeStandard, ButtonStandard, enabled = false) | |
| 115 | + | |
| 116 | + b.text( | |
| 117 | + if s.poked.len == 0: "none pressed yet" else: &"last pressed: {s.poked}", | |
| 118 | + TextCaption) | |
| 119 | + | |
| 120 | +proc layoutPage(b: Builder; s: ptr State) = | |
| 121 | + ## The gap between the boxes is host state, so the row is re-described at a | |
| 122 | + ## different spacing each time rather than being mutated in place. | |
| 123 | + b.text(&"row spacing: {s.gap}px", TextHeading) | |
| 124 | + | |
| 125 | + b.row: | |
| 126 | + b.spacing(float(s.gap)) | |
| 127 | + b.alignCenter() | |
| 128 | + for label in ["one", "two", "three"]: | |
| 129 | + b.container: | |
| 130 | + b.padding(space(SpaceXs)) | |
| 131 | + b.text(label, TextBody) | |
| 132 | + | |
| 133 | + b.row: | |
| 134 | + b.spacing(space(SpaceXs)) | |
| 135 | + b.alignCenter() | |
| 136 | + b.button("narrower", IdGapNarrower, ButtonText, enabled = s.gap > 0) | |
| 137 | + b.button("wider", IdGapWider, ButtonText, enabled = s.gap < 48) | |
| 138 | + | |
| 139 | + b.space(0, space(SpaceS)) | |
| 140 | + | |
| 141 | + b.text("theme spacing scale", TextHeading) | |
| 142 | + b.column: | |
| 143 | + b.spacing(space(SpaceXxxs)) | |
| 144 | + for step in SpaceStep: | |
| 145 | + b.text(&"{step}: {space(step):.0f}px", TextMonotext) | |
| 146 | + | |
| 147 | +proc onView(ctx: pointer; b: Builder) {.cdecl.} = | |
| 148 | + let s = cast[ptr State](ctx) | |
| 149 | + | |
| 150 | + b.container: | |
| 151 | + b.fill() | |
| 152 | + b.alignCenter() | |
| 153 | + b.spacing(space(SpaceM)) | |
| 154 | + | |
| 155 | + b.navBar(s) | |
| 156 | + | |
| 157 | + case s.page | |
| 158 | + of PageCounter: b.counterPage(s) | |
| 159 | + of PageText: b.textPage() | |
| 160 | + of PageButtons: b.buttonsPage(s) | |
| 161 | + of PageLayout: b.layoutPage(s) | |
| 162 | + | |
| 163 | +proc main() = | |
| 164 | + var state = State(page: PageCounter, value: 0, step: 1, gap: 12) | |
| 165 | + var config = CosmicConfig( | |
| 166 | + title: "Nim ❤ COSMIC", | |
| 167 | + onView: onView, | |
| 168 | + onPress: onPress, | |
| 169 | + ctx: addr state, | |
| 170 | + width: 560, | |
| 171 | + height: 460, | |
| 172 | + ) | |
| 173 | + let rc = cosmicRun(addr config) | |
| 174 | + if rc != 0: | |
| 175 | + quit(&"cosmic_run failed: {rc}", 1) | |
| 176 | + echo &"final count: {state.value}, last page: {state.page}" | |
| 177 | + | |
| 178 | +main() | |
| new file mode 100644 | |||
| @@ -0,0 +1,178 @@ | |||
| 1 | +## A small gallery of what the builder can express. | ||
| 2 | +## | ||
| 3 | +## Every frame the whole tree is rebuilt from Nim state, so switching pages | ||
| 4 | +## is not a widget being shown or hidden — it is a different tree. The same | ||
| 5 | +## goes for the disabled buttons and the history line: they are simply not | ||
| 6 | +## described when the state does not call for them. | ||
| 7 | + | ||
| 8 | +import std/[strformat, strutils] | ||
| 9 | +import cosmicnim | ||
| 10 | + | ||
| 11 | +type Page = enum | ||
| 12 | + PageCounter = "Counter" | ||
| 13 | + PageText = "Text" | ||
| 14 | + PageButtons = "Buttons" | ||
| 15 | + PageLayout = "Layout" | ||
| 16 | + | ||
| 17 | +const | ||
| 18 | + # Nav takes one id per page; pages take ids from 10 up. | ||
| 19 | + IdNavBase = 0'i32 | ||
| 20 | + IdDec = 10'i32 | ||
| 21 | + IdInc = 11'i32 | ||
| 22 | + IdReset = 12'i32 | ||
| 23 | + IdStepDown = 13'i32 | ||
| 24 | + IdStepUp = 14'i32 | ||
| 25 | + IdPokeStandard = 20'i32 | ||
| 26 | + IdPokeSuggested = 21'i32 | ||
| 27 | + IdPokeDestructive = 22'i32 | ||
| 28 | + IdGapNarrower = 30'i32 | ||
| 29 | + IdGapWider = 31'i32 | ||
| 30 | + | ||
| 31 | +type State = object | ||
| 32 | + page: Page | ||
| 33 | + value: int | ||
| 34 | + step: int | ||
| 35 | + history: seq[int] | ||
| 36 | + poked: string | ||
| 37 | + gap: int | ||
| 38 | + | ||
| 39 | +proc apply(s: var State; delta: int) = | ||
| 40 | + s.history.add s.value | ||
| 41 | + if s.history.len > 6: | ||
| 42 | + s.history.delete(0) | ||
| 43 | + s.value += delta | ||
| 44 | + | ||
| 45 | +proc onPress(ctx: pointer; id: int32) {.cdecl.} = | ||
| 46 | + let s = cast[ptr State](ctx) | ||
| 47 | + if id >= IdNavBase and id <= IdNavBase + int32(ord(high(Page))): | ||
| 48 | + s.page = Page(id - IdNavBase) | ||
| 49 | + return | ||
| 50 | + case id | ||
| 51 | + of IdDec: s[].apply(-s.step) | ||
| 52 | + of IdInc: s[].apply(s.step) | ||
| 53 | + of IdReset: s[].apply(-s.value) | ||
| 54 | + of IdStepDown: s.step = max(1, s.step div 2) | ||
| 55 | + of IdStepUp: s.step = min(100, s.step * 2) | ||
| 56 | + of IdPokeStandard: s.poked = "standard" | ||
| 57 | + of IdPokeSuggested: s.poked = "suggested" | ||
| 58 | + of IdPokeDestructive: s.poked = "destructive" | ||
| 59 | + of IdGapNarrower: s.gap = max(0, s.gap - 4) | ||
| 60 | + of IdGapWider: s.gap = min(48, s.gap + 4) | ||
| 61 | + else: discard | ||
| 62 | + | ||
| 63 | +proc navBar(b: Builder; s: ptr State) = | ||
| 64 | + ## One button per page. The current page's button is inert, which is both | ||
| 65 | + ## the highlight and the reason it cannot be pressed twice. | ||
| 66 | + b.row: | ||
| 67 | + b.spacing(space(SpaceXxs)) | ||
| 68 | + b.alignCenter() | ||
| 69 | + for p in Page: | ||
| 70 | + b.button($p, IdNavBase + int32(ord(p)), | ||
| 71 | + if p == s.page: ButtonSuggested else: ButtonText, | ||
| 72 | + enabled = p != s.page) | ||
| 73 | + | ||
| 74 | +proc counterPage(b: Builder; s: ptr State) = | ||
| 75 | + b.text(&"{s.value}", TextTitle1) | ||
| 76 | + | ||
| 77 | + b.row: | ||
| 78 | + b.spacing(space(SpaceS)) | ||
| 79 | + b.alignCenter() | ||
| 80 | + b.button(&"−{s.step}", IdDec) | ||
| 81 | + b.button("Reset", IdReset, ButtonDestructive, enabled = s.value != 0) | ||
| 82 | + b.button(&"+{s.step}", IdInc, ButtonSuggested) | ||
| 83 | + | ||
| 84 | + b.row: | ||
| 85 | + b.spacing(space(SpaceXs)) | ||
| 86 | + b.alignCenter() | ||
| 87 | + b.button("÷2", IdStepDown, ButtonText, enabled = s.step > 1) | ||
| 88 | + b.text(&"step {s.step}", TextCaption) | ||
| 89 | + b.button("×2", IdStepUp, ButtonText, enabled = s.step < 100) | ||
| 90 | + | ||
| 91 | + if s.history.len > 0: | ||
| 92 | + b.text(s.history.join(" → ") & " → " & $s.value, TextCaption) | ||
| 93 | + | ||
| 94 | +proc textPage(b: Builder) = | ||
| 95 | + ## Every style the binding exposes, labelled with itself. | ||
| 96 | + b.column: | ||
| 97 | + b.spacing(space(SpaceXxs)) | ||
| 98 | + for style in TextStyle: | ||
| 99 | + b.text($style, style) | ||
| 100 | + | ||
| 101 | +proc buttonsPage(b: Builder; s: ptr State) = | ||
| 102 | + b.row: | ||
| 103 | + b.spacing(space(SpaceS)) | ||
| 104 | + b.alignCenter() | ||
| 105 | + b.button("Standard", IdPokeStandard) | ||
| 106 | + b.button("Suggested", IdPokeSuggested, ButtonSuggested) | ||
| 107 | + b.button("Destructive", IdPokeDestructive, ButtonDestructive) | ||
| 108 | + | ||
| 109 | + b.row: | ||
| 110 | + b.spacing(space(SpaceS)) | ||
| 111 | + b.alignCenter() | ||
| 112 | + b.button("Text", IdPokeStandard, ButtonText) | ||
| 113 | + b.button("Link", IdPokeStandard, ButtonLink) | ||
| 114 | + b.button("Disabled", IdPokeStandard, ButtonStandard, enabled = false) | ||
| 115 | + | ||
| 116 | + b.text( | ||
| 117 | + if s.poked.len == 0: "none pressed yet" else: &"last pressed: {s.poked}", | ||
| 118 | + TextCaption) | ||
| 119 | + | ||
| 120 | +proc layoutPage(b: Builder; s: ptr State) = | ||
| 121 | + ## The gap between the boxes is host state, so the row is re-described at a | ||
| 122 | + ## different spacing each time rather than being mutated in place. | ||
| 123 | + b.text(&"row spacing: {s.gap}px", TextHeading) | ||
| 124 | + | ||
| 125 | + b.row: | ||
| 126 | + b.spacing(float(s.gap)) | ||
| 127 | + b.alignCenter() | ||
| 128 | + for label in ["one", "two", "three"]: | ||
| 129 | + b.container: | ||
| 130 | + b.padding(space(SpaceXs)) | ||
| 131 | + b.text(label, TextBody) | ||
| 132 | + | ||
| 133 | + b.row: | ||
| 134 | + b.spacing(space(SpaceXs)) | ||
| 135 | + b.alignCenter() | ||
| 136 | + b.button("narrower", IdGapNarrower, ButtonText, enabled = s.gap > 0) | ||
| 137 | + b.button("wider", IdGapWider, ButtonText, enabled = s.gap < 48) | ||
| 138 | + | ||
| 139 | + b.space(0, space(SpaceS)) | ||
| 140 | + | ||
| 141 | + b.text("theme spacing scale", TextHeading) | ||
| 142 | + b.column: | ||
| 143 | + b.spacing(space(SpaceXxxs)) | ||
| 144 | + for step in SpaceStep: | ||
| 145 | + b.text(&"{step}: {space(step):.0f}px", TextMonotext) | ||
| 146 | + | ||
| 147 | +proc onView(ctx: pointer; b: Builder) {.cdecl.} = | ||
| 148 | + let s = cast[ptr State](ctx) | ||
| 149 | + | ||
| 150 | + b.container: | ||
| 151 | + b.fill() | ||
| 152 | + b.alignCenter() | ||
| 153 | + b.spacing(space(SpaceM)) | ||
| 154 | + | ||
| 155 | + b.navBar(s) | ||
| 156 | + | ||
| 157 | + case s.page | ||
| 158 | + of PageCounter: b.counterPage(s) | ||
| 159 | + of PageText: b.textPage() | ||
| 160 | + of PageButtons: b.buttonsPage(s) | ||
| 161 | + of PageLayout: b.layoutPage(s) | ||
| 162 | + | ||
| 163 | +proc main() = | ||
| 164 | + var state = State(page: PageCounter, value: 0, step: 1, gap: 12) | ||
| 165 | + var config = CosmicConfig( | ||
| 166 | + title: "Nim ❤ COSMIC", | ||
| 167 | + onView: onView, | ||
| 168 | + onPress: onPress, | ||
| 169 | + ctx: addr state, | ||
| 170 | + width: 560, | ||
| 171 | + height: 460, | ||
| 172 | + ) | ||
| 173 | + let rc = cosmicRun(addr config) | ||
| 174 | + if rc != 0: | ||
| 175 | + quit(&"cosmic_run failed: {rc}", 1) | ||
| 176 | + echo &"final count: {state.value}, last page: {state.page}" | ||
| 177 | + | ||
| 178 | +main() | ||
modified
justfile +11 -32 | @@ -2,18 +2,16 @@ | ||
| 2 | 2 | |
| 3 | 3 | so := "nim/libcosmic_ffi.so" |
| 4 | 4 | export REMOTE := "rickub" |
| 5 | -export BASE_URL := "https://rickub.com/nandi/cosmicnim" | |
| 6 | -export TARGET := "x86_64-unknown-linux-gnu" | |
| 7 | 5 | |
| 8 | 6 | default: run |
| 9 | 7 | |
| 10 | -# Run the Nim demo. Needs nim/libcosmic_ffi.so (see `fetch` or `build`). | |
| 8 | +# Run the demo gallery. Needs nim/libcosmic_ffi.so (see `fetch` or `build`). | |
| 11 | 9 | run: _have-so |
| 12 | - cd nim && nim c -r app.nim | |
| 10 | + nim c -r --path:nim examples/gallery.nim | |
| 13 | 11 | |
| 14 | 12 | # Type-check the Nim side. Needs no .so: the binding loads it lazily. |
| 15 | 13 | check: |
| 16 | - cd nim && nim check app.nim | |
| 14 | + nim check --path:nim examples/gallery.nim | |
| 17 | 15 | |
| 18 | 16 | # Build the cdylib from source into nim/ (cold builds are slow; CI is faster) |
| 19 | 17 | build: |
| @@ -27,36 +25,17 @@ rust-check: | ||
| 27 | 25 | fmt: |
| 28 | 26 | cd cosmic_ffi && cargo fmt |
| 29 | 27 | |
| 30 | -# Install the .so from a release. No tag means the newest v* tag on the remote. | |
| 28 | +# Download the cdylib. No tag means the one matching cosmicnim.nimble. | |
| 31 | 29 | fetch tag="": |
| 32 | 30 | #!/usr/bin/env bash |
| 33 | 31 | set -euo pipefail |
| 34 | - tag="{{tag}}" | |
| 35 | - if [ -z "$tag" ]; then | |
| 36 | - # The remote is the source of truth; local tags may be stale or absent. | |
| 37 | - tag=$(git ls-remote --tags --refs "$REMOTE" 'v*' \ | |
| 38 | - | awk -F/ '{print $NF}' | sort -V | tail -1) | |
| 39 | - [ -n "$tag" ] || { echo "no v* tags on $REMOTE" >&2; exit 1; } | |
| 40 | - echo "latest release is $tag" | |
| 41 | - fi | |
| 42 | - just fetch-url "$BASE_URL/releases/download/$tag/cosmic_ffi-$tag-$TARGET.tar.gz" | |
| 32 | + # Only export when non-empty: an empty COSMICNIM_TAG would beat the default. | |
| 33 | + [ -z "{{tag}}" ] || export COSMICNIM_TAG="{{tag}}" | |
| 34 | + nimble fetchLib | |
| 43 | 35 | |
| 44 | -# Install the .so from an explicit asset URL. | |
| 45 | -fetch-url url: | |
| 46 | - #!/usr/bin/env bash | |
| 47 | - set -euo pipefail | |
| 48 | - tmp=$(mktemp -d) | |
| 49 | - trap 'rm -rf "$tmp"' EXIT | |
| 50 | - curl -fsSL -o "$tmp/asset.tar.gz" "{{url}}" | |
| 51 | - # The workflow publishes a checksum beside every tarball; use it if it is there. | |
| 52 | - if curl -fsSL -o "$tmp/asset.sha256" "{{url}}.sha256" 2>/dev/null; then | |
| 53 | - (cd "$tmp" && sed "s| .*| asset.tar.gz|" asset.sha256 | sha256sum -c -) | |
| 54 | - else | |
| 55 | - echo "no .sha256 alongside the asset; skipping checksum" >&2 | |
| 56 | - fi | |
| 57 | - tar xzf "$tmp/asset.tar.gz" -C "$tmp" | |
| 58 | - cp "$tmp"/cosmic_ffi-*/libcosmic_ffi.so {{so}} | |
| 59 | - echo "installed {{so}}" | |
| 36 | +# Install the package (and its cdylib) into the local nimble store. | |
| 37 | +install: | |
| 38 | + nimble install --verbose | |
| 60 | 39 | |
| 61 | 40 | # Cut a release by pushing a tag with git (an API-made tag triggers no CI) |
| 62 | 41 | tag version: |
| @@ -64,7 +43,7 @@ tag version: | ||
| 64 | 43 | git push "$REMOTE" "{{version}}" |
| 65 | 44 | |
| 66 | 45 | clean: |
| 67 | - rm -rf cosmic_ffi/target nim/nimcache nim/app | |
| 46 | + rm -rf cosmic_ffi/target examples/nimcache examples/gallery | |
| 68 | 47 | |
| 69 | 48 | _have-so: |
| 70 | 49 | @test -f {{so}} || { echo "missing {{so}} -- run 'just build' or 'just fetch <url>'" >&2; exit 1; } |
| @@ -2,18 +2,16 @@ | |||
| 2 | 2 | ||
| 3 | so := "nim/libcosmic_ffi.so" | 3 | so := "nim/libcosmic_ffi.so" |
| 4 | export REMOTE := "rickub" | 4 | export REMOTE := "rickub" |
| 5 | -export BASE_URL := "https://rickub.com/nandi/cosmicnim" | ||
| 6 | -export TARGET := "x86_64-unknown-linux-gnu" | ||
| 7 | 5 | ||
| 8 | default: run | 6 | default: run |
| 9 | 7 | ||
| 10 | -# Run the Nim demo. Needs nim/libcosmic_ffi.so (see `fetch` or `build`). | 8 | +# Run the demo gallery. Needs nim/libcosmic_ffi.so (see `fetch` or `build`). |
| 11 | run: _have-so | 9 | run: _have-so |
| 12 | - cd nim && nim c -r app.nim | 10 | + nim c -r --path:nim examples/gallery.nim |
| 13 | 11 | ||
| 14 | # Type-check the Nim side. Needs no .so: the binding loads it lazily. | 12 | # Type-check the Nim side. Needs no .so: the binding loads it lazily. |
| 15 | check: | 13 | check: |
| 16 | - cd nim && nim check app.nim | 14 | + nim check --path:nim examples/gallery.nim |
| 17 | 15 | ||
| 18 | # Build the cdylib from source into nim/ (cold builds are slow; CI is faster) | 16 | # Build the cdylib from source into nim/ (cold builds are slow; CI is faster) |
| 19 | build: | 17 | build: |
| @@ -27,36 +25,17 @@ rust-check: | |||
| 27 | fmt: | 25 | fmt: |
| 28 | cd cosmic_ffi && cargo fmt | 26 | cd cosmic_ffi && cargo fmt |
| 29 | 27 | ||
| 30 | -# Install the .so from a release. No tag means the newest v* tag on the remote. | 28 | +# Download the cdylib. No tag means the one matching cosmicnim.nimble. |
| 31 | fetch tag="": | 29 | fetch tag="": |
| 32 | #!/usr/bin/env bash | 30 | #!/usr/bin/env bash |
| 33 | set -euo pipefail | 31 | set -euo pipefail |
| 34 | - tag="{{tag}}" | 32 | + # Only export when non-empty: an empty COSMICNIM_TAG would beat the default. |
| 35 | - if [ -z "$tag" ]; then | 33 | + [ -z "{{tag}}" ] || export COSMICNIM_TAG="{{tag}}" |
| 36 | - # The remote is the source of truth; local tags may be stale or absent. | 34 | + nimble fetchLib |
| 37 | - tag=$(git ls-remote --tags --refs "$REMOTE" 'v*' \ | ||
| 38 | - | awk -F/ '{print $NF}' | sort -V | tail -1) | ||
| 39 | - [ -n "$tag" ] || { echo "no v* tags on $REMOTE" >&2; exit 1; } | ||
| 40 | - echo "latest release is $tag" | ||
| 41 | - fi | ||
| 42 | - just fetch-url "$BASE_URL/releases/download/$tag/cosmic_ffi-$tag-$TARGET.tar.gz" | ||
| 43 | 35 | ||
| 44 | -# Install the .so from an explicit asset URL. | 36 | +# Install the package (and its cdylib) into the local nimble store. |
| 45 | -fetch-url url: | 37 | +install: |
| 46 | - #!/usr/bin/env bash | 38 | + nimble install --verbose |
| 47 | - set -euo pipefail | ||
| 48 | - tmp=$(mktemp -d) | ||
| 49 | - trap 'rm -rf "$tmp"' EXIT | ||
| 50 | - curl -fsSL -o "$tmp/asset.tar.gz" "{{url}}" | ||
| 51 | - # The workflow publishes a checksum beside every tarball; use it if it is there. | ||
| 52 | - if curl -fsSL -o "$tmp/asset.sha256" "{{url}}.sha256" 2>/dev/null; then | ||
| 53 | - (cd "$tmp" && sed "s| .*| asset.tar.gz|" asset.sha256 | sha256sum -c -) | ||
| 54 | - else | ||
| 55 | - echo "no .sha256 alongside the asset; skipping checksum" >&2 | ||
| 56 | - fi | ||
| 57 | - tar xzf "$tmp/asset.tar.gz" -C "$tmp" | ||
| 58 | - cp "$tmp"/cosmic_ffi-*/libcosmic_ffi.so {{so}} | ||
| 59 | - echo "installed {{so}}" | ||
| 60 | 39 | ||
| 61 | # Cut a release by pushing a tag with git (an API-made tag triggers no CI) | 40 | # Cut a release by pushing a tag with git (an API-made tag triggers no CI) |
| 62 | tag version: | 41 | tag version: |
| @@ -64,7 +43,7 @@ tag version: | |||
| 64 | git push "$REMOTE" "{{version}}" | 43 | git push "$REMOTE" "{{version}}" |
| 65 | 44 | ||
| 66 | clean: | 45 | clean: |
| 67 | - rm -rf cosmic_ffi/target nim/nimcache nim/app | 46 | + rm -rf cosmic_ffi/target examples/nimcache examples/gallery |
| 68 | 47 | ||
| 69 | _have-so: | 48 | _have-so: |
| 70 | @test -f {{so}} || { echo "missing {{so}} -- run 'just build' or 'just fetch <url>'" >&2; exit 1; } | 49 | @test -f {{so}} || { echo "missing {{so}} -- run 'just build' or 'just fetch <url>'" >&2; exit 1; } |
added
nim/app +0 -0 | new file mode 100755 | ||
| Binary files /dev/null and b/nim/app differ | ||
| new file mode 100755 | |||
| Binary files /dev/null and b/nim/app differ | Binary files /dev/null and b/nim/app differ | ||
deleted
nim/app.nim +0 -80 | deleted file mode 100644 | ||
| @@ -1,80 +0,0 @@ | ||
| 1 | -## A counter whose state *and layout* live in Nim; libcosmic only draws it. | |
| 2 | -## | |
| 3 | -## The tree is rebuilt from scratch every frame, so it can depend on the | |
| 4 | -## state: reset is disabled at zero, and the history only appears once | |
| 5 | -## there is some. | |
| 6 | - | |
| 7 | -import std/[strformat, strutils] | |
| 8 | -import cosmic | |
| 9 | - | |
| 10 | -const | |
| 11 | - IdDec = 0'i32 | |
| 12 | - IdInc = 1'i32 | |
| 13 | - IdReset = 2'i32 | |
| 14 | - IdStepDown = 3'i32 | |
| 15 | - IdStepUp = 4'i32 | |
| 16 | - | |
| 17 | -type Counter = object | |
| 18 | - value: int | |
| 19 | - step: int | |
| 20 | - history: seq[int] | |
| 21 | - | |
| 22 | -proc apply(c: var Counter; delta: int) = | |
| 23 | - c.history.add c.value | |
| 24 | - if c.history.len > 5: | |
| 25 | - c.history.delete(0) | |
| 26 | - c.value += delta | |
| 27 | - | |
| 28 | -proc onPress(ctx: pointer; id: int32) {.cdecl.} = | |
| 29 | - let c = cast[ptr Counter](ctx) | |
| 30 | - case id | |
| 31 | - of IdDec: c[].apply(-c.step) | |
| 32 | - of IdInc: c[].apply(c.step) | |
| 33 | - of IdReset: c[].apply(-c.value) | |
| 34 | - of IdStepDown: c.step = max(1, c.step div 2) | |
| 35 | - of IdStepUp: c.step = min(100, c.step * 2) | |
| 36 | - else: discard | |
| 37 | - | |
| 38 | -proc onView(ctx: pointer; b: Builder) {.cdecl.} = | |
| 39 | - let c = cast[ptr Counter](ctx) | |
| 40 | - | |
| 41 | - b.container: | |
| 42 | - b.fill() | |
| 43 | - b.alignCenter() | |
| 44 | - b.spacing(space(SpaceM)) | |
| 45 | - | |
| 46 | - b.text(&"{c.value}", TextTitle1) | |
| 47 | - | |
| 48 | - b.row: | |
| 49 | - b.spacing(space(SpaceS)) | |
| 50 | - b.alignCenter() | |
| 51 | - b.button(&"−{c.step}", IdDec) | |
| 52 | - b.button("Reset", IdReset, ButtonDestructive, enabled = c.value != 0) | |
| 53 | - b.button(&"+{c.step}", IdInc, ButtonSuggested) | |
| 54 | - | |
| 55 | - b.row: | |
| 56 | - b.spacing(space(SpaceXs)) | |
| 57 | - b.alignCenter() | |
| 58 | - b.button("÷2", IdStepDown, ButtonText, enabled = c.step > 1) | |
| 59 | - b.text(&"step {c.step}", TextCaption) | |
| 60 | - b.button("×2", IdStepUp, ButtonText, enabled = c.step < 100) | |
| 61 | - | |
| 62 | - if c.history.len > 0: | |
| 63 | - b.text(c.history.join(" → ") & " → " & $c.value, TextCaption) | |
| 64 | - | |
| 65 | -proc main() = | |
| 66 | - var counter = Counter(value: 0, step: 1) | |
| 67 | - var config = CosmicConfig( | |
| 68 | - title: "Nim ❤ COSMIC", | |
| 69 | - onView: onView, | |
| 70 | - onPress: onPress, | |
| 71 | - ctx: addr counter, | |
| 72 | - width: 460, | |
| 73 | - height: 320, | |
| 74 | - ) | |
| 75 | - let rc = cosmicRun(addr config) | |
| 76 | - if rc != 0: | |
| 77 | - quit(&"cosmic_run failed: {rc}", 1) | |
| 78 | - echo &"final count: {counter.value}" | |
| 79 | - | |
| 80 | -main() | |
| deleted file mode 100644 | |||
| @@ -1,80 +0,0 @@ | |||
| 1 | -## A counter whose state *and layout* live in Nim; libcosmic only draws it. | ||
| 2 | -## | ||
| 3 | -## The tree is rebuilt from scratch every frame, so it can depend on the | ||
| 4 | -## state: reset is disabled at zero, and the history only appears once | ||
| 5 | -## there is some. | ||
| 6 | - | ||
| 7 | -import std/[strformat, strutils] | ||
| 8 | -import cosmic | ||
| 9 | - | ||
| 10 | -const | ||
| 11 | - IdDec = 0'i32 | ||
| 12 | - IdInc = 1'i32 | ||
| 13 | - IdReset = 2'i32 | ||
| 14 | - IdStepDown = 3'i32 | ||
| 15 | - IdStepUp = 4'i32 | ||
| 16 | - | ||
| 17 | -type Counter = object | ||
| 18 | - value: int | ||
| 19 | - step: int | ||
| 20 | - history: seq[int] | ||
| 21 | - | ||
| 22 | -proc apply(c: var Counter; delta: int) = | ||
| 23 | - c.history.add c.value | ||
| 24 | - if c.history.len > 5: | ||
| 25 | - c.history.delete(0) | ||
| 26 | - c.value += delta | ||
| 27 | - | ||
| 28 | -proc onPress(ctx: pointer; id: int32) {.cdecl.} = | ||
| 29 | - let c = cast[ptr Counter](ctx) | ||
| 30 | - case id | ||
| 31 | - of IdDec: c[].apply(-c.step) | ||
| 32 | - of IdInc: c[].apply(c.step) | ||
| 33 | - of IdReset: c[].apply(-c.value) | ||
| 34 | - of IdStepDown: c.step = max(1, c.step div 2) | ||
| 35 | - of IdStepUp: c.step = min(100, c.step * 2) | ||
| 36 | - else: discard | ||
| 37 | - | ||
| 38 | -proc onView(ctx: pointer; b: Builder) {.cdecl.} = | ||
| 39 | - let c = cast[ptr Counter](ctx) | ||
| 40 | - | ||
| 41 | - b.container: | ||
| 42 | - b.fill() | ||
| 43 | - b.alignCenter() | ||
| 44 | - b.spacing(space(SpaceM)) | ||
| 45 | - | ||
| 46 | - b.text(&"{c.value}", TextTitle1) | ||
| 47 | - | ||
| 48 | - b.row: | ||
| 49 | - b.spacing(space(SpaceS)) | ||
| 50 | - b.alignCenter() | ||
| 51 | - b.button(&"−{c.step}", IdDec) | ||
| 52 | - b.button("Reset", IdReset, ButtonDestructive, enabled = c.value != 0) | ||
| 53 | - b.button(&"+{c.step}", IdInc, ButtonSuggested) | ||
| 54 | - | ||
| 55 | - b.row: | ||
| 56 | - b.spacing(space(SpaceXs)) | ||
| 57 | - b.alignCenter() | ||
| 58 | - b.button("÷2", IdStepDown, ButtonText, enabled = c.step > 1) | ||
| 59 | - b.text(&"step {c.step}", TextCaption) | ||
| 60 | - b.button("×2", IdStepUp, ButtonText, enabled = c.step < 100) | ||
| 61 | - | ||
| 62 | - if c.history.len > 0: | ||
| 63 | - b.text(c.history.join(" → ") & " → " & $c.value, TextCaption) | ||
| 64 | - | ||
| 65 | -proc main() = | ||
| 66 | - var counter = Counter(value: 0, step: 1) | ||
| 67 | - var config = CosmicConfig( | ||
| 68 | - title: "Nim ❤ COSMIC", | ||
| 69 | - onView: onView, | ||
| 70 | - onPress: onPress, | ||
| 71 | - ctx: addr counter, | ||
| 72 | - width: 460, | ||
| 73 | - height: 320, | ||
| 74 | - ) | ||
| 75 | - let rc = cosmicRun(addr config) | ||
| 76 | - if rc != 0: | ||
| 77 | - quit(&"cosmic_run failed: {rc}", 1) | ||
| 78 | - echo &"final count: {counter.value}" | ||
| 79 | - | ||
| 80 | -main() | ||
deleted
nim/app.nims +0 -1 | deleted file mode 100644 | ||
| @@ -1 +0,0 @@ | ||
| 1 | -switch("passL", "-Wl,-rpath,$ORIGIN:$ORIGIN/../lib") | |
| deleted file mode 100644 | |||
| @@ -1 +0,0 @@ | |||
| 1 | -switch("passL", "-Wl,-rpath,$ORIGIN:$ORIGIN/../lib") | ||
renamed
nim/cosmicnim.nim +7 -1 | similarity index 92% | ||
| rename from nim/cosmic.nim | ||
| rename to nim/cosmicnim.nim | ||
| @@ -14,7 +14,13 @@ | ||
| 14 | 14 | ## b.button("+", id = 1, style = ButtonSuggested) |
| 15 | 15 | ## ``` |
| 16 | 16 | |
| 17 | -const libCosmicFfi* = "libcosmic_ffi.so" | |
| 17 | +import std/os | |
| 18 | + | |
| 19 | +const libCosmicFfi* {.strdefine.} = currentSourcePath().parentDir / | |
| 20 | + "libcosmic_ffi.so" | |
| 21 | + ## Absolute at compile time, so the library is found wherever nimble put | |
| 22 | + ## the package rather than only next to the binary. Override with | |
| 23 | + ## `-d:libCosmicFfi=/some/other/libcosmic_ffi.so`. | |
| 18 | 24 | |
| 19 | 25 | type |
| 20 | 26 | Builder* = distinct pointer |
| similarity index 92% | |||
| rename from nim/cosmic.nim | |||
| rename to nim/cosmicnim.nim | |||
| @@ -14,7 +14,13 @@ | |||
| 14 | ## b.button("+", id = 1, style = ButtonSuggested) | 14 | ## b.button("+", id = 1, style = ButtonSuggested) |
| 15 | ## ``` | 15 | ## ``` |
| 16 | 16 | ||
| 17 | -const libCosmicFfi* = "libcosmic_ffi.so" | 17 | +import std/os |
| 18 | + | ||
| 19 | +const libCosmicFfi* {.strdefine.} = currentSourcePath().parentDir / | ||
| 20 | + "libcosmic_ffi.so" | ||
| 21 | + ## Absolute at compile time, so the library is found wherever nimble put | ||
| 22 | + ## the package rather than only next to the binary. Override with | ||
| 23 | + ## `-d:libCosmicFfi=/some/other/libcosmic_ffi.so`. | ||
| 18 | 24 | ||
| 19 | type | 25 | type |
| 20 | Builder* = distinct pointer | 26 | Builder* = distinct pointer |