# pear-electron

> Pear User-Interface Library for Electron

**Status: WIP**

## Installation

```sh
npm install pear-electron
```

## Usage

Instantiate a `pear-electron` runtime instance from a Pear Application's entrypoint JavaScript file:

```js
import Runtime from 'pear-electron'
import Bridge from 'pear-bridge'

const runtime = new Runtime()

const bridge = new Bridge()
await bridge.ready()

const pipe = runtime.start({ bridge })
Pear.teardown(() => pipe.end())
```

Call `runtime.start` to open the UI.

## Initialization API

### `new Runtime() -> runtime`

Create the runtime instances with `new Runtime()`.

### `runtime.ready()`

Prepare the runtime, runtime binaries for the runtime version may be bootstrapped peer-to-peer at this point. This only runs once per version and any prior bootstraps can be reused for subsequent versions where state hasn't changed. In a production scenario any bootstrapping would be performed in advance by the application distributable.

### `runtime.start(opts)`

Opens the UI. 

#### Options

* `bridge` - An instance of `pear-bridge`.

## User-Interface API

```js
const ui = require('pear-electron')
```

### `ui.app <Object>`

UI Application controls

### `const success = await app.focus()`

Resolves to: `<Boolean>`

Focus current view or window.

### `const success = await app.blur()`

Resolves to: `<Boolean>`

Blur current view or window.

### `const success = await app.show()`

Resolves to: `<Boolean>`

Show current view or window.

### `const success = await app.hide()`

Resolves to: `<Boolean>`

Hide current view or window.

### `const sourceId = await app.getMediaSourceId()`

Get the sourceId of the current window or view.

**References**

* [win.getMediaSourceId()](const-sourceId--await-wingetMediaSourceId)


### `const success = await app.minimize()`

Resolves to: `<Boolean>`

Minimize current window.

### `const success = await app.maximize()`

Resolves to: `<Boolean>`

Maximize current window.

### `const success = await app.restore()`

Resolves to: `<Boolean>`

Unmaximize/unminimize the current window if it is currently maximized/minimized.

### `const success = await app.close()`

Resolves to: `<Boolean>`

Closes the current view or window.


### `const isVisible = await app.isVisible()`

Resolves to: `<Boolean>`

Whether the current window or view is visible.

### `const isMaximized = await app.isMaximized()`

Resolves to: `<Boolean>`

### `const isMinimized = await app.isMinimized()`

Resolves to: `<Boolean>`

### `const found = await app.find(options <Object>)`

Resolves to: `<Found> extends <streamx.Readable>`

Find and select text, emit matches as data events.

**Options**
* text `<String>` - search term
* forward `<Boolean>` - search forward (`true`) or backward (`false`). Defaults `true`.
* matchCase `<Boolean>`  - case-sensitivity. Default `false`.

#### `await found.proceed()`

Find & select next match, emit result as stream data.

#### `await found.clear()`

Stop search and clear matching text selection. Implies destroy.

#### `await found.keep()`

Stop search and convert matching text selection to text highlight. Implies destroy.

#### `await found.activate()`

Stop search and simulate a click event on the selected match. Implies destroy.


### `ui.media <Object>`

Media interface

#### `const status = await ui.media.status.microphone()`

Resolves to: `<String>`

If access to the microphone is available, resolved value will be `'granted'`.

Any other string indicates lack of permission. Possible values are `'granted'`, `'not-determined'`, `'denied'`, `'restricted'`, `'unknown'`.

#### `const status = await ui.media.status.camera()`

Resolves to: `<String>`

If access to the camera is available, resolved value will be `'granted'`.

Any other string indicates lack of permission. Possible values are `'granted'`, `'not-determined'`, `'denied'`, `'restricted'`, `'unknown'`.

#### `const status = await ui.media.status.screen()`

Resolves to: `<String>`

If access to the screen is available, resolved value will be `'granted'`.

Any other string indicates lack of permission. Possible values are `'granted'`, `'not-determined'`, `'denied'`, `'restricted'`, `'unknown'`.

#### `const success = await ui.media.access.microphone()`

Resolves to: `<Boolean>`

Request access to the microphone. Resolves to `true` if permission is granted.

#### `const success = await ui.media.access.camera()`

Resolves to: `<Boolean>`

Request access to the camera. Resolves to `true` if permission is granted.

#### `const success = await ui.media.access.screen()`

Resolves to: `<Boolean>`

Request access to screen sharing. Resolves to `true` if permission is granted.

#### `const sources = await ui.media.desktopSources(options <Object>)`

Captures available desktop sources. Resolves to an array of objects with shape `{ id <String>, name <String>, thumbnail <NativeImage>, display_id <String>, appIcon <NativeImage> }`. The `id` is the window or screen identifier. The `name` is the window title or `'Screen <index>'` in multiscreen scenarios or else `Entire Screen`. The `display_id` identifies the screen. The thumbnail is a scaled down screen capture of the window/screen.

**Options**

* `types <Array<String>>` - Default: `['screen', 'window']`. Filter by types. Types are `'screen'` and `'window'`.
* `thumbnailSize <Object>` - Default: `{width: 150, height: 150}`. Set thumbnail scaling (pixels)
* `fetchWindowIcons <Boolean>` - Default: `false`. Populate `appIcon` with Window icons, or else `null`.

**References**

* [win.getMediaSourceId()](#const-sourceid--await-wingetmediasourceid)
* [view.getMediaSourceId()](#const-sourceid--await-viewgetmediasourceid)
* [self.getMediaSourceId()](#const-sourceid--await-selfgetmediasourceid)
* [parent.getMediaSourceId()](#const-sourceid--await-parentgetmediasourceid)
* https://www.electronjs.org/docs/latest/api/desktop-capturer#desktopcapturergetsourcesoptions
* https://www.electronjs.org/docs/latest/api/structures/desktop-capturer-source
* [`<NativeImage>`](https://www.electronjs.org/docs/latest/api/native-image)

Exits the process with the provided exit code.

### `const win = new ui.Window(entry <String>, options <Object>)`

Desktop Applications only.

Create a new `Window` instance.

**Options**

* `show <Boolean>` Default: `true` - show the window as soon as it has been opened
* `x <Integer>` - the horizontal position of left side of the window (pixels)
* `y <Integer>` - vertical window position (pixels)
* `width <Integer>` - the width of the window (pixels)
* `height <Integer>` - the height of the window (pixels)
* `animate <Boolean>` Default: `false` - animate the dimensional change. MacOS only, ignored on other OS's.
* `center <Boolean` - center the window upon opening
* `minWidth <Integer>` - window minimum width (pixels)
* `minHeight <Integer>` - window minimum height (pixels)
* `maxWidth <Integer>` - window maximum width (pixels)
* `maxHeight <Integer>` - window maximum height (pixels)
* `resizable <Boolean>` - window resizability
* `movable <Boolean>` - window movability
* `minimizable <Boolean>` - window minimizability
* `maximizable <Boolean>` - window maximizability
* `closable <Boolean>` - window closability
* `focusable <Boolean>` - window focusability
* `alwaysOnTop <Boolean>` - Set window to always be on top
* `fullscreen <Boolean>` - Set window to fullscreen upon open
* `kiosk <Boolean>` - Set window to enter kiosk mode upon open
* `autoHideMenuBar <Boolean>` - Hide menu bar unless Alt key is pressed (Linux, Windows)
* `hasShadow <Boolean>` - Set window shadow
* `opacity <Number>` - Set window opacity (0.0 - 1.0) (Windows, macOS)
* `transparent <Boolean>` - Set window transparency
* `backgroundColor <String>` Default: `'#FFF'` - window default background color. Hex, RGB, RGBA, HSL HSLA, CSS color

### `win.on[ce]('message', (...args) => { })`
### `for await (const [ ...args ] of win)`

Receive a message from the window. The received `args` array is deserialized via `JSON.parse`.

**References**

* [`win.send()`](#await-winsendargs)

### `const success = await win.open(options <Object>)`

Resolves to: `<Boolean>`

Open the window.

**Options**

* `show` Default: `true` - show the window as soon as it has been opened
* `x <Integer>` - the horizontal position of left side of the window (pixels)
* `y <Integer>` - vertical window position (pixels)
* `width <Integer>` - the width of the window (pixels)
* `height <Integer>` - the height of the window (pixels)
* `animate <Boolean>` Default: `false` - animate the dimensional change. MacOS only, ignored on other OS's.
* `center <Boolean` - center the window upon opening
* `minWidth <Integer>` - window minimum width (pixels)
* `minHeight <Integer>` - window minimum height (pixels)
* `maxWidth <Integer>` - window maximum width (pixels)
* `maxHeight <Integer>` - window maximum height (pixels)
* `resizable <Boolean>` - window resizability
* `movable <Boolean>` - window movability
* `minimizable <Boolean>` - window minimizability
* `maximizable <Boolean>` - window maximizability
* `closable <Boolean>` - window closability
* `focusable <Boolean>` - window focusability
* `alwaysOnTop <Boolean>` - Set window to always be on top
* `fullscreen <Boolean>` - Set window to fullscreen upon open
* `kiosk <Boolean>` - Set window to enter kiosk mode upon open
* `autoHideMenuBar <Boolean>` - Hide menu bar unless Alt key is pressed (Linux, Windows)
* `hasShadow <Boolean>` - Set window shadow
* `opacity <Number>` - Set window opacity (0.0 - 1.0) (Windows, macOS)
* `transparent <Boolean>` - Set window transparency
* `backgroundColor <String>` Default: `'#FFF'` - window default background color. Hex, RGB, RGBA, HSL HSLA, CSS color

### `const success = await win.close()`

Resolves to: `<Boolean>`

Close the window.

### `const success = await win.show()`

Resolves to: `<Boolean>`

Show the window.

### `const success = await win.hide()`

Resolves to: `<Boolean>`

Hide the window.

### `const success = await win.focus(options <Object>)`

Resolves to: `<Boolean>`

Focus the window.

**Options**

* `steal` Default: `true` - brings the window to the foreground and attempts to take focus, even if another application is currently active, or the window is hidden or minimized.

### `const success = await win.blur()`

Resolves to: `<Boolean>`

Blur the window.

### `const success = await win.minimize()`

Resolves to: `<Boolean>`

Minimize the window.

### `const success = await win.maximize()`

Resolves to: `<Boolean>`

Maximize the window.

### `const success = await win.restore()`

Resolves to: `<Boolean>`

Unmaximize/unminimize the window if it is currently maximized/minimized.

### `const sourceId = await win.getMediaSourceId()`

Resolves to: `<String>`

Correlates to the `id` property of objects in the array returned from [ui.media.desktopSources](#const-sources---await-appmediadesktopsources-options).

**References**

* [ui.media.desktopSources](#const-sources--await-appmediadesktopsourcesoptions-object)
* https://www.electronjs.org/docs/latest/api/browser-window#wingetmediasourceid

### `await win.send(...args)`

Send arguments to the window. They will be serialized with `JSON.stringify`.

### `const found = await win.find(options <Object>)`

Resolves to: `<Found> extends <streamx.Readable>`

Find and select text, emit matches as data events.

**Options**
* text `<String>` - search term
* forward `<Boolean>` - search forward (`true`) or backward (`false`). Defaults `true`.
* matchCase `<Boolean>`  - case-sensitivity. Default `false`.

#### `await found.proceed()`

Find & select next match, emit result as stream data.

#### `await found.clear()`

Stop search and clear matching text selection. Implies destroy.

#### `await found.keep()`

Stop search and convert matching text selection to text highlight. Implies destroy.

#### `await found.activate()`

Stop search and simulate a click event on the selected match. Implies destroy.


### `const dimensions = await win.dimensions()`

Resolves to: `{x <Integer>, y <Integer>, width <Integer>, height <Integer>} | null`.

The height, width, horizontal (`x`), vertical (`y`) position of the window relative to the screen.

All units are (pixels)

If the window is closed this will resolve to `null`.

**References**

* [await win.dimensions(options)](#await-windimensionsoptions-object)

### `await win.dimensions(options <Object>)`

```js
const win = new ui.Window('./some.html', {
  x: 10,
  y: 450,
  width: 300,
  height: 350
})

await win.open()
await new Promise((resolve) => setTimeout(resolve, 1000))

await win.dimensions({
  x: 20,
  y: 50,
  width: 550,
  height: 300,
  animate: true // only has an effect on macOS
})

```

Sets the dimensions of the window.

**Options**

* `x <Integer>` - the horizontal position of left side of the window (pixels)
* `y <Integer>` - the vertical position of the top of the window (pixels)
* `width <Integer>` - the width of the window (pixels)
* `height <Integer>` - the height of the window (pixels)
* `animate <Boolean>` Default: `false` - animate the dimensional change. MacOS only, ignored on other OS's.
* `position <String>` - may be `'center'` to set the window in the center of the screen or else `undefined`.

**References**

* [const dimensions = await win.dimensions()](#const-dimensions-await-windimensions)


### `const visible = await win.isVisible()`

Resolves to: `<Boolean>`

Whether the window is visible.

### `const minimized = await win.isMinimized()`

Resolves to: `<Boolean>`

Whether the window is minimized.

### `const maximized = await win.isMaximized()`

Resolves to: `<Boolean>`

Whether the window is maximized.

### `const closed = await win.isClosed()`

Resolves to: `<Boolean>`

Whether the window is closed.

### `const view = new ui.View(options <Object>)`

Desktop Applications only.

Create a new `View` instance. Views provide isolated content views. Frameless, chromeless windows that can be embedded inside other windows and views.

**Options**

* `x <Integer>` - the horizontal position of left side of the view (pixels)
* `y <Integer>` - vertical view position (pixels)
* `width <Integer>` - the width of the view (pixels)
* `height <Integer>` - the height of the view (pixels)
* `backgroundColor <String>` Default: `'#FFF'` - view default background color. Hex, RGB, RGBA, HSL HSLA, CSS color
* `autoresize <Object>` Default `{ width=true, height=true, vertical=false, horizontal=false }` - dimensions for the view to autoresize alongside. For example, if `width` is `true` and the view container increases/decreases in width, the view will increase/decrease in width at the same rate.

**References**

* https://www.electronjs.org/docs/latest/api/browser-view#viewsetautoresizeoptions-experimental
* https://www.electronjs.org/docs/latest/api/browser-view#viewsetbackgroundcolorcolor-experimental

### `view.on[ce]('message', (...args) => { })`
### `for await (const [ ...args ] of view)`

Receive a message from the view. The received `args` array is deserialized via `JSON.parse`.

**References**

* [`view.send()`](#await-viewsendargs)

### `const success = await view.open(options <Object>)`

Resolves to: `<Boolean>`

Open the view.

**Options**

* `x <Integer>` - the horizontal position of left side of the view (pixels)
* `y <Integer>` - vertical view position (pixels)
* `width <Integer>` - the width of the view (pixels)
* `height <Integer>` - the height of the view (pixels)
* `backgroundColor <String>` Default: `'#FFF'` - view default background color. Hex, RGB, RGBA, HSL HSLA, CSS color
* `autoresize <Object>` Default `{ width=true, height=true, vertical=false, horizontal=false }` - dimensions for the view to autoresize alongside. For example, if `width` is `true` and the view container increases/decreases in width, the view will increase/decrease in width at the same rate.

### `const success = await view.close()`

Resolves to: `<Boolean>`

Close the view.

### `const success = await view.show()`

Resolves to: `<Boolean>`

Show the view.

### `const success = await view.hide()`

Resolves to: `<Boolean>`

Hide the view.

### `const success = await view.focus()`

Resolves to: `<Boolean>`

Focus the view.

### `const success = await view.blur()`

Resolves to: `<Boolean>`

Blur the view.

### `const sourceId = await view.getMediaSourceId()`

Resolves to: `<String>`

Supplies the `id` property of objects in the array returned from [ui.media.desktopSources](#const-sources---await-appmediadesktopsources-options).

**References**

* [ui.media.desktopSources](#const-sources---await-appmediadesktopsources-options)
* https://www.electronjs.org/docs/latest/api/browser-window#wingetmediasourceid

### `await view.send(...args)`

Send arguments to the view. They will be serialized with `JSON.stringify`.

### `const found = await win.find(options <Object>)`

Resolves to: `<Found> extends <streamx.Readable>`

Find and select text, emit matches as data events.

**Options**
* text `<String>` - search term
* forward `<Boolean>` - search forward (`true`) or backward (`false`). Defaults `true`.
* matchCase `<Boolean>`  - case-sensitivity. Default `false`.

#### `await found.proceed()`

Find & select next match, emit result as stream data.

#### `await found.clear()`

Stop search and clear matching text selection. Implies destroy.

#### `await found.keep()`

Stop search and convert matching text selection to text highlight. Implies destroy.

#### `await found.activate()`

Stop search and simulate a click event on the selected match. Implies destroy.

### `const dimensions = await view.dimensions()`

Resolves to: `{x <Integer>, y <Integer>, width <Integer>, height <Integer>} | null`.

The height, width, horizontal (`x`), vertical (`y`) position of the window relative to the screen.

All units are (pixels)

If the Window is closed this will resolve to `null`.

**References**

* [await view.dimensions(options)](#await-viewdimensionsoptions-object)

### `await view.dimensions(options <Object>)`

```js
const view = new ui.View('./some.html', {
  x: 10,
  y: 450,
  width: 300,
  height: 350
})

await view.open()
await new Promise((resolve) => setTimeout(resolve, 1000))

await view.dimensions({
  x: 20,
  y: 50,
  width: 550,
  height: 300
})
```

Sets the dimensions of the view.

**Options**

* `x <Integer>` - the horizontal position of left side of the window (pixels)
* `y <Integer>` - the vertical position of the top of the window (pixels)
* `width <Integer>` - the width of the window (pixels)
* `height <Integer>` - the height of the window (pixels)


**References**

* [const dimensions = await view.dimensions()](#const-dimensions--await-viewdimensions)

### `const visible = await view.isVisible()`

Resolves to: `<Boolean>`

Whether the view is visible.

### `const closed = await view.isClosed()`

Resolves to: `<Boolean>`

Whether the view is closed.

### `const { self } = ui.Window`  `const { self } = ui.View`

> DEPRECATED use `ui.app`.

### `const { parent } = ui.Window`  `const { parent } = ui.View`

### `parent.on[ce]('message', (...args) => { })`
### `for await (const [ ...args ] of parent)`

Receive a message from the parent window or view. The received `args` array is deserialized via `JSON.parse`.

### `await parent.send(...args)`

Send arguments to the parent view or window. They will be serialized with `JSON.stringify`.


### `const success = await parent.focus()`

Resolves to: `<Boolean>`

Focus parent view or window.

### `const success = await parent.blur()`

Resolves to: `<Boolean>`

Blur parent view or window.

### `const success = await parent.show()`

Resolves to: `<Boolean>`

Show parent view or window.

### `const success = await parent.hide()`

Resolves to: `<Boolean>`

Hide parent view or window.

### `const sourceId = await parent.getMediaSourceId()`

Get the sourceId of the parent window or view.

**References**

* [win.getMediaSourceId()](#const-sourceId--await-wingetMediaSourceId)


### `const success = await parent.minimize()`

Resolves to: `<Boolean>`

Minimize parent window.

Throws a `TypeError` if `parent` is a view.

### `const success = await parent.maximize()`

Resolves to: `<Boolean>`

Maximize parent window.

Throws a `TypeError` if `parent` is a view.

### `const success = await parent.restore()`

Resolves to: `<Boolean>`

Unmaximize/unminimize the parent window if it is currently maximized/minimized.

Throws a `TypeError` if `parent` is a view.

### `const success = await parent.close()`

Resolves to: `<Boolean>`

Closes the parent view or window.

### `const isVisible = await parent.isVisible()`

Resolves to: `<Boolean>`

Whether the parent window or view is visible.

### `const isMaximized = await parent.isMaximized()`
Resolves to: `<Boolean>`

Whether the parent window is maximized. Throws a `TypeError` if `parent` is a view.


### `const isMinimized = await parent.isMinimized()`

Resolves to: `<Boolean>`

Whether the parent window is minimized. Throws a `TypeError` if `parent` is a view.

### `const found = await parent.find(options <Object>)`

Resolves to: `<Found> extends <streamx.Readable>`

Find and select text, emit matches as data events.

**Options**
* text `<String>` - search term
* forward `<Boolean>` - search forward (`true`) or backward (`false`). Defaults `true`.
* matchCase `<Boolean>`  - case-sensitivity. Default `false`.

#### `await found.proceed()`

Find & select next match, emit result as stream data.

#### `await found.clear()`

Stop search and clear matching text selection. Implies destroy.

#### `await found.keep()`

Stop search and convert matching text selection to text highlight. Implies destroy.

#### `await found.activate()`

Stop search and simulate a click event on the selected match. Implies destroy.


## Web APIs

Most [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API) will work as-is.

This section details deviations in behavior from and notable aspects of Web APIs as they relate to `pear-electron`.

### `window.open`

The [`window.open`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) Web API function will ignore all arguments except for the URL parameter.

In browsers, `window.open` opens a new browser window. The opened window belongs to the same browser from which `window.open` is called.

With `pear-electron` UI Library, `window.open` loads the URL in the **default system browser**. It does *not* create a new application window (use `Pear.Window` to create application windows).

Therefore Pear's `window.open` only supports a single URL argument. The `target` and `windowFeatures` parameters that browsers support are discarded.

### Scripts and Modules

Like browsers, there is no support for CommonJS (e.g. the `require` function as used by Node.js is not supported in Pear Applications).

Like browsers, there is support for native EcmaScript Modules (ESM). A JavaScript Script has no module capabilities. A JavaScript Module has ESM capabilities.

Use `<script type="module" src="path/to/my-file.js">` to load a JavaScript Module.

Use `<script src="path/to/my-file.js">` to load a JavaScript Script.

## Graphical User Interface Options

GUI options for an application are set in the application `package.json` `pear.gui` field.

### `width <Number>`

Window width (pixels).

### `height <Number>`

Window height (pixels).

### `x <Number>`

Horizontal window position (pixels).

### `y <Number>`

Vertical window position (pixels).

### `minWidth <Number>`

Window minimum width (pixels).

### `minHeight <Number>`

Window minimum height (pixels).

### `maxWidth <Number>`

Window maximum width (pixels).

### `maxHeight <Number>`

Window maximum height (pixels).

### `center <Boolean>` (default: `false`)

Center window.

### `resizable <Boolean>` (default: `true`)

Window resizability.

### `movable <Boolean>` (default: `true`)

Window movability.

### `minimizable <Boolean>` (default: `true`)

Window minimizability.

### `maximizable <Boolean>` (default: `true`)

Window maximizability.

### `closable <Boolean>` (default: `true`)

Window closability.

### `focusable <Boolean>` (default: `true`)

Window focusability.

### `alwaysOnTop <Boolean>` (default: `false`)

Set window to always be on top.

### `fullscreen <Boolean>` (default: `false`)

Set window to fullscreen on start.

### `kiosk <Boolean>` (default: `false`)

Set window to enter kiosk mode on start.

### `autoHideMenuBar <Boolean>` (default: `false`)

Hide menu bar unless Alt key is pressed (Linux, Windows).

### `hasShadow <Boolean>` (default: `true`)

Window shadow.

### `opacity <Number>` (default: `1`)

Set window opacity (0.0 - 1.0) (Windows, macOS).

### `transparent <Boolean>` (default: `false`)

Enable transparency. Must be set for opacity to work.

### `backgroundColor <String>` (default: "#000" non-transparent, "#00000000" transparent)

Background color (Hex, RGB, RGBA, HSL, HSLA, CSS color).

## Development

The `pear-electron` library is a Pear User Interface Runtime Library, as such `pear-electron` (and any Pear UI Lib.) is multifaceted and behaves differently depending on context.

* When loaded into a UI,  `pear-electron` is the UI API
* When loaded into non-UI (i.e app entrypoint js file), `pear-electron` is the runtime initializor
  * When there is no runtime binary on the system, `pear-electron` performs bootstrapping of the UI runtime executable, into `<pear-dir>/interfaces/pear-electron/<semver>`
* The `pear-electron` repo is also self-bootstrapping and generates the runtime drive (with `by-arch`, `prebuilds` and `boot.bundle`), which can then be staged with Pear. The pear link for the staged `pear-electron` contents in `pear-electron` `package.json` `pear.gui.runtime` field is then set, with fork and length included. This locks runtime builds for a given semver to a specific runtime drive checkout.
  * This is what `pear-electron` bootstraps from during `runtime.ready()`.


# LICENSE

Apache 2.0
