mirror of
https://github.com/kusti8/proton-native.git
synced 2026-05-15 14:15:50 -06:00
Add initial documentation, add form, and prep for tab (pass full parent, not just element)
This commit is contained in:
parent
6266d3c336
commit
a35ed645ae
19 changed files with 284 additions and 45 deletions
10
Demo.js
10
Demo.js
|
|
@ -1,6 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
import { Window, App, Text, render, Button, Box, TextInput, ColorButton } from './src/';
|
||||
import { Window, App, Text, render, Button, Box, TextInput, ColorButton, Form } from './src/';
|
||||
|
||||
class A extends Component {
|
||||
state = { name: true };
|
||||
|
|
@ -8,16 +8,16 @@ class A extends Component {
|
|||
return (
|
||||
<App>
|
||||
<Window name="Hi" height={640} width={480} menuBar={true}>
|
||||
<Box vertical={this.state.name} padded={true}>
|
||||
<Form padded={true}>
|
||||
<Button
|
||||
stretchy={false}
|
||||
label="Don't press the button"
|
||||
onClicked={() => this.setState({ name: !this.state.name })}
|
||||
>
|
||||
Hello
|
||||
</Button>
|
||||
<ColorButton stretchy={false} color='white' onChanged={color => console.log(color)}/>
|
||||
<TextInput multiline={false} stretchy={false} secure={true} onChanged={text => console.log(text)}>My name is Gustav!</TextInput>
|
||||
</Box>
|
||||
<TextInput label="Very secret" multiline={false} stretchy={false} secure={true} onChanged={text => console.log(text)}>My name is Gustav!</TextInput>
|
||||
</Form>
|
||||
</Window>
|
||||
</App>
|
||||
);
|
||||
|
|
|
|||
0
docs/.nojekyll
Normal file
0
docs/.nojekyll
Normal file
97
docs/README.md
Normal file
97
docs/README.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# Reactor Native
|
||||
|
||||
> Create native desktop applications through a React syntax, on all platforms
|
||||
|
||||
## Why?
|
||||
|
||||
On mobile, it used to be hard to build beatiful cross-platform apps. Then React Native came along, giving us
|
||||
a seamless way to build user interfaces and manage state in code, all while doing it cross platform.
|
||||
|
||||
On desktop, there is no such tool. You can create a GUI using something like Qt, but the code to make it is messy and unorganized.
|
||||
Having made a very large GUI myself, it gets very cumbersome to manage all of that.
|
||||
|
||||
Some of you might be saying that you could do it in Electron. It's a good tool, but it brings in a lot of overhead, running a full webbrowser
|
||||
to manage a small GUI, while Reactor Native can do the same, using native tools, with a smaller size and with less resource usage.
|
||||
|
||||
Reactor Native does the same
|
||||
to desktop that React Native did to mobile. Build cross-platform apps for the desktop, all while never leaving the React eco-system. Popular
|
||||
React packages such as Redux still work.
|
||||
|
||||
**Compare this code in Qt (Python):**
|
||||
|
||||
```python
|
||||
import sys
|
||||
from PyQt5.QtWidgets import (QWidget, QToolTip,
|
||||
QPushButton, QApplication)
|
||||
from PyQt5.QtGui import QFont
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
def print_hello(self):
|
||||
print("Hello")
|
||||
|
||||
|
||||
def initUI(self):
|
||||
btn = QPushButton('Button', self)
|
||||
btn.clicked.connect(self.print_hello)
|
||||
btn.resize(btn.sizeHint())
|
||||
btn.move(50, 50)
|
||||
|
||||
self.setGeometry(300, 300, 300, 200)
|
||||
self.setWindowTitle('Example')
|
||||
self.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec_())
|
||||
```
|
||||
|
||||
**To this code using Reactor Native:**
|
||||
|
||||
```javascript
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { render, Window, App, Button } from 'reactor-native';
|
||||
|
||||
class Example extends Component {
|
||||
state = { name: true };
|
||||
render() {
|
||||
return (
|
||||
<App>
|
||||
<Window name="Example" height={300} width={300} menuBar={false}>
|
||||
<Button
|
||||
stretchy={false}
|
||||
onClicked={() => console.log("Hello")}
|
||||
>
|
||||
Button
|
||||
</Button>
|
||||
</Window>
|
||||
</App>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This will create a file 'text.docx' in the current directory!
|
||||
render(<Example />);
|
||||
```
|
||||
|
||||
It is not only shorter, it is also easier to read and to edit, and can easily utilize the power of the state.
|
||||
|
||||
## Features
|
||||
|
||||
- Same syntax as React Native. Only a few component names have changed, almost everything else is the same
|
||||
- Cross platform
|
||||
- Native components. No more Electron
|
||||
|
||||
## Examples TODO ADD LINK
|
||||
|
||||
Check out the examples to see more.
|
||||
2
docs/_sidebar.md
Normal file
2
docs/_sidebar.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
- [Home](/)
|
||||
- [Quick start](quickstart.md)
|
||||
22
docs/index.html
Normal file
22
docs/index.html
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>react-native-cross - React Native for the desktop, cross compatible</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="description" content="React Native for the desktop, cross compatible">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/dark.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: 'react-native-cross',
|
||||
repo: 'https://github.com/kusti8/react-native-cross',
|
||||
loadSidebar: true
|
||||
}
|
||||
</script>
|
||||
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
24
docs/js_example.js
Normal file
24
docs/js_example.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
import { render, Window, App, Button } from 'reactor-native';
|
||||
|
||||
class Example extends Component {
|
||||
state = { name: true };
|
||||
render() {
|
||||
return (
|
||||
<App>
|
||||
<Window name="Example" height={300} width={300} menuBar={false}>
|
||||
<Button
|
||||
stretchy={false}
|
||||
onClicked={() => console.log("Hello")}
|
||||
>
|
||||
Button
|
||||
</Button>
|
||||
</Window>
|
||||
</App>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This will create a file 'text.docx' in the current directory!
|
||||
render(<Example />);
|
||||
33
docs/python_example.py
Normal file
33
docs/python_example.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import sys
|
||||
from PyQt5.QtWidgets import (QWidget, QToolTip,
|
||||
QPushButton, QApplication)
|
||||
from PyQt5.QtGui import QFont
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
def print_hello(self):
|
||||
print("Hello")
|
||||
|
||||
|
||||
def initUI(self):
|
||||
btn = QPushButton('Button', self)
|
||||
btn.clicked.connect(self.print_hello)
|
||||
btn.resize(btn.sizeHint())
|
||||
btn.move(50, 50)
|
||||
|
||||
self.setGeometry(300, 300, 300, 200)
|
||||
self.setWindowTitle('Example')
|
||||
self.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec_())
|
||||
2
docs/quickstart.md
Normal file
2
docs/quickstart.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Quickstart
|
||||
|
||||
|
|
@ -28,6 +28,7 @@
|
|||
"fbjs": "^0.8.16",
|
||||
"libui-node": "kusti8/libui-node",
|
||||
"performance-now": "^2.1.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^16.2.0",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-reconciler": "^0.7.0"
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ class ColorButton extends DesktopComponent {
|
|||
input = input.toLowerCase()
|
||||
let alpha
|
||||
let c = Color(input).object()
|
||||
if (typeof c.alpha !== 'undefined') {
|
||||
if (this.exists(c.alpha)) {
|
||||
alpha = c.alpha
|
||||
} else if (typeof c.a !== 'undefined') {
|
||||
} else if (this.exists(c.a)) {
|
||||
alpha = c.a
|
||||
} else {
|
||||
alpha = 1
|
||||
|
|
@ -38,7 +38,7 @@ class ColorButton extends DesktopComponent {
|
|||
this.element.color = this.convertToColor(newProps.color);
|
||||
}
|
||||
|
||||
if (typeof this.expectedEvents !== 'undefined') {
|
||||
if (this.exists(this.expectedEvents)) {
|
||||
for (let prop of this.expectedEvents) { // event props
|
||||
if (newProps[prop] !== oldProps[prop]) {
|
||||
this.element[prop](newProps[prop]);
|
||||
|
|
@ -48,13 +48,13 @@ class ColorButton extends DesktopComponent {
|
|||
}
|
||||
|
||||
initialProps(props) {
|
||||
if (typeof props !== 'undefined') {
|
||||
if (typeof props.color !== 'undefined') {
|
||||
if (this.exists(props)) {
|
||||
if (this.exists(props.color)) {
|
||||
this.element.color = this.convertToColor(props.color)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof this.expectedEvents !== 'undefined') {
|
||||
if (this.exists(this.expectedEvents)) {
|
||||
for (let prop in this.expectedEvents) { // event props
|
||||
if (prop in props) {
|
||||
if (this.expectedEvents[prop] !== '') {
|
||||
|
|
@ -66,7 +66,7 @@ class ColorButton extends DesktopComponent {
|
|||
}
|
||||
}
|
||||
|
||||
if (typeof this.expectedChild !== 'undefined') { // text child
|
||||
if (this.exists(this.expectedChild)) { // text child
|
||||
if (props.children) {
|
||||
this.element[this.expectedChild] = props.children;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,24 +5,27 @@ export default class DesktopComponent {
|
|||
this.children = [];
|
||||
}
|
||||
|
||||
appendChild(child) {
|
||||
exists(a) {
|
||||
return typeof a !== 'undefined'
|
||||
}
|
||||
|
||||
appendChild(child) { // add a child to the list to be rendered
|
||||
this.children.push(child);
|
||||
}
|
||||
|
||||
removeChild(child) {
|
||||
if (typeof this.element.setChild !== 'undefined') {
|
||||
removeChild(child) { // remove it, and destroy it
|
||||
if (this.exists(this.element.setChild)) {
|
||||
// if it can only have one child, we don't need to "de-render" it
|
||||
} else if (typeof this.element.deleteAt !== 'undefined') {
|
||||
} else if (this.exists(this.element.deleteAt)) {
|
||||
// if it can have multiple ex. VerticalBox
|
||||
this.element.deleteAt(this.children.indexOf(child));
|
||||
child.element.destroy();
|
||||
}
|
||||
const index = this.children.indexOf(child);
|
||||
this.children.splice(index, 1);
|
||||
console.log('Completed delete');
|
||||
}
|
||||
|
||||
renderChildNode(parent) {
|
||||
renderChildNode(parent) { // render the children
|
||||
for (let i = 0; i < this.children.length; i += 1) {
|
||||
if (typeof this.children[i] === 'object') {
|
||||
this.children[i].render(parent);
|
||||
|
|
@ -30,22 +33,31 @@ export default class DesktopComponent {
|
|||
}
|
||||
}
|
||||
|
||||
addParent(parent) {
|
||||
if (typeof parent.setChild !== 'undefined') {
|
||||
parent.setChild(this.element);
|
||||
} else if (typeof parent.append !== 'undefined') {
|
||||
if (typeof this.specialAppend !== 'undefined') { // we have special parameters for append
|
||||
this.specialAppend(parent)
|
||||
} else {
|
||||
const stretchy =
|
||||
typeof this.props.stretchy === 'undefined' ? true : this.props.stretchy;
|
||||
parent.append(this.element, stretchy);
|
||||
addParentAppend(parent) { // append to parent. Can be overriden
|
||||
const stretchy =
|
||||
!this.exists(this.props.stretchy) ? true : this.props.stretchy;
|
||||
if (parent instanceof libui.UiForm) {
|
||||
parent.element.append(this.props.label, this.element, stretchy)
|
||||
} else if (parent instanceof libui.UiTab) {
|
||||
parent.element.append(this.props.label, this.element, stretchy)
|
||||
if (this.exists(this.props.margined)) {
|
||||
//parent.element.setMargined()
|
||||
}
|
||||
} else {
|
||||
parent.element.append(this.element, stretchy);
|
||||
}
|
||||
}
|
||||
|
||||
addParent(parent) { // add itself to the parent
|
||||
if (this.exists(parent.element.setChild)) {
|
||||
parent.element.setChild(this.element);
|
||||
} else if (this.exists(parent.element.append)) {
|
||||
this.addParentAppend(parent) // append itself to the parent
|
||||
}
|
||||
}
|
||||
|
||||
updateProps(oldProps, newProps) {
|
||||
if (typeof this.expectedProps !== 'undefined') {
|
||||
if (this.exists(this.expectedProps)) {
|
||||
for (let prop of this.expectedProps) { // normal props
|
||||
if (newProps[prop] !== oldProps[prop] && prop in newProps) {
|
||||
this.element[prop] = newProps[prop];
|
||||
|
|
@ -55,7 +67,7 @@ export default class DesktopComponent {
|
|||
}
|
||||
|
||||
updateEvents(oldProps, newProps) {
|
||||
if (typeof this.expectedEvents !== 'undefined') {
|
||||
if (this.exists(this.expectedEvents)) {
|
||||
for (let prop in this.expectedEvents) { // event props
|
||||
if (prop in newProps && newProps[prop] !== oldProps[prop]) {
|
||||
if (this.expectedEvents[prop] !== '') {
|
||||
|
|
@ -69,22 +81,22 @@ export default class DesktopComponent {
|
|||
}
|
||||
|
||||
updateChild(oldProps, newProps) {
|
||||
if (typeof this.expectedChild !== 'undefined') {
|
||||
if (this.exists(this.expectedChild)) {
|
||||
if (newProps.children !== oldProps.children) { // text child
|
||||
this.element[this.expectedChild] = newProps.children;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update(oldProps, newProps) {
|
||||
update(oldProps, newProps) { // update all things, split into props, events, and children
|
||||
this.updateProps(oldProps, newProps)
|
||||
this.updateEvents(oldProps, newProps)
|
||||
this.updateChild(oldProps, newProps)
|
||||
}
|
||||
|
||||
initialNormalProps(props) {
|
||||
if (typeof props !== 'undefined') {
|
||||
if (typeof this.expectedProps !== 'undefined') {
|
||||
if (this.exists(props)) {
|
||||
if (this.exists(this.expectedProps)) {
|
||||
for (let prop of this.expectedProps) { // normal props
|
||||
if (prop in props) {
|
||||
this.element[prop] = props[prop];
|
||||
|
|
@ -95,7 +107,7 @@ export default class DesktopComponent {
|
|||
}
|
||||
|
||||
initialEvents(props) {
|
||||
if (typeof this.expectedEvents !== 'undefined') {
|
||||
if (this.exists(this.expectedEvents)) {
|
||||
for (let prop in this.expectedEvents) { // event props
|
||||
if (prop in props) {
|
||||
if (this.expectedEvents[prop] !== '') {
|
||||
|
|
@ -109,14 +121,14 @@ export default class DesktopComponent {
|
|||
}
|
||||
|
||||
initialChild(props) {
|
||||
if (typeof this.expectedChild !== 'undefined') { // text child
|
||||
if (this.exists(this.expectedChild)) { // text child
|
||||
if (props.children) {
|
||||
this.element[this.expectedChild] = props.children;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initialProps(props) {
|
||||
initialProps(props) { // same, but don't check for oldProps vs newProps, just set them
|
||||
this.initialNormalProps(props)
|
||||
this.initialEvents(props)
|
||||
this.initialChild(props)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import DesktopComponent from './DesktopComponent';
|
||||
import libui from 'libui-node';
|
||||
|
||||
class Form extends DesktopComponent {
|
||||
expectedProps = ['enabled', 'visible', 'padded']
|
||||
|
||||
constructor(root, props) {
|
||||
super(root, props);
|
||||
this.root = root;
|
||||
this.props = props;
|
||||
this.element = new libui.UiForm();
|
||||
this.initialProps(props)
|
||||
}
|
||||
|
||||
render(parent) {
|
||||
this.addParent(parent)
|
||||
this.renderChildNode(this);
|
||||
}
|
||||
}
|
||||
|
||||
export default Form;
|
||||
|
|
@ -14,7 +14,7 @@ class HorizontalBox extends DesktopComponent {
|
|||
|
||||
render(parent) {
|
||||
this.addParent(parent)
|
||||
this.renderChildNode(this.element);
|
||||
this.renderChildNode(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
21
src/components/Tab.js
Normal file
21
src/components/Tab.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import DesktopComponent from './DesktopComponent';
|
||||
import libui from 'libui-node';
|
||||
|
||||
class Tab extends DesktopComponent {
|
||||
expectedProps = ['enabled', 'visible']
|
||||
|
||||
constructor(root, props) {
|
||||
super(root, props);
|
||||
this.root = root;
|
||||
this.props = props;
|
||||
this.element = new libui.UiTab();
|
||||
this.initialProps(props)
|
||||
}
|
||||
|
||||
render(parent) {
|
||||
this.addParent(parent)
|
||||
this.renderChildNode(this);
|
||||
}
|
||||
}
|
||||
|
||||
export default Tab;
|
||||
|
|
@ -14,7 +14,7 @@ class VerticalBox extends DesktopComponent {
|
|||
|
||||
render(parent) {
|
||||
this.addParent(parent)
|
||||
this.renderChildNode(this.element);
|
||||
this.renderChildNode(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,14 @@ class Window extends DesktopComponent {
|
|||
props.menuBar
|
||||
);
|
||||
this.element.onClosing(() => {
|
||||
if (typeof this.props.onClose !== 'undefined') {
|
||||
if (this.exists(this.props.onClose)) {
|
||||
this.props.onClose();
|
||||
}
|
||||
this.element.close();
|
||||
console.log(this.props.lastWindow);
|
||||
if (
|
||||
this.props.lastWindow === true ||
|
||||
typeof this.props.lastWindow === 'undefined'
|
||||
!this.exists(this.props.lastWindow)
|
||||
) {
|
||||
libui.stopLoop();
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ class Window extends DesktopComponent {
|
|||
|
||||
render() {
|
||||
this.element.show();
|
||||
this.renderChildNode(this.element);
|
||||
this.renderChildNode(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,5 +9,6 @@ import Entry from './Entry'
|
|||
import PasswordEntry from './PasswordEntry'
|
||||
import MultilineEntry from './MultilineEntry'
|
||||
import ColorButton from './ColorButton'
|
||||
import Form from './Form'
|
||||
|
||||
export { Root, Text, App, Window, Button, VerticalBox, HorizontalBox, Entry, PasswordEntry, MultilineEntry, ColorButton };
|
||||
export { Root, Text, App, Window, Button, VerticalBox, HorizontalBox, Entry, PasswordEntry, MultilineEntry, ColorButton, Form };
|
||||
|
|
|
|||
|
|
@ -12,5 +12,6 @@ const Entry = 'ENTRY'
|
|||
const PasswordEntry = 'PASSWORDENTRY'
|
||||
const MultilineEntry = 'MULTILINEENTRY'
|
||||
const ColorButton = 'COLORBUTTON'
|
||||
const Form = 'FORM'
|
||||
|
||||
export { render, Text, App, Window, Button, VerticalBox, HorizontalBox, Box, Entry, PasswordEntry, TextInput, MultilineEntry, ColorButton };
|
||||
export { render, Text, App, Window, Button, VerticalBox, HorizontalBox, Box, Entry, PasswordEntry, TextInput, MultilineEntry, ColorButton, Form };
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ import {
|
|||
Entry,
|
||||
PasswordEntry,
|
||||
MultilineEntry,
|
||||
ColorButton
|
||||
ColorButton,
|
||||
Form
|
||||
} from '../components/';
|
||||
import { ROOT_NODE } from '../render/';
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ function createElement(type, props) {
|
|||
PASSWORDENTRY: () => new PasswordEntry(ROOT_NODE, props),
|
||||
MULTILINEENTRY: () => new MultilineEntry(ROOT_NODE, props),
|
||||
COLORBUTTON: () => new ColorButton(ROOT_NODE, props),
|
||||
FORM: () => new Form(ROOT_NODE, props),
|
||||
default: undefined,
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue