ink-sdl 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -168,6 +168,7 @@ Creates stdin/stdout streams and a window for use with Ink.
168
168
  | `borderless` | `boolean` | `false` | Remove window decorations (title bar, borders) |
169
169
  | `minWidth` | `number` | `undefined` | Minimum window width in pixels |
170
170
  | `minHeight` | `number` | `undefined` | Minimum window height in pixels |
171
+ | `existing` | `ExistingSdlResources` | `undefined` | Use existing SDL window/renderer (see Advanced Usage) |
171
172
 
172
173
  #### Returns
173
174
 
@@ -213,6 +214,49 @@ if (isSdlAvailable()) {
213
214
 
214
215
  ## Advanced Usage
215
216
 
217
+ ### Using Existing SDL Window/Renderer
218
+
219
+ For applications that need to share a single SDL window between ink-sdl and custom rendering (e.g., an emulator with a menu UI), you can pass existing SDL resources:
220
+
221
+ ```typescript
222
+ import { render, Text, Box } from "ink";
223
+ import { createSdlStreams, getSdl2, type ExistingSdlResources } from "ink-sdl";
224
+
225
+ // Create your own SDL window and renderer
226
+ const sdl = getSdl2();
227
+ sdl.init(0x20 | 0x4000); // SDL_INIT_VIDEO | SDL_INIT_EVENTS
228
+
229
+ const myWindow = sdl.createWindow("My App", 100, 100, 800, 600, 0x4);
230
+ const myRenderer = sdl.createRenderer(myWindow, -1, 0x2);
231
+
232
+ // Use them with ink-sdl
233
+ const streams = createSdlStreams({
234
+ existing: { window: myWindow, renderer: myRenderer },
235
+ fontSize: 16,
236
+ });
237
+
238
+ render(<MenuApp />, { stdin: streams.stdin, stdout: streams.stdout });
239
+
240
+ // When done with ink-sdl UI, close() cleans up ink-sdl resources
241
+ // but does NOT destroy your window/renderer
242
+ streams.window.close();
243
+
244
+ // You can now render directly to the same window, or create new streams later
245
+ // When completely done, destroy the window/renderer yourself
246
+ sdl.destroyRenderer(myRenderer);
247
+ sdl.destroyWindow(myWindow);
248
+ ```
249
+
250
+ **Ownership rules:**
251
+
252
+ - When `existing` is provided, ink-sdl does NOT own the window/renderer
253
+ - `close()` will NOT destroy the provided window/renderer
254
+ - The caller retains ownership and must destroy them when fully done
255
+ - Window options (`width`, `height`, `title`, `fullscreen`, `borderless`) are ignored
256
+ - Rendering options (`fontSize`, `scaleFactor`, `fontPath`, etc.) still apply
257
+
258
+ ### Low-Level Components
259
+
216
260
  For more control, you can use the lower-level components directly:
217
261
 
218
262
  ```typescript
@@ -223,6 +267,8 @@ import {
223
267
  InputBridge,
224
268
  getSdl2,
225
269
  getSdlTtf,
270
+ type ExistingSdlResources,
271
+ type SDLPointer,
226
272
  } from "ink-sdl";
227
273
  ```
228
274
 
@@ -1886,6 +1886,10 @@ var SdlUiRenderer = class {
1886
1886
  renderer = null;
1887
1887
  textRenderer = null;
1888
1888
  renderTarget = null;
1889
+ /** Whether we own the window (should destroy it on cleanup) */
1890
+ ownsWindow = true;
1891
+ /** Whether we own the renderer (should destroy it on cleanup) */
1892
+ ownsRenderer = true;
1889
1893
  ansiParser;
1890
1894
  inputBridge;
1891
1895
  windowWidth;
@@ -1920,38 +1924,13 @@ var SdlUiRenderer = class {
1920
1924
  * Initialize SDL window and renderer
1921
1925
  */
1922
1926
  initSDL(options) {
1923
- if (!this.sdl.init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
1924
- throw new Error("Failed to initialize SDL2 for UI rendering");
1925
- }
1926
1927
  this.defaultBgColor = parseBackgroundColor(options.backgroundColor);
1927
1928
  this.bgColor = { ...this.defaultBgColor };
1928
- let windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI;
1929
- if (!options.fullscreen) {
1930
- windowFlags |= SDL_WINDOW_RESIZABLE;
1931
- }
1932
- if (options.fullscreen === "desktop") {
1933
- windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1934
- } else if (options.fullscreen === true) {
1935
- windowFlags |= SDL_WINDOW_FULLSCREEN;
1936
- }
1937
- if (options.borderless && !options.fullscreen) {
1938
- windowFlags |= SDL_WINDOW_BORDERLESS;
1939
- }
1940
- this.window = this.sdl.createWindow(
1941
- options.title ?? "ink-sdl",
1942
- SDL_WINDOWPOS_CENTERED,
1943
- SDL_WINDOWPOS_CENTERED,
1944
- this.windowWidth,
1945
- this.windowHeight,
1946
- windowFlags
1947
- );
1948
- if (options.minWidth !== void 0 || options.minHeight !== void 0) {
1949
- const minW = options.minWidth ?? 1;
1950
- const minH = options.minHeight ?? 1;
1951
- this.sdl.setWindowMinimumSize(this.window, minW, minH);
1929
+ if (options.existing) {
1930
+ this.initWithExistingResources(options);
1931
+ } else {
1932
+ this.initNewResources(options);
1952
1933
  }
1953
- const rendererFlags = options.vsync !== false ? SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC : SDL_RENDERER_ACCELERATED;
1954
- this.renderer = this.sdl.createRenderer(this.window, -1, rendererFlags);
1955
1934
  this.userScaleFactor = options.scaleFactor === void 0 ? null : options.scaleFactor;
1956
1935
  if (this.userScaleFactor !== null) {
1957
1936
  this.scaleFactor = this.userScaleFactor;
@@ -1979,7 +1958,62 @@ var SdlUiRenderer = class {
1979
1958
  this.sdl.setRenderTarget(this.renderer, null);
1980
1959
  this.sdl.renderClear(this.renderer);
1981
1960
  this.sdl.renderPresent(this.renderer);
1982
- this.sdl.raiseWindow(this.window);
1961
+ if (this.ownsWindow) {
1962
+ this.sdl.raiseWindow(this.window);
1963
+ }
1964
+ }
1965
+ /**
1966
+ * Initialize with existing SDL window and renderer
1967
+ */
1968
+ initWithExistingResources(options) {
1969
+ const existing = options.existing;
1970
+ this.window = existing.window;
1971
+ this.renderer = existing.renderer;
1972
+ this.ownsWindow = false;
1973
+ this.ownsRenderer = false;
1974
+ if (!this.sdl.init(SDL_INIT_EVENTS)) {
1975
+ throw new Error("Failed to initialize SDL2 events");
1976
+ }
1977
+ const size = this.sdl.getWindowSize(this.window);
1978
+ this.windowWidth = size.width;
1979
+ this.windowHeight = size.height;
1980
+ }
1981
+ /**
1982
+ * Initialize by creating new SDL window and renderer
1983
+ */
1984
+ initNewResources(options) {
1985
+ if (!this.sdl.init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
1986
+ throw new Error("Failed to initialize SDL2 for UI rendering");
1987
+ }
1988
+ this.ownsWindow = true;
1989
+ this.ownsRenderer = true;
1990
+ let windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI;
1991
+ if (!options.fullscreen) {
1992
+ windowFlags |= SDL_WINDOW_RESIZABLE;
1993
+ }
1994
+ if (options.fullscreen === "desktop") {
1995
+ windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1996
+ } else if (options.fullscreen === true) {
1997
+ windowFlags |= SDL_WINDOW_FULLSCREEN;
1998
+ }
1999
+ if (options.borderless && !options.fullscreen) {
2000
+ windowFlags |= SDL_WINDOW_BORDERLESS;
2001
+ }
2002
+ this.window = this.sdl.createWindow(
2003
+ options.title ?? "ink-sdl",
2004
+ SDL_WINDOWPOS_CENTERED,
2005
+ SDL_WINDOWPOS_CENTERED,
2006
+ this.windowWidth,
2007
+ this.windowHeight,
2008
+ windowFlags
2009
+ );
2010
+ if (options.minWidth !== void 0 || options.minHeight !== void 0) {
2011
+ const minW = options.minWidth ?? 1;
2012
+ const minH = options.minHeight ?? 1;
2013
+ this.sdl.setWindowMinimumSize(this.window, minW, minH);
2014
+ }
2015
+ const rendererFlags = options.vsync !== false ? SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC : SDL_RENDERER_ACCELERATED;
2016
+ this.renderer = this.sdl.createRenderer(this.window, -1, rendererFlags);
1983
2017
  }
1984
2018
  /**
1985
2019
  * Update terminal dimensions based on window size
@@ -2344,6 +2378,11 @@ var SdlUiRenderer = class {
2344
2378
  }
2345
2379
  /**
2346
2380
  * Clean up resources
2381
+ *
2382
+ * When using existing window/renderer (via `existing` option), this method
2383
+ * will NOT destroy the window or renderer - only the resources created by
2384
+ * ink-sdl (TextRenderer, render target texture). The caller is responsible
2385
+ * for destroying the window/renderer they provided.
2347
2386
  */
2348
2387
  destroy() {
2349
2388
  if (this.textRenderer) {
@@ -2354,14 +2393,22 @@ var SdlUiRenderer = class {
2354
2393
  this.sdl.destroyTexture(this.renderTarget);
2355
2394
  this.renderTarget = null;
2356
2395
  }
2357
- if (this.renderer) {
2396
+ if (this.ownsRenderer && this.renderer) {
2358
2397
  this.sdl.destroyRenderer(this.renderer);
2359
- this.renderer = null;
2360
2398
  }
2361
- if (this.window) {
2399
+ this.renderer = null;
2400
+ if (this.ownsWindow && this.window) {
2362
2401
  this.sdl.destroyWindow(this.window);
2363
- this.window = null;
2364
2402
  }
2403
+ this.window = null;
2404
+ }
2405
+ /**
2406
+ * Check if this renderer owns the SDL window
2407
+ *
2408
+ * Returns false when using an existing window via the `existing` option.
2409
+ */
2410
+ ownsResources() {
2411
+ return this.ownsWindow && this.ownsRenderer;
2365
2412
  }
2366
2413
  /**
2367
2414
  * Reset state for reuse
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createSdlStreams
4
- } from "./chunk-YE6CUIKT.js";
4
+ } from "./chunk-LP5C65TH.js";
5
5
 
6
6
  // src/cli.tsx
7
7
  import { parseArgs } from "util";
package/dist/index.d.ts CHANGED
@@ -220,6 +220,19 @@ declare const createSDLRect: (x: number, y: number, w: number, h: number) => Buf
220
220
  * from Ink and renders it to an SDL window using text rendering.
221
221
  */
222
222
 
223
+ /**
224
+ * Existing SDL resources to use instead of creating new ones.
225
+ *
226
+ * When provided, ink-sdl will use these resources instead of creating its own.
227
+ * The caller retains ownership and is responsible for destroying them after
228
+ * ink-sdl is done.
229
+ */
230
+ interface ExistingSdlResources {
231
+ /** Existing SDL window pointer */
232
+ window: SDLPointer;
233
+ /** Existing SDL renderer pointer */
234
+ renderer: SDLPointer;
235
+ }
223
236
  interface SdlUiRendererOptions {
224
237
  width?: number;
225
238
  height?: number;
@@ -243,6 +256,19 @@ interface SdlUiRendererOptions {
243
256
  minWidth?: number | undefined;
244
257
  /** Minimum window height in pixels */
245
258
  minHeight?: number | undefined;
259
+ /**
260
+ * Use existing SDL window and renderer instead of creating new ones.
261
+ *
262
+ * When provided, ink-sdl will:
263
+ * - Use the existing window/renderer for all rendering
264
+ * - NOT destroy them when destroy() is called (caller retains ownership)
265
+ * - Read dimensions from the existing window
266
+ * - Ignore width/height/title/fullscreen/borderless options (window already exists)
267
+ *
268
+ * This enables sharing a single SDL window between ink-sdl and other renderers,
269
+ * such as an emulator that switches between menu UI and game rendering.
270
+ */
271
+ existing?: ExistingSdlResources | undefined;
246
272
  }
247
273
  /** Result from processing SDL events */
248
274
  interface ProcessEventsResult {
@@ -265,6 +291,10 @@ declare class SdlUiRenderer {
265
291
  private renderer;
266
292
  private textRenderer;
267
293
  private renderTarget;
294
+ /** Whether we own the window (should destroy it on cleanup) */
295
+ private ownsWindow;
296
+ /** Whether we own the renderer (should destroy it on cleanup) */
297
+ private ownsRenderer;
268
298
  private ansiParser;
269
299
  private inputBridge;
270
300
  private windowWidth;
@@ -291,6 +321,14 @@ declare class SdlUiRenderer {
291
321
  * Initialize SDL window and renderer
292
322
  */
293
323
  private initSDL;
324
+ /**
325
+ * Initialize with existing SDL window and renderer
326
+ */
327
+ private initWithExistingResources;
328
+ /**
329
+ * Initialize by creating new SDL window and renderer
330
+ */
331
+ private initNewResources;
294
332
  /**
295
333
  * Update terminal dimensions based on window size
296
334
  */
@@ -383,8 +421,19 @@ declare class SdlUiRenderer {
383
421
  getScaleFactor(): number;
384
422
  /**
385
423
  * Clean up resources
424
+ *
425
+ * When using existing window/renderer (via `existing` option), this method
426
+ * will NOT destroy the window or renderer - only the resources created by
427
+ * ink-sdl (TextRenderer, render target texture). The caller is responsible
428
+ * for destroying the window/renderer they provided.
386
429
  */
387
430
  destroy(): void;
431
+ /**
432
+ * Check if this renderer owns the SDL window
433
+ *
434
+ * Returns false when using an existing window via the `existing` option.
435
+ */
436
+ ownsResources(): boolean;
388
437
  /**
389
438
  * Reset state for reuse
390
439
  */
@@ -553,6 +602,37 @@ interface SdlStreamsOptions {
553
602
  minWidth?: number | undefined;
554
603
  /** Minimum window height in pixels */
555
604
  minHeight?: number | undefined;
605
+ /**
606
+ * Use existing SDL window and renderer instead of creating new ones.
607
+ *
608
+ * When provided, ink-sdl will:
609
+ * - Use the existing window/renderer for all rendering
610
+ * - NOT destroy them when the window is closed (caller retains ownership)
611
+ * - Read dimensions from the existing window
612
+ * - Ignore width/height/title/fullscreen/borderless options
613
+ *
614
+ * @example
615
+ * ```typescript
616
+ * // Create your own SDL window and renderer
617
+ * const myWindow = SDL_CreateWindow(...);
618
+ * const myRenderer = SDL_CreateRenderer(myWindow, ...);
619
+ *
620
+ * // Use them with ink-sdl
621
+ * const streams = createSdlStreams({
622
+ * existing: { window: myWindow, renderer: myRenderer },
623
+ * fontSize: 16,
624
+ * });
625
+ *
626
+ * // When done with ink-sdl, clean up
627
+ * streams.window.close();
628
+ *
629
+ * // You can now use the window/renderer for other purposes
630
+ * // or destroy them yourself when fully done
631
+ * SDL_DestroyRenderer(myRenderer);
632
+ * SDL_DestroyWindow(myWindow);
633
+ * ```
634
+ */
635
+ existing?: ExistingSdlResources | undefined;
556
636
  }
557
637
  /**
558
638
  * SDL Window wrapper that emits events
@@ -1059,4 +1139,4 @@ declare class InputBridge {
1059
1139
  */
1060
1140
  declare const isSdlAvailable: () => boolean;
1061
1141
 
1062
- export { AnsiParser, type Color, type DrawCommand, type InkKeyEvent, InputBridge, type SDLPointer, Sdl2, SdlInputStream, type SdlKeyEvent, SdlOutputStream, type SdlStreams, type SdlStreamsOptions, SdlTtf, SdlUiRenderer, type SdlUiRendererOptions, SdlWindow, TextRenderer, createSDLRect, createSdlStreams, getSdl2, getSdlTtf, isSdl2Available, isSdlAvailable, isSdlTtfAvailable };
1142
+ export { AnsiParser, type Color, type DrawCommand, type ExistingSdlResources, type InkKeyEvent, InputBridge, type SDLPointer, Sdl2, SdlInputStream, type SdlKeyEvent, SdlOutputStream, type SdlStreams, type SdlStreamsOptions, SdlTtf, SdlUiRenderer, type SdlUiRendererOptions, SdlWindow, TextRenderer, createSDLRect, createSdlStreams, getSdl2, getSdlTtf, isSdl2Available, isSdlAvailable, isSdlTtfAvailable };
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  isSdl2Available,
16
16
  isSdlAvailable,
17
17
  isSdlTtfAvailable
18
- } from "./chunk-YE6CUIKT.js";
18
+ } from "./chunk-LP5C65TH.js";
19
19
  export {
20
20
  AnsiParser,
21
21
  InputBridge,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ink-sdl",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Render Ink terminal apps in native SDL windows",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",