diff --git a/README.md b/README.md
index b6cde27..f580976 100644
--- a/README.md
+++ b/README.md
@@ -12,16 +12,15 @@
[](https://github.com/kusti8/proton-native/blob/master/LICENSE)
[](https://gitter.im/Proton-Native)
-
Create native desktop applications through a React syntax, on all platforms
## Features
-- Same syntax as React Native
-- Works with existing React libraries such as Redux
-- Cross platform
-- Native components. No more Electron
-- Compatible with all normal Node.js packages
+* Same syntax as React Native
+* Works with existing React libraries such as Redux
+* Cross platform
+* Native components. No more Electron
+* Compatible with all normal Node.js packages
## Images
@@ -39,10 +38,10 @@ Examples can be found in `examples/`.
All contributions are welcome. Just make a PR. Below is a list of general improvements that need to be added that I would love help with:
-- [ ] More examples and improve documentation
-- [ ] Add UiArea
-- [ ] Clean up code
-- [ ] Make packaging easier
-- [x] Make creating a new app easier (something similar to `create-react-app`): https://github.com/albe-rosado/create-proton-app
+* [ ] More examples and improve documentation
+* [x] Add UiArea
+* [ ] Clean up code
+* [ ] Make packaging easier
+* [x] Make creating a new app easier (something similar to `create-react-app`): https://github.com/albe-rosado/create-proton-app
Accelerated by KeyCDN
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 3d36def..10b9159 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -5,6 +5,15 @@
* Components
* [App](component_APIs/app.md)
+ * [Area](component_APIs/area.md)
+ * [Props](component_APIs/area_props.md)
+ * [Area.Group](component_APIs/area_group.md)
+ * [Area.Rectangle](component_APIs/area_rectangle.md)
+ * [Area.Line](component_APIs/area_line.md)
+ * [Area.Circle](component_APIs/area_circle.md)
+ * [Area.Arc](component_APIs/area_bezier.md)
+ * [Area.Bezier](component_APIs/area_bezier.md)
+ * [Area.Path](component_APIs/area_path.md)
* [Box](component_APIs/box.md)
* [Button](component_APIs/button.md)
* [Checkbox](component_APIs/checkbox.md)
diff --git a/docs/component_APIs/area.md b/docs/component_APIs/area.md
new file mode 100644
index 0000000..0164909
--- /dev/null
+++ b/docs/component_APIs/area.md
@@ -0,0 +1,108 @@
+# Area
+
+A component onto which area components like rectangles or circles can be drawn.
+
+Some props can be applied to all area components (**including Area itself** and children): see [Area Props](area_props.md).
+
+```jsx
+import React, { Component } from 'react';
+
+import { render, Window, App, Area, Rectangle } from 'proton-native';
+
+class Example extends Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
+
+render();
+```
+
+## Props
+
+* [visible](#visible)
+* [onMouseMove](#onMouseMove)
+* [onMouseUp](#onMouseUp)
+* [onMouseDown](#onMouseDown)
+* [onMouseEnter](#onMouseEnter)
+* [onMouseLeave](#onMouseLeave)
+* [onKeyUp](#onKeyUp)
+* [onKeyDown](#onKeyDown)
+
+## Reference
+
+### visible
+
+Whether the area can be seen.
+
+| **Type** | **Required** | **Default** |
+| -------- | ------------ | ----------- |
+| bool | No | true |
+
+### onMouseMove
+
+Called when the mouse is moved over the area
+
+| **Type** | **Required** |
+| ----------------------------------------------------------------------------------------- | ------------ |
+| function({x: number, y: number, width: number, height: number, buttons: Array\}) | No |
+
+### onMouseUp
+
+**Not working at the moment**
+
+Called when releasing a mouse button over the area.
+
+| **Type** | **Required** |
+| ------------------------------------------------------------------------------- | ------------ |
+| function({x: number, y: number, width: number, height: number, button: number}) | No |
+
+### onMouseDown
+
+Called when pressing a mouse button over the area.
+
+| **Type** | **Required** |
+| ------------------------------------------------------------------------------- | ------------ |
+| function({x: number, y: number, width: number, height: number, button: number}) | No |
+
+### onMouseEnter
+
+Called when the mouse enters the area.
+
+| **Type** | **Required** |
+| ---------- | ------------ |
+| function() | No |
+
+### onMouseLeave
+
+Called when the mouse leaves the area.
+
+| **Type** | **Required** |
+| ---------- | ------------ |
+| function() | No |
+
+### onKeyUp
+
+Called when pressing a key.
+Return `true` to signal that this event got handled (always returning true will disable any menu accelerators).
+
+| **Type** | **Required** |
+| ------------------------------------------------------------------------------- | ------------ |
+| function({key: string, extKey: number, modifierKey: number, modifiers: number}) | No |
+
+### onKeyDown
+
+Called when releasing a key.
+Return `true` to signal that this event got handled (always returning true will disable any menu accelerators).
+
+| **Type** | **Required** |
+| ------------------------------------------------------------------------------- | ------------ |
+| function({key: string, extKey: number, modifierKey: number, modifiers: number}) | No |
diff --git a/docs/component_APIs/area_arc.md b/docs/component_APIs/area_arc.md
new file mode 100644
index 0000000..4fb212f
--- /dev/null
+++ b/docs/component_APIs/area_arc.md
@@ -0,0 +1,83 @@
+# Area.Arc
+
+A circular arc to be displayed in an [Area](area.md) component.
+
+```jsx
+import React, { Component } from 'react';
+
+import { render, Window, App, Area } from 'proton-native';
+
+class Example extends Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
+
+render();
+```
+
+## Props
+
+* [x](#x)
+* [y](#y)
+* [r](#r)
+* [start](#start)
+* [sweep](#sweep)
+* (All props listed in [Area Props](area_props.md))
+
+## Reference
+
+### x
+
+The x coordinate of the center of the arc.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### y
+
+The y coordinate of the center of the arc.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### r
+
+The arc's radius. Percentage values use the Area's width.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### start
+
+The start angle of the arc in degrees. Value increases clockwise with `0` meaning the rightmost point ("east") of the imaginary circle.
+
+| **Type** | **Required** | **Default** |
+| ------------------------- | ------------ | ----------- |
+| number \| string (number) | false | 0 |
+
+### sweep
+
+The sweep angle of the arc in degrees. Value increases clockwise.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
diff --git a/docs/component_APIs/area_bezier.md b/docs/component_APIs/area_bezier.md
new file mode 100644
index 0000000..9a2f068
--- /dev/null
+++ b/docs/component_APIs/area_bezier.md
@@ -0,0 +1,114 @@
+# Area.Bezier
+
+A Bezier curve to be displayed in an [Area](area.md) component.
+
+```jsx
+import React, { Component } from 'react';
+
+import { render, Window, App, Area } from 'proton-native';
+
+class Example extends Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
+
+render();
+```
+
+## Props
+
+* [x1](#x1)
+* [y1](#y1)
+* [cx1](#cx1)
+* [cy1](#cy1)
+* [x2](#x2)
+* [y2](#y2)
+* [cx2](#cx2)
+* [cy2](#cy2)
+* (All props listed in [Area Props](area_props.md))
+
+## Reference
+
+### x1
+
+The x coordinate of the curve's start point.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### y1
+
+The y coordinate of the curve's start point.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### cx1
+
+The x coordinate of the curve's control point at the start.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### cy1
+
+The y coordinate of the curve's control point at the start.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### x2
+
+The x coordinate of the curve's end point.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### y2
+
+The y coordinate of the curve's end point.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### cx2
+
+The x coordinate of the curve's control point at the end.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### cy2
+
+The y coordinate of the curve's control point at the end.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
diff --git a/docs/component_APIs/area_circle.md b/docs/component_APIs/area_circle.md
new file mode 100644
index 0000000..d1e33b8
--- /dev/null
+++ b/docs/component_APIs/area_circle.md
@@ -0,0 +1,58 @@
+# Area.Circle
+
+A circle to be displayed in an [Area](area.md) component.
+
+```jsx
+import React, { Component } from 'react';
+
+import { render, Window, App, Area } from 'proton-native';
+
+class Example extends Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
+
+render();
+```
+
+## Props
+
+* [x](#x)
+* [y](#y)
+* [r](#r)
+* (All props listed in [Area Props](area_props.md))
+
+## Reference
+
+### x
+
+The x coordinate of the center of the cirle.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### y
+
+The y coordinate of the center of the cirle.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### r
+
+The circle's radius. Percentage values use the Area's width.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
diff --git a/docs/component_APIs/area_group.md b/docs/component_APIs/area_group.md
new file mode 100644
index 0000000..d908335
--- /dev/null
+++ b/docs/component_APIs/area_group.md
@@ -0,0 +1,44 @@
+# Area.Group
+
+A component to apply props to all it's children in an [Area](area.md) component.
+
+To be able to use percentage values in transforms, the props `width` and `height` need to be specified (they have no graphical effect).
+
+```jsx
+import React, { Component } from 'react';
+
+import { render, Window, App, Area } from 'proton-native';
+
+class Example extends Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+render();
+```
+
+## Props
+
+* All props listed in [Area Props](area_props.md)
diff --git a/docs/component_APIs/area_line.md b/docs/component_APIs/area_line.md
new file mode 100644
index 0000000..8f3ed1b
--- /dev/null
+++ b/docs/component_APIs/area_line.md
@@ -0,0 +1,67 @@
+# Area.Line
+
+A straigt line to be displayed in an [Area](area.md) component.
+
+```jsx
+import React, { Component } from 'react';
+
+import { render, Window, App, Area } from 'proton-native';
+
+class Example extends Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
+
+render();
+```
+
+## Props
+
+* [x1](#x1)
+* [y1](#y1)
+* [x2](#x2)
+* [y2](#y2)
+* (All props listed in [Area Props](area_props.md))
+
+## Reference
+
+### x1
+
+The x coordinate of the line's start point.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### y1
+
+The y coordinate of the line's start point.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### x2
+
+The x coordinate of the line's end point.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### y2
+
+The y coordinate of the line's end point.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
diff --git a/docs/component_APIs/area_path.md b/docs/component_APIs/area_path.md
new file mode 100644
index 0000000..21745ad
--- /dev/null
+++ b/docs/component_APIs/area_path.md
@@ -0,0 +1,61 @@
+# Area.Path
+
+A component describing a path to be displayed in an [Area](area.md) component.
+
+To be able to use percentage values in transforms, the props `width` and `height` need to be specified (they have no graphical effect).
+
+```jsx
+import React, { Component } from 'react';
+
+import { render, Window, App, Area } from 'proton-native';
+
+class Example extends Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+render();
+```
+
+## Props
+
+* [d](#d)
+* [fillMode](#fillmode)
+* (All props listed in [Area Props](area_props.md))
+
+## Reference
+
+### d
+
+A string describing the path (uses SVG's path syntax, explanation [here](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths)).
+
+A warning is displayed whan an unimplemented shaped are used (Quadratic Beziers and Arcs).
+
+| **Type** | **Required** |
+| -------- | ------------ |
+| string | true |
+
+### fillMode
+
+Sets the methods how to determine wheter to fill a path. Explanation [here](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule).
+
+| **Type** | **Required** |
+| -------------------------- | ------------ |
+| enum('nonzero', 'evenodd') | true |
diff --git a/docs/component_APIs/area_props.md b/docs/component_APIs/area_props.md
new file mode 100644
index 0000000..9263076
--- /dev/null
+++ b/docs/component_APIs/area_props.md
@@ -0,0 +1,175 @@
+# Area Props
+
+Some props can be applied to Area itself and all Area components (for those the normal [universal props](universal_props.md) don't work). They get inherited to all corresponding children. The [AreaGroup](area_group.md) component can be used to apply these to multiple children.
+
+All color properties use the CSS color syntax. Examples: "red", "#070707", "#222", "rgba(255, 255, 0, 1)", "hsl(0, 100%, 50%)".
+
+* [fill](#fill)
+* [fillOpacity](#fillopacity)
+* [stroke](#stroke)
+* [strokeOpacity](#strokeopacity)
+* [strokeWidth](#strokewidth)
+* [strokeLinecap](#strokelinecap)
+* [strokeLinejoin](#strokelinejoin)
+* [strokeMiterlimit](#strokemiterlimit)
+* [transform](#transform)
+ * [rotate](#rotate)
+ * [translate](#translate)
+ * [scale](#scale)
+ * [skew](#skew)
+ * [matrix](#matrix)
+
+## fill
+
+The fill color for the component.
+
+| **Type** | **Required** | **Default** |
+| -------------- | ------------ | ----------- |
+| string (Color) | false | 'none' |
+
+## fillOpacity
+
+The opacity of the fill (between 0 and 1). Gets multiplied with the fill colors alpha value.
+
+| **Type** | **Required** | **Default** |
+| ------------------------- | ------------ | ----------- |
+| number \| string (number) | false | 1 |
+
+## stroke
+
+The stroke (line) color for the component.
+
+| **Type** | **Required** | **Default** |
+| -------------- | ------------ | ----------- |
+| string (Color) | false | 'none' |
+
+## strokeOpacity
+
+The opacity of the stroke (between 0 and 1). Gets multiplied with the stroke colors alpha value.
+
+| **Type** | **Required** | **Default** |
+| ------------------------- | ------------ | ----------- |
+| number \| string (number) | false | 1 |
+
+## strokeWidth
+
+| **Type** | **Required** | **Default** |
+| ------------------------- | ------------ | ----------- |
+| number \| string (number) | false | 1 |
+
+## strokeLinecap
+
+| **Type** | **Required** | **Default** |
+| ------------------------------- | ------------ | ----------- |
+| enum('flat', 'round', 'square') | false | 'flat' |
+
+## strokeLinejoin
+
+| **Type** | **Required** | **Default** |
+| ------------------------------- | ------------ | ----------- |
+| enum('miter', 'round', 'bevel') | false | 'miter' |
+
+## strokeMiterlimit
+
+How far to extend the stroke at a sharp corner when using `strokeLinejoin='miter'`, see [here](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit) for a more detailed explanation.
+
+| **Type** | **Required** | **Default** |
+| ------------------------- | ------------ | ----------- |
+| number \| string (number) | false | 10 |
+
+## transform
+
+List of transformations to apply to the component (are quite similar to SVG transformations). Example for multiple transformations: `transform="translate(100, 100) rotate(90)"`.
+All x and y coordinates specified in a transformation are relative _to the component itself_, meaning that `translate(-50%, 0)` will translate the component by 50% of it's own width to left.
+
+| **Type** | **Required** | **Default** |
+| -------- | ------------ | ----------- |
+| string | false | '' |
+
+### rotate
+
+Rotates the component `a` degrees around the center `(xCenter, yCenter)`.
+
+Syntax:
+
+```
+rotate(a)
+rotate(a, xCenter, yCenter)
+```
+
+| **Variable** | **Required** | **Default** |
+| ------------ | ------------ | ----------- |
+| xCenter | false | 50% |
+| yCenter | false | 50% |
+
+### translate
+
+Moves the component `x` to the right and `t` down.
+
+Syntax:
+
+```
+translate(x)
+translate(x, y)
+```
+
+| **Variable** | **Required** | **Default** |
+| ------------ | ------------ | ----------- |
+| x | true | |
+| y | false | x |
+
+### scale
+
+Scales the component by `x` in the horizontal axis and by `y` in the vertical axis. The scale center is specified by `(xCenter, yCenter)`.
+
+Syntax:
+
+```
+scale(x)
+scale(x, y)
+scale(x, xCenter, yCenter)
+scale(x, y, xCenter, yCenter)
+```
+
+| **Variable** | **Required** | **Default** |
+| ------------ | ------------ | ----------- |
+| x | true | 50% |
+| y | false | y |
+| xCenter | false | 50% |
+| yCenter | false | 50% |
+
+### skew
+
+Skews the component by `x` degrees in the horizontal axis and by `y` degrees in the vertical axis. The center is specified by `(xCenter, yCenter)`.
+
+Syntax:
+
+```
+skew(x, y)
+skew(x, y, xCenter, yCenter)
+```
+
+| **Variable** | **Required** | **Default** |
+| ------------ | ------------ | ----------- |
+| x | true | |
+| y | true | |
+| xCenter | false | 50% |
+| yCenter | false | 50% |
+
+### matrix
+
+Applies a matrix transformation. (Overwrites all other transformations.)
+
+Syntax:
+
+```
+matrix(a, b, c, d, e, f)
+```
+
+Applies the matrix:
+
+```
+/ a b 0 \
+| c d 0 |
+\ e f 1 /
+```
diff --git a/docs/component_APIs/area_rectangle.md b/docs/component_APIs/area_rectangle.md
new file mode 100644
index 0000000..cadae0b
--- /dev/null
+++ b/docs/component_APIs/area_rectangle.md
@@ -0,0 +1,73 @@
+# Area.Rectangle
+
+A rectangle to be displayed in an [Area](area.md) component.
+
+```jsx
+import React, { Component } from 'react';
+
+import { render, Window, App, Area } from 'proton-native';
+
+class Example extends Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
+
+render();
+```
+
+## Props
+
+* [x](#x)
+* [y](#y)
+* [width](#width)
+* [height](#height)
+* (All props listed in [Area Props](area_props.md))
+
+## Reference
+
+### x
+
+The x coordinate of the rectangles top left corner.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### y
+
+The y coordinate of the rectangles top left corner.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### width
+
+The width of the rectangle.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
+
+### height
+
+The height of the rectangle.
+
+| **Type** | **Required** |
+| ------------------------- | ------------ |
+| number \| string (number) | true |
diff --git a/examples/area.js b/examples/area.js
new file mode 100644
index 0000000..4089993
--- /dev/null
+++ b/examples/area.js
@@ -0,0 +1,180 @@
+import React, { Component } from 'react';
+import { render, Window, App, Box, Menu, Button, Slider, Area } from '../src/';
+
+class Example extends Component {
+ state = { bool: false, val: 40, dragging: false, pos: { x: 50, y: 220 } };
+
+ render() {
+ return (
+
+
+
+ {
+ // console.log('up', e.key);
+ // return true;
+ // }}
+ // onKeyDown={e => {
+ // console.log('down', e.key);
+ // return true;
+ // }}
+ // onMouseEnter={() => console.log('enter')}
+ // onMouseLeave={() => console.log('leave')}
+ onMouseMove={e => {
+ if (e.buttons.includes(1)) {
+ if (this.state.dragging)
+ this.setState({ pos: { x: e.x, y: e.y } });
+ }
+ }}
+ onMouseUp={e => {
+ this.setState({ dragging: false });
+ }}
+ onMouseDown={e => {
+ if (
+ Math.sqrt(
+ Math.pow(this.state.pos.x - e.x, 2) +
+ Math.pow(this.state.pos.y - e.y, 2)
+ ) < 40
+ ) {
+ this.setState({ dragging: true });
+ }
+ }}
+ strokeWidth="4"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ this.setState({ val: v })}
+ />
+
+
+
+ );
+ }
+}
+
+render();
diff --git a/package-lock.json b/package-lock.json
index d88cab1..59f6ce6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "proton-native",
- "version": "1.0.16",
+ "version": "1.0.17",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2775,6 +2775,7 @@
"ajv": {
"version": "4.11.8",
"bundled": true,
+ "dev": true,
"optional": true,
"requires": {
"co": "4.6.0",
@@ -2944,6 +2945,7 @@
"debug": {
"version": "2.6.8",
"bundled": true,
+ "dev": true,
"optional": true,
"requires": {
"ms": "2.0.0"
@@ -3001,6 +3003,7 @@
"form-data": {
"version": "2.1.4",
"bundled": true,
+ "dev": true,
"optional": true,
"requires": {
"asynckit": "0.4.0",
@@ -3095,6 +3098,7 @@
"har-validator": {
"version": "4.2.1",
"bundled": true,
+ "dev": true,
"optional": true,
"requires": {
"ajv": "4.11.8",
@@ -3110,6 +3114,7 @@
"hawk": {
"version": "3.1.3",
"bundled": true,
+ "dev": true,
"requires": {
"boom": "2.10.1",
"cryptiles": "2.0.5",
@@ -3156,6 +3161,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
+ "dev": true,
"requires": {
"number-is-nan": "1.0.1"
}
@@ -3234,17 +3240,20 @@
"assert-plus": {
"version": "1.0.0",
"bundled": true,
+ "dev": true,
"optional": true
}
}
},
"mime-db": {
"version": "1.27.0",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"mime-types": {
"version": "2.1.15",
"bundled": true,
+ "dev": true,
"requires": {
"mime-db": "1.27.0"
}
@@ -3252,17 +3261,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
+ "dev": true,
"requires": {
"brace-expansion": "1.1.7"
}
},
"minimist": {
"version": "0.0.8",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"mkdirp": {
"version": "0.5.1",
"bundled": true,
+ "dev": true,
"requires": {
"minimist": "0.0.8"
}
@@ -3270,6 +3282,7 @@
"ms": {
"version": "2.0.0",
"bundled": true,
+ "dev": true,
"optional": true
},
"node-pre-gyp": {
@@ -3294,6 +3307,7 @@
"nopt": {
"version": "4.0.1",
"bundled": true,
+ "dev": true,
"optional": true,
"requires": {
"abbrev": "1.1.0",
@@ -3314,16 +3328,19 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"oauth-sign": {
"version": "0.8.2",
"bundled": true,
+ "dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
"bundled": true,
+ "dev": true,
"optional": true
},
"once": {
@@ -3337,16 +3354,19 @@
"os-homedir": {
"version": "1.0.2",
"bundled": true,
+ "dev": true,
"optional": true
},
"os-tmpdir": {
"version": "1.0.2",
"bundled": true,
+ "dev": true,
"optional": true
},
"osenv": {
"version": "0.1.4",
"bundled": true,
+ "dev": true,
"optional": true,
"requires": {
"os-homedir": "1.0.2",
@@ -3355,30 +3375,36 @@
},
"path-is-absolute": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"performance-now": {
"version": "0.2.0",
"bundled": true,
+ "dev": true,
"optional": true
},
"process-nextick-args": {
"version": "1.0.7",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"punycode": {
"version": "1.4.1",
"bundled": true,
+ "dev": true,
"optional": true
},
"qs": {
"version": "6.4.0",
"bundled": true,
+ "dev": true,
"optional": true
},
"rc": {
"version": "1.2.1",
"bundled": true,
+ "dev": true,
"optional": true,
"requires": {
"deep-extend": "0.4.2",
@@ -3390,6 +3416,7 @@
"minimist": {
"version": "1.2.0",
"bundled": true,
+ "dev": true,
"optional": true
}
}
@@ -3448,26 +3475,31 @@
},
"safe-buffer": {
"version": "5.0.1",
- "bundled": true
+ "bundled": true,
+ "dev": true
},
"semver": {
"version": "5.3.0",
"bundled": true,
+ "dev": true,
"optional": true
},
"set-blocking": {
"version": "2.0.0",
"bundled": true,
+ "dev": true,
"optional": true
},
"signal-exit": {
"version": "3.0.2",
"bundled": true,
+ "dev": true,
"optional": true
},
"sntp": {
"version": "1.0.9",
"bundled": true,
+ "dev": true,
"requires": {
"hoek": "2.16.3"
}
@@ -3492,6 +3524,7 @@
"assert-plus": {
"version": "1.0.0",
"bundled": true,
+ "dev": true,
"optional": true
}
}
@@ -3499,6 +3532,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
+ "dev": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
@@ -3508,6 +3542,7 @@
"string_decoder": {
"version": "1.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"safe-buffer": "5.0.1"
}
@@ -3515,11 +3550,13 @@
"stringstream": {
"version": "0.0.5",
"bundled": true,
+ "dev": true,
"optional": true
},
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
+ "dev": true,
"requires": {
"ansi-regex": "2.1.1"
}
@@ -3527,6 +3564,7 @@
"strip-json-comments": {
"version": "2.0.1",
"bundled": true,
+ "dev": true,
"optional": true
},
"tar": {
@@ -3558,6 +3596,7 @@
"tough-cookie": {
"version": "2.3.2",
"bundled": true,
+ "dev": true,
"optional": true,
"requires": {
"punycode": "1.4.1"
@@ -7935,6 +7974,12 @@
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
},
+ "svg-path-parser": {
+ "version": "1.1.0",
+ "resolved":
+ "https://registry.npmjs.org/svg-path-parser/-/svg-path-parser-1.1.0.tgz",
+ "integrity": "sha1-4WtLOd8NKw056DR9t5/doUU6YEY="
+ },
"symbol-observable": {
"version": "0.2.4",
"resolved":
diff --git a/package.json b/package.json
index 3f5ffd9..8b76b97 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,8 @@
"prop-types": "^15.6.1",
"react": "^16.3.0",
"react-dom": "^16.3.0",
- "react-reconciler": "^0.7.0"
+ "react-reconciler": "^0.7.0",
+ "svg-path-parser": "^1.1.0"
},
"devDependencies": {
"babel-cli": "^6.26.0",
diff --git a/src/components/Area.js b/src/components/Area.js
new file mode 100644
index 0000000..71916f5
--- /dev/null
+++ b/src/components/Area.js
@@ -0,0 +1,694 @@
+import DesktopComponent, {
+ universalPropTypes,
+ universalDefaultProps,
+} from './DesktopComponent';
+import { Component } from 'react';
+import libui from 'libui-node';
+import PropTypes from 'prop-types';
+import Color from 'color';
+import parseSVG from 'svg-path-parser';
+
+class Area extends DesktopComponent {
+ constructor(root, props) {
+ super(root, props);
+ this.root = root;
+ this.props = { ...props };
+ this.setDefaults(props);
+
+ this.element = new libui.UiArea(
+ (area, p) => {
+ for (let i = 0; i < this.children.length; i += 1) {
+ if (typeof this.children[i] === 'object') {
+ this.children[i].render(this, area, p);
+ }
+ }
+ },
+ (area, evt) => {
+ const down = evt.getDown();
+ const up = 0; //evt.getUp();
+ if (up) {
+ this.props.onMouseUp({
+ x: evt.getX(),
+ y: evt.getY(),
+ width: evt.getAreaWidth(),
+ height: evt.getAreaHeight(),
+ button: up,
+ });
+ } else if (down) {
+ this.props.onMouseDown({
+ x: evt.getX(),
+ y: evt.getY(),
+ width: evt.getAreaWidth(),
+ height: evt.getAreaHeight(),
+ button: down,
+ });
+ } else {
+ const buttons = [];
+ const held = evt.getHeld1To64();
+ if (held > 0) {
+ for (let i = 0; i <= 6; i++) {
+ if (held & Math.pow(2, i)) buttons.push(i + 1);
+ if (!(held >> (i + 1))) break;
+ }
+ }
+ this.props.onMouseMove({
+ x: evt.getX(),
+ y: evt.getY(),
+ width: evt.getAreaWidth(),
+ height: evt.getAreaHeight(),
+ buttons,
+ });
+ }
+ },
+ (area, inOut) => {
+ if (inOut === 0) {
+ this.props.onMouseEnter();
+ } else {
+ this.props.onMouseLeave();
+ }
+ },
+ function dragBroken() {},
+ (area, event) => {
+ if (event.getUp()) {
+ return this.props.onKeyUp({
+ key: event.getKey(),
+ extKey: event.getExtKey(),
+ modifierKey: event.getModifier(),
+ modifiers: event.getModifiers(),
+ });
+ } else {
+ return this.props.onKeyDown({
+ key: event.getKey(),
+ extKey: event.getExtKey(),
+ modifierKey: event.getModifier(),
+ modifiers: event.getModifiers(),
+ });
+ }
+ }
+ );
+ }
+
+ getArea() {
+ return this.element;
+ }
+
+ // to prevent TypeError: Cannot read property 'undefined' of undefined
+ // because onMouseMove, ... shouldn't be handled by DesktopComponent
+ update(oldProps, newProps) {}
+
+ render(parent) {
+ this.addParent(parent);
+ }
+}
+
+Area.PropTypes = {
+ ...universalPropTypes,
+ onMouseMove: PropTypes.func,
+ onMouseUp: PropTypes.func,
+ onMouseDown: PropTypes.func,
+ onMouseEnter: PropTypes.func,
+ onMouseLeave: PropTypes.func,
+ onKeyUp: PropTypes.func,
+ onKeyDown: PropTypes.func,
+ children: PropTypes.oneOfType([
+ PropTypes.element,
+ PropTypes.arrayOf(PropTypes.element),
+ ]),
+};
+
+Area.defaultProps = {
+ ...universalDefaultProps,
+ onMouseMove: e => {},
+ onMouseUp: e => {},
+ onMouseDown: e => {},
+ onMouseEnter: area => {},
+ onMouseLeave: area => {},
+ onKeyUp: (area, event) => {},
+ onKeyDown: (area, event) => {},
+};
+
+function fallback(...vals) {
+ let func = a => Number(a);
+ if (typeof vals[vals.length - 1] === 'function') {
+ func = vals.pop();
+ }
+
+ for (let v of vals) {
+ if (typeof v !== 'undefined') {
+ return func(v);
+ }
+ }
+}
+
+function createBrush(color, alpha) {
+ const brush = new libui.DrawBrush();
+ brush.color = new libui.Color(
+ color.red() / 255,
+ color.green() / 255,
+ color.blue() / 255,
+ color.alpha() * alpha
+ );
+ brush.type = 0 /*uiDrawBrushTypeSolid*/;
+
+ return brush;
+}
+
+class AreaComponent {
+ constructor(root, props) {
+ this.root = root;
+ this.props = { ...props };
+ this.setDefaults(props);
+ }
+
+ setDefaults(props) {
+ for (let prop in this.constructor.defaultProps) {
+ if (!(prop in props) || typeof props[prop] === 'undefined') {
+ // children can exist, but be undefined
+ this.props[prop] = this.constructor.defaultProps[prop];
+ }
+ }
+ PropTypes.checkPropTypes(
+ this.constructor.PropTypes,
+ this.props,
+ 'prop',
+ this.constructor.name
+ );
+ }
+
+ getArea() {
+ return this.parent.getArea();
+ }
+
+ update(oldProps, newProps) {
+ this.props = { ...this.props, ...newProps };
+ if (this.parent) this.getArea().queueRedrawAll();
+ }
+
+ getWidth(p) {
+ return this.props.width ? this.parseParent(this.props.width, p) : 0;
+ }
+
+ getHeight(p) {
+ return this.props.width ? this.parseParent(this.props.height, p, true) : 0;
+ }
+
+ // parse numbers (especially percentages with respect to the parent)
+ parseParent(val, p, y = false) {
+ if (typeof val === 'string') {
+ let num = Number(val);
+ if (num == val) {
+ return num;
+ } else if (val.slice(-1) == '%') {
+ let num = Number(val.slice(0, -1));
+ return num / 100 * (y ? p.getAreaHeight() : p.getAreaWidth());
+ }
+ } else if (typeof val === 'number') {
+ return val;
+ }
+ }
+
+ // parse numbers (especially percentages with respect to itself)
+ parseSelf(val, p, y = false) {
+ if (typeof val === 'string') {
+ let num = Number(val);
+ if (num == val) {
+ return num;
+ } else if (val.slice(-1) == '%') {
+ let num = Number(val.slice(0, -1));
+ return num / 100 * (y ? this.getHeight(p) : this.getWidth(p));
+ }
+ } else if (typeof val === 'number') {
+ return val;
+ }
+ }
+
+ // translates coordinates relative to this component into the area coordinate system
+ selfToParent(xx, yy, p) {
+ // get top left corner
+ let x = 0;
+ let y = 0;
+ if (this.props.x) {
+ x = this.parseParent(this.props.x, p);
+ } else if (this.props.x1 && this.props.x2) {
+ const realX1 = this.parseParent(this.props.x1, p);
+ const realX2 = this.parseParent(this.props.x2, p);
+ x = realX1 < realX2 ? realX1 : realX2;
+ }
+ if (this.props.y) {
+ y = this.parseParent(this.props.y, p, true);
+ } else if (this.props.y1 && this.props.y2) {
+ const realY1 = this.parseParent(this.props.y1, p, true);
+ const realY2 = this.parseParent(this.props.y2, p, true);
+ y = realY1 < realY2 ? realY1 : realY2;
+ }
+
+ return {
+ x: x + this.parseSelf(xx, p),
+ y: y + this.parseSelf(yy, p, true),
+ };
+ }
+
+ render(parent, area, p, props) {
+ this.parent = parent;
+ const { children, ...appendProps } = this.props;
+ props = { ...props, ...appendProps };
+
+ if (this.props.transform) {
+ p.getContext().save();
+
+ const mat = new libui.UiDrawMatrix();
+ mat.setIdentity();
+
+ for (const transform of this.props.transform.match(/\w+\([^)]+\)/g)) {
+ // rotate(deg [,x, y])
+ // default x: 50%, y: 50%
+ const rotate = transform.match(
+ /rotate\s*\(\s*([-0-9.]+)(?:\s*,\s*([-0-9.%]+)\s*,\s*([-0-9.%]+))?\s*\)/
+ );
+ if (rotate) {
+ const xy = this.selfToParent(
+ fallback(rotate[2], '50%', v => v),
+ fallback(rotate[3], '50%', v => v),
+ p
+ );
+ const rad = Number(rotate[1]) * (Math.PI / 180);
+ mat.rotate(xy.x, xy.y, rad);
+ }
+
+ // translate(x [y])
+ // default y: x
+ const translate = transform.match(
+ /translate\s*\(\s*([-0-9.%]+)(?:\s*,\s*([-0-9.%]+))?\s*\)/
+ );
+ if (translate) {
+ mat.translate(
+ this.parseSelf(translate[1], p),
+ fallback(translate[2], translate[1], v =>
+ this.parseSelf(v, p, true)
+ )
+ );
+ }
+
+ // 1: scale(x)
+ // 2: scale(x, y)
+ // 3: scale(x, xCenter, yCenter)
+ // 4: scale(x, y, xCenter, yCenter)
+ // default y: x, xCenter=yCenter: 50%
+ const scale = transform.match(
+ /scale\s*\(([-0-9.]+)(?:(?:\s*,\s*([-0-9.]+))?(?:\s*,\s*([-0-9.%]+)\s*,\s*([-0-9.%]+))?)?\)/
+ );
+ if (scale) {
+ const xy = this.selfToParent(
+ fallback(scale[3], '50%', v => v),
+ fallback(scale[4], '50%', v => v),
+ p
+ );
+ if (process.platform === 'win32') {
+ mat.scale(xy.x, xy.y, scale[1], fallback(scale[2], scale[1]));
+ } else {
+ // https://github.com/andlabs/libui/issues/331:
+ mat.translate(xy.x, xy.y);
+ mat.scale(0, 0, scale[1], fallback(scale[2], scale[1]));
+ mat.translate(-xy.x, -xy.y);
+ }
+ }
+
+ // skew(a, b [,x, y])
+ // a, b: x/y angle
+ // default x=y: 50%
+ const skew = transform.match(
+ /skew\s*\(\s*([-0-9.]+)\s*,\s*([-0-9.]+)(?:,\s*([-0-9.%]+),\s*([-0-9.%]+))?\)/
+ );
+ if (skew) {
+ const rad1 = Number(skew[1]) * (Math.PI / 180);
+ const rad2 = Number(skew[2]) * (Math.PI / 180);
+ mat.skew(
+ fallback(skew[2], '50%', v => this.parseSelf(v, p)),
+ fallback(skew[3], '50%', v => this.parseSelf(v, p, true)),
+ rad1,
+ rad2
+ );
+ }
+
+ // matrix(a, b, c, d, e, f, g)
+ const matrix = transform.match(
+ /matrix\s*\(\s*([-0-9.]+)\s*,\s*([-0-9.]+)\s*,\s*([-0-9.]+)\s*,\s*([-0-9.]+)\s*,\s*([-0-9.]+)\s*,\s*([-0-9.]+)\s*\)/
+ );
+ if (matrix) {
+ mat.setM11(matrix[1]);
+ mat.setM12(matrix[2]);
+ mat.setM21(matrix[3]);
+ mat.setM22(matrix[4]);
+ mat.setM31(matrix[5]);
+ mat.setM32(matrix[6]);
+ }
+ }
+
+ p.getContext().transform(mat);
+ }
+
+ const path = this.draw(area, p, props);
+
+ if (path) {
+ const fillBrush =
+ props.fill &&
+ props.fill != 'none' &&
+ createBrush(Color(props.fill), Number(props.fillOpacity));
+ const strokeBrush =
+ props.stroke &&
+ props.stroke != 'none' &&
+ createBrush(Color(props.stroke), Number(props.strokeOpacity));
+
+ if (strokeBrush) {
+ const sp = new libui.DrawStrokeParams();
+
+ switch (props.strokeLinecap) {
+ case 'flat':
+ sp.cap = 0;
+ break;
+ case 'round':
+ sp.cap = 1;
+ break;
+ case 'square':
+ sp.cap = 2;
+ break;
+ }
+
+ switch (props.strokeLinejoin) {
+ case 'miter':
+ sp.join = 0;
+ break;
+ case 'round':
+ sp.join = 1;
+ break;
+ case 'bevel':
+ sp.join = 2;
+ break;
+ }
+
+ sp.thickness = Number(props.strokeWidth);
+ sp.miterLimit = Number(props.strokeMiterlimit);
+
+ p.getContext().stroke(path, strokeBrush, sp);
+
+ //sp.free();
+ //strokBrush.free();
+ }
+
+ if (fillBrush) {
+ p.getContext().fill(path, fillBrush);
+ //fillBrush.free();
+ }
+
+ path.freePath();
+ }
+
+ if (this.props.transform) {
+ p.getContext().restore();
+ }
+ }
+
+ draw(area, p) {}
+}
+
+const AreaComponentPropTypes = {
+ transform: PropTypes.string,
+ fill: PropTypes.string,
+ fillOpacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ stroke: PropTypes.string,
+ strokeOpacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ strokeWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ strokeLinecap: PropTypes.oneOf(['flat', 'round', 'square']),
+ strokeLinejoin: PropTypes.oneOf(['miter', 'round', 'bevel']),
+ strokeMiterlimit: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+};
+
+const AreaComponentDefaultProps = {
+ fillOpacity: 1,
+ strokeOpacity: 1,
+ strokeWidth: 1,
+ strokeMiterlimit: 10,
+ strokeLinecap: 'flat',
+ strokeLinejoin: 'miter',
+};
+
+Area.Group = class AreaGroup extends AreaComponent {
+ constructor(root, props) {
+ super(root, props);
+ this.children = [];
+ }
+
+ appendChild(child) {
+ this.children.push(child);
+ }
+
+ draw(area, p, props) {
+ for (let i = 0; i < this.children.length; i += 1) {
+ if (typeof this.children[i] === 'object') {
+ this.children[i].render(this, area, p, props);
+ }
+ }
+ }
+};
+
+Area.Group.PropTypes = {
+ ...AreaComponentPropTypes,
+ width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+};
+
+Area.Rectangle = class Rectangle extends AreaComponent {
+ getWidth(p) {
+ return this.parseParent(this.props.width, p);
+ }
+
+ getHeight(p) {
+ return this.parseParent(this.props.height, p, true);
+ }
+
+ draw(area, p) {
+ const path = new libui.UiDrawPath(0 /*uiDrawFillModeWinding*/);
+ path.addRectangle(
+ this.parseParent(this.props.x, p),
+ this.parseParent(this.props.y, p, true),
+ this.parseParent(this.props.width, p),
+ this.parseParent(this.props.height, p, true)
+ );
+ path.end();
+ return path;
+ }
+};
+
+Area.Rectangle.PropTypes = {
+ ...AreaComponentPropTypes,
+ x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ y: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+};
+
+Area.Line = class Line extends AreaComponent {
+ getWidth(p) {
+ return Math.abs(
+ this.parseParent(this.props.x2, p) - this.parseParent(this.props.x1, p)
+ );
+ }
+
+ getHeight(p) {
+ return Math.abs(
+ this.parseParent(this.props.y2, p, true) -
+ this.parseParent(this.props.y1, p, true)
+ );
+ }
+
+ draw(area, p) {
+ const path = new libui.UiDrawPath(0 /*uiDrawFillModeWinding*/);
+ path.newFigure(
+ this.parseParent(this.props.x1, p),
+ this.parseParent(this.props.y1, p, true)
+ );
+ path.lineTo(
+ this.parseParent(this.props.x2, p),
+ this.parseParent(this.props.y2, p, true)
+ );
+ path.end();
+
+ return path;
+ }
+};
+
+Area.Line.PropTypes = {
+ ...AreaComponentPropTypes,
+ x1: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ y1: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ x2: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ y2: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+};
+
+Area.Arc = class Arc extends AreaComponent {
+ getWidth(p) {
+ return 2 * this.parseParent(this.props.r, p);
+ }
+
+ getHeight(p) {
+ return getWidth(p);
+ }
+
+ draw(area, p) {
+ const path = new libui.UiDrawPath(0 /*uiDrawFillModeWinding*/);
+ path.newFigureWithArc(
+ this.parseParent(this.props.x, p),
+ this.parseParent(this.props.y, p, true),
+ this.parseParent(this.props.r, p),
+ Number(this.props.start) * (Math.PI / 180),
+ Number(this.props.sweep) * (Math.PI / 180),
+ false
+ );
+ path.end();
+ return path;
+ }
+};
+
+Area.Arc.PropTypes = {
+ ...AreaComponentPropTypes,
+ x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ y: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ r: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ start: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ sweep: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+};
+
+Area.Arc.defaultProps = {
+ ...AreaComponentDefaultProps,
+ start: 0,
+};
+
+Area.Circle = class Circle extends AreaComponent {
+ getWidth(p) {
+ return 2 * this.parseParent(this.props.r, p);
+ }
+
+ getHeight(p) {
+ return getWidth(p);
+ }
+
+ draw(area, p) {
+ const path = new libui.UiDrawPath(0 /*uiDrawFillModeWinding*/);
+ path.newFigureWithArc(
+ this.parseParent(this.props.x, p),
+ this.parseParent(this.props.y, p, true),
+ this.parseParent(this.props.r, p),
+ 0,
+ 2 * Math.PI,
+ false
+ );
+ path.end();
+ return path;
+ }
+};
+
+Area.Circle.PropTypes = {
+ ...AreaComponentPropTypes,
+ x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ y: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ r: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+};
+
+Area.Circle.defaultProps = {
+ ...AreaComponentDefaultProps,
+};
+
+Area.Bezier = class Bezier extends AreaComponent {
+ draw(area, p) {
+ const path = new libui.UiDrawPath(0 /*uiDrawFillModeWinding*/);
+ path.newFigure(
+ this.parseParent(this.props.x1, p),
+ this.parseParent(this.props.y1, p, true)
+ );
+ path.bezierTo(
+ this.parseParent(this.props.cx1, p),
+ this.parseParent(this.props.cy1, p, true),
+ this.parseParent(this.props.cx2, p),
+ this.parseParent(this.props.cy2, p, true),
+ this.parseParent(this.props.x2, p),
+ this.parseParent(this.props.y2, p, true)
+ );
+ path.end();
+
+ return path;
+ }
+};
+
+Area.Bezier.PropTypes = {
+ ...AreaComponentPropTypes,
+ x1: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ y1: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ cx1: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ cy1: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ x2: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ y2: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ cx2: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ cy2: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+};
+
+Area.Path = class Path extends AreaComponent {
+ draw(area, p) {
+ const path = new libui.UiDrawPath(
+ this.props.fillRule === 'evenodd' ? 1 : 0
+ );
+ const commands = parseSVG(this.props.d);
+ parseSVG.makeAbsolute(commands);
+
+ for (let i = 0; i < commands.length; i++) {
+ const c = commands[i];
+ switch (c.command) {
+ case 'moveto':
+ path.newFigure(c.x, c.y);
+ break;
+
+ case 'lineto':
+ case 'horizontal lineto':
+ case 'vertical lineto':
+ path.lineTo(c.x, c.y);
+ break;
+
+ case 'curveto':
+ path.bezierTo(c.x1, c.y1, c.x2, c.y2, c.x, c.y);
+ break;
+
+ case 'smooth curveto':
+ //uses point from previous curve
+ const x1 = c.x0 - (commands[i - 1].x2 - c.x0);
+ const y1 = c.y0 - (commands[i - 1].y2 - c.y0);
+ path.bezierTo(x1, y1, c.x2, c.y2, c.x, c.y);
+ break;
+
+ case 'closepath':
+ path.closeFigure();
+ break;
+
+ default:
+ // 'quadratic curveto', 'elliptical arc'
+ throw new Error(
+ 'Not implemented in Area.Path - ' + c.code + ': ' + c.command
+ );
+ }
+ }
+
+ path.end();
+
+ return path;
+ }
+};
+
+Area.Path.PropTypes = {
+ ...AreaComponentPropTypes,
+ d: PropTypes.string.isRequired,
+ fillRule: PropTypes.oneOf(['nonzero', 'evenodd']),
+};
+
+Area.Path.defaultProps = {
+ fillRule: 'nonzero',
+};
+
+export default Area;
diff --git a/src/components/index.js b/src/components/index.js
index 856fdd6..f4b80fb 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -25,6 +25,7 @@ import ProgressBar from './ProgressBar';
import MenuBar from './MenuBar';
import FontButton from './FontButton';
import Dialog from './Dialog';
+import Area from './Area';
export {
Root,
@@ -54,4 +55,5 @@ export {
MenuBar,
FontButton,
Dialog,
+ Area,
};
diff --git a/src/index.js b/src/index.js
index 3f52c58..d9263ee 100644
--- a/src/index.js
+++ b/src/index.js
@@ -6,6 +6,7 @@ import {
RadioButtons,
Separator,
Menu,
+ Area,
} from './react-components';
import { Dialog } from './components';
@@ -38,6 +39,14 @@ const ProgressBar = 'PROGRESSBAR';
const MenuBar = 'MENUBAR';
const MenuBarItem = 'MENUBARITEM';
const FontButton = 'FONTBUTTON';
+const AreaInternal = 'AREA';
+Area.Rectangle = 'AREARECTANGLE';
+Area.Line = 'AREALINE';
+Area.Arc = 'AREAARC';
+Area.Bezier = 'AREABEZIER';
+Area.Path = 'AREAPATH';
+Area.Group = 'AREAGROUP';
+Area.Circle = 'AREACIRCLE';
export {
render,
@@ -76,4 +85,6 @@ export {
Menu,
FontButton,
Dialog,
+ AreaInternal,
+ Area,
};
diff --git a/src/react-components/Area.js b/src/react-components/Area.js
new file mode 100644
index 0000000..6fcda06
--- /dev/null
+++ b/src/react-components/Area.js
@@ -0,0 +1,79 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { AreaInternal } from '../';
+
+const AreaComponentPropTypes = {
+ transform: PropTypes.string,
+ fill: PropTypes.string,
+ fillOpacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ stroke: PropTypes.string,
+ strokeOpacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ strokeWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ strokeLinecap: PropTypes.oneOf(['flat', 'round', 'square']),
+ strokeLinejoin: PropTypes.oneOf(['miter', 'round', 'bevel']),
+ strokeMiterlimit: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+};
+
+const AreaComponentDefaultProps = {
+ fillOpacity: 1,
+ strokeOpacity: 1,
+ strokeWidth: 1,
+ strokeMiterlimit: 10,
+ strokeLinecap: 'flat',
+ strokeLinejoin: 'miter',
+};
+
+class Area extends Component {
+ render() {
+ const {
+ children,
+ stretchy,
+ label,
+ column,
+ row,
+ span,
+ expand,
+ align,
+ onMouseMove,
+ onMouseUp,
+ onMouseDown,
+ onMouseEnter,
+ onMouseLeave,
+ onKeyUp,
+ onKeyDown,
+ ...groupProps
+ } = this.props;
+ const areaProps = {
+ children,
+ stretchy,
+ label,
+ column,
+ row,
+ span,
+ expand,
+ align,
+ onMouseMove,
+ onMouseUp,
+ onMouseDown,
+ onMouseEnter,
+ onMouseLeave,
+ onKeyUp,
+ onKeyDown,
+ };
+ return React.createElement(
+ AreaInternal,
+ areaProps,
+ React.createElement(Area.Group, groupProps, children)
+ );
+ }
+}
+
+Area.propTypes = {
+ ...AreaComponentPropTypes,
+};
+
+Area.defaultProps = {
+ ...AreaComponentDefaultProps,
+};
+
+export default Area;
diff --git a/src/react-components/index.js b/src/react-components/index.js
index b747608..a6b1765 100644
--- a/src/react-components/index.js
+++ b/src/react-components/index.js
@@ -4,5 +4,6 @@ import Picker from './Picker';
import RadioButtons from './RadioButtons';
import Separator from './Separator';
import Menu from './Menu';
+import Area from './Area';
-export { Box, TextInput, Picker, RadioButtons, Separator, Menu };
+export { Box, TextInput, Picker, RadioButtons, Separator, Menu, Area };
diff --git a/src/utils/createElement.js b/src/utils/createElement.js
index f3b1dca..cbe3421 100644
--- a/src/utils/createElement.js
+++ b/src/utils/createElement.js
@@ -25,6 +25,7 @@ import {
ProgressBar,
MenuBar,
FontButton,
+ Area,
} from '../components/';
import { ROOT_NODE } from '../render/';
@@ -64,6 +65,14 @@ function createElement(type, props) {
MENUBAR: () => new MenuBar(ROOT_NODE, props),
MENUBARITEM: () => new MenuBar.Item(ROOT_NODE, props),
FONTBUTTON: () => new FontButton(ROOT_NODE, props),
+ AREA: () => new Area(ROOT_NODE, props),
+ AREARECTANGLE: () => new Area.Rectangle(ROOT_NODE, props),
+ AREALINE: () => new Area.Line(ROOT_NODE, props),
+ AREAARC: () => new Area.Arc(ROOT_NODE, props),
+ AREABEZIER: () => new Area.Bezier(ROOT_NODE, props),
+ AREAPATH: () => new Area.Path(ROOT_NODE, props),
+ AREAGROUP: () => new Area.Group(ROOT_NODE, props),
+ AREACIRCLE: () => new Area.Circle(ROOT_NODE, props),
default: undefined,
};