added MainWindow widget and updated readme
This commit is contained in:
94
README.md
94
README.md
@@ -1,8 +1,21 @@
|
||||
# PyUIBuilder - The only Python GUI builder you'll ever need
|
||||
|
||||
<p align="center">
|
||||
<a href="https://twitter.com/share?url=https://github.com/PaulleDemon/tkbuilder&text=Check out PyUIBuilder tool">
|
||||
<img src="./assets/share/1.png" height="30" />
|
||||
</a>
|
||||
<a href="https://www.reddit.com/submit?url=https://github.com/PaulleDemon/tkbuilder&title=Check out PyUIBuilder tool">
|
||||
<img src="./assets/share/4.png" height="30" />
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/shareArticle?mini=true&url=https://github.com/PaulleDemon/tkbuilder&title=check out PyUIBuilder tool">
|
||||
<img src="./assets/share/2.png" height="30" />
|
||||
</a>
|
||||
<a href="https://youtube.com/">
|
||||
<img src="./assets/share/3.png" height="30" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
Build Python GUI's with the ease of Canva
|
||||
|
||||
|
||||
|
||||
@@ -10,9 +23,82 @@
|
||||
* Framework agnostic - Can outputs code in multiple frameworks.
|
||||
* Easy to use.
|
||||
* Plugins to extend 3rd party UI libraries
|
||||
* Generate Code.
|
||||
* Generates Code.
|
||||
|
||||
## Roadmap
|
||||
Here are some of the upcoming features.
|
||||
* Treeview on the sidebar
|
||||
* Kivy Framework support
|
||||
* Pyqt/PySide Support
|
||||
* **Downloadable Electron app** and more.
|
||||
|
||||
To learn more/ see upcoming features visit [roadmap](./roadmap.md)
|
||||
|
||||
|
||||
### All code generated by the builder tools are under MIT license and can be used commercially
|
||||
## License
|
||||
|
||||
To support open-source and development of this tool, consider buying a one-time license.
|
||||
|
||||
License will give you access to upcoming features, early access and more.
|
||||
|
||||
The discount's will be available for limited time only on pre-orders.
|
||||
|
||||
| Type | Free | Premium - Hobbyist / Per user | Premium - Commercial / Per user |
|
||||
|-------------------------------------------------------------------|-------------------|----------------------------------------------------------|------------------------------------------------------------|
|
||||
| **Support open-source development** | 👍️ | 😎 | 🚀 |
|
||||
| **Priority support** - (priorities your feature requests, issues) | community support | ✅ | ✅ |
|
||||
| **Lifetime license** (one-time purchase) | 👍️ | ✅ | ✅ |
|
||||
| **Early access** to upcoming features | ❌ | ✅ | ✅ |
|
||||
| **Downloadable Electron App** (upcoming) | ❌ | ✅ | ✅ |
|
||||
| **Run Preview live**(upcoming) | ❌ | ✅ | ✅ |
|
||||
| **Save and Load UI files** (upcoming) | ❌ | ✅ | ✅ |
|
||||
| **Load 3rd party plugins locally** | ❌ | ✅ | ✅ |
|
||||
| **Dark theme** (upcoming) | ❌ | ✅ | ✅ |
|
||||
| **Commercial Use** | ✅ | ❌ | ✅ |
|
||||
| **Support for PyQt/PySide frameworks** (upcoming) | ❌ | ❌ | ✅ |
|
||||
| **More upcoming features and support** | ❓️ | ✅ | ✅ |
|
||||
| **Price** | - | ~~$129~~ $29 (save 77.52% for limited time on pre-order) | ~~180~~ $49 (Save 72.78% for a limited time on pre-orders) |
|
||||
| Pre-order now! | | [Get license]() | [Get license]() |
|
||||
|
||||
|
||||
## FAQ
|
||||
|
||||
|
||||
1. **Why do I need a GUI builder?**
|
||||
|
||||
**A.** GUI builders assist you quickly create GUI without learning too much about GUI frameworks. It can also help you quickly prototype and see things visually.
|
||||
|
||||
2. **Do I need to purchase a license to use this?**
|
||||
|
||||
**A.** Webbased editor will remain free to use. To support open-source development, If you want a downloadable exe for local development and additional features, you'll need to purchase license based on your needs (hobbiest / commercial)
|
||||
|
||||
3. **How does this compare to other UI builders?**
|
||||
|
||||
**A.**
|
||||
* Most GUI builders out there are framework specific, this UI Builder tool is framework independent.
|
||||
|
||||
* This outputs code in Python, not in XML or other formats which can be hard to debug. So its easier to modify even after downloading the code.
|
||||
|
||||
* Support for 3rd party UI libraries. Many GUI builders don't come with support for 3rd party libraries.
|
||||
|
||||
## License Information
|
||||
|
||||
To support development of this project, license differ depending on the usecase.
|
||||
|
||||
#### Webbased Editor
|
||||
* All code generated by the builder tools are licensed under MIT and can be used commercially
|
||||
|
||||
#### Electron App - Hobbyist License
|
||||
This is meant for students and hobbiest's
|
||||
* All code generated by the builder tools are free to use for non-commercial purposes. If you are using
|
||||
this for a startup or your business you'll need to get a business license.
|
||||
|
||||
#### Electron App - Commercial License
|
||||
This is meant for business usecases, you can use the code even for commercial use.
|
||||
* All code generated by the builder tools are free to use for commercial and non-commercial purposes. If you are using this for a startup or your business you'll need to get a commercial license.
|
||||
|
||||
|
||||
|
||||
## Author
|
||||
* Paul
|
||||
* Github: PaulleDemon
|
||||
BIN
assets/share/1.png
Normal file
BIN
assets/share/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
BIN
assets/share/2.png
Normal file
BIN
assets/share/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/share/3.png
Normal file
BIN
assets/share/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/share/4.png
Normal file
BIN
assets/share/4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
3
notes.md
3
notes.md
@@ -1 +1,2 @@
|
||||
### State management in react is a f*king mess
|
||||
### State management in react is a f*king mess
|
||||
### Update to TypeScript
|
||||
@@ -9,6 +9,7 @@ Any feature that has 👑 beside it, is meant only for [premium users](./readme.
|
||||
- [ ] UI fixes and enhancement
|
||||
- [ ] Tree view for elements on the canvas
|
||||
- [ ] Add text editor to support event handlers
|
||||
- [ ] Support more widgets
|
||||
- [ ] Rewrite DND for better feedback - (swappy/react-dnd-kit/ GSAP draggable)
|
||||
- [ ] Duplicate widgets
|
||||
|
||||
@@ -23,6 +24,7 @@ Any feature that has 👑 beside it, is meant only for [premium users](./readme.
|
||||
### 2.0.0
|
||||
- [ ] Support for more third party plugins
|
||||
- [ ] Support more templates
|
||||
- [ ] Allow creating components
|
||||
- [ ] Support for Kivy
|
||||
- [ ] Sharable Templates
|
||||
- [ ] Dark theme 👑
|
||||
|
||||
@@ -713,6 +713,8 @@ class Canvas extends React.Component {
|
||||
throw new Error("widgetComponentType must be a subclass of Widget class")
|
||||
}
|
||||
|
||||
console.log("componete: ", widgetComponentType)
|
||||
|
||||
const widgetRef = React.createRef()
|
||||
|
||||
const id = `${widgetComponentType.widgetType}_${UID()}`
|
||||
@@ -842,7 +844,7 @@ class Canvas extends React.Component {
|
||||
|
||||
// TODO: handle drop from sidebar
|
||||
// if the widget is being dropped from the sidebar, use the info to create the widget first
|
||||
this.createWidget(Widget, ({ id, widgetRef }) => {
|
||||
this.createWidget(widgetClass, ({ id, widgetRef }) => {
|
||||
widgetRef.current.setPos(finalPosition.x, finalPosition.y)
|
||||
})
|
||||
|
||||
@@ -968,6 +970,7 @@ class Canvas extends React.Component {
|
||||
|
||||
{/* <ActiveWidgetProvider> */}
|
||||
<DroppableWrapper id="canvas-droppable"
|
||||
droppableTags={{exclude: ["image", "video"]}}
|
||||
className="tw-w-full tw-h-full"
|
||||
onDrop={this.handleDropEvent}>
|
||||
{/* <DragWidgetProvider> */}
|
||||
|
||||
@@ -55,7 +55,7 @@ class Widget extends React.Component {
|
||||
"load": { "args1": "number", "args2": "string" }
|
||||
}
|
||||
|
||||
this.droppableTags = ["widget"] // This indicates if the draggable can be dropped on this widget
|
||||
this.droppableTags = {} // This indicates if the draggable can be dropped on this widget
|
||||
this.boundingRect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -143,7 +143,6 @@ class Widget extends React.Component {
|
||||
},
|
||||
}
|
||||
|
||||
this.mousePress = this.mousePress.bind(this)
|
||||
this.getElement = this.getElement.bind(this)
|
||||
|
||||
this.getId = this.getId.bind(this)
|
||||
@@ -169,7 +168,6 @@ class Widget extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.elementRef.current?.addEventListener("click", this.mousePress)
|
||||
|
||||
// FIXME: initial layout is not set properly
|
||||
console.log("prior layout: ", this.state.attrs.layout.value)
|
||||
@@ -182,7 +180,6 @@ class Widget extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.elementRef.current?.removeEventListener("click", this.mousePress)
|
||||
}
|
||||
|
||||
updateState = (newState, callback) => {
|
||||
@@ -273,12 +270,6 @@ class Widget extends React.Component {
|
||||
return this.__id
|
||||
}
|
||||
|
||||
mousePress(event) {
|
||||
// event.preventDefault()
|
||||
if (!this._disableSelection) {
|
||||
}
|
||||
}
|
||||
|
||||
select() {
|
||||
this.setState({
|
||||
selected: true
|
||||
@@ -642,7 +633,12 @@ class Widget extends React.Component {
|
||||
show: true
|
||||
}
|
||||
|
||||
if (this.droppableTags.length === 0 || this.droppableTags.includes(dragEleType)) {
|
||||
const allowDrop = (this.droppableTags && (Object.keys(this.droppableTags).length === 0 ||
|
||||
(this.droppableTags.include?.length > 0 && this.droppableTags.include?.includes(dragEleType)) ||
|
||||
(this.droppableTags.exclude?.length > 0 && !this.droppableTags.exclude?.includes(dragEleType))
|
||||
))
|
||||
|
||||
if (allowDrop) {
|
||||
showDrop = {
|
||||
allow: true,
|
||||
show: true
|
||||
@@ -670,7 +666,12 @@ class Widget extends React.Component {
|
||||
// console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer)
|
||||
const dragEleType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
if (this.droppableTags.length === 0 || this.droppableTags.includes(dragEleType)) {
|
||||
const allowDrop = (this.droppableTags && (Object.keys(this.droppableTags).length === 0 ||
|
||||
(this.droppableTags.include?.length > 0 && this.droppableTags.include?.includes(dragEleType)) ||
|
||||
(this.droppableTags.exclude?.length > 0 && !this.droppableTags.exclude?.includes(dragEleType))
|
||||
))
|
||||
|
||||
if (allowDrop) {
|
||||
e.preventDefault() // NOTE: this is necessary to allow drop to take place
|
||||
}
|
||||
|
||||
@@ -692,7 +693,12 @@ class Widget extends React.Component {
|
||||
|
||||
const dragEleType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
if (this.droppableTags.length > 0 && !this.droppableTags.includes(dragEleType)) {
|
||||
const allowDrop = (this.droppableTags && (Object.keys(this.droppableTags).length === 0 ||
|
||||
(this.droppableTags.include?.length > 0 && this.droppableTags.include?.includes(dragEleType)) ||
|
||||
(this.droppableTags.exclude?.length > 0 && !this.droppableTags.exclude?.includes(dragEleType))
|
||||
))
|
||||
|
||||
if (allowDrop) {
|
||||
return // prevent drop if the draggable element doesn't match
|
||||
}
|
||||
|
||||
@@ -771,11 +777,14 @@ class Widget extends React.Component {
|
||||
this.elementRef.current.style.pointerEvents = "auto"
|
||||
}
|
||||
|
||||
// FIXME: children outside the bounding box, add tw-overflow-hidden
|
||||
renderContent() {
|
||||
/**
|
||||
* Note: you must implement this method in subclass, if you want children make sure to pass
|
||||
* {this.props.children}, to modify the style add this.state.widgetStyling
|
||||
*/
|
||||
renderContent() {
|
||||
// throw new NotImplementedError("render method has to be implemented")
|
||||
return (
|
||||
<div className="tw-w-full tw-h-full tw-p-2 tw-content-start tw-rounded-md" style={this.state.widgetStyling}>
|
||||
<div className="tw-w-full tw-h-full tw-p-2 tw-content-start tw-rounded-md tw-overflow-hidden" style={this.state.widgetStyling}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useDragContext } from "../../components/draggable/draggableContext"
|
||||
*/
|
||||
const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="widget",
|
||||
onDragEnter, onDragLeave, onDrop, style={},
|
||||
droppableTags = ["widget"], ...props }) => {
|
||||
droppableTags = {}, ...props }) => {
|
||||
|
||||
// const { draggedElement, onDragStart, onDragEnd } = useDragWidgetContext()
|
||||
const { draggedElement, onDragStart, onDragEnd, overElement, setOverElement } = useDragContext()
|
||||
@@ -71,8 +71,12 @@ const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="wid
|
||||
allow: true,
|
||||
show: true
|
||||
}
|
||||
const allowDrop = (Object.keys(droppableTags).length === 0 ||
|
||||
(droppableTags.include.length > 0 && droppableTags.include.includes(dragEleType)) ||
|
||||
(droppableTags.exclude.length > 0 && !droppableTags.exclude.includes(dragEleType))
|
||||
)
|
||||
|
||||
if (droppableTags.length === 0 || droppableTags.includes(dragEleType)) {
|
||||
if (allowDrop) {
|
||||
showDrop = {
|
||||
allow: true,
|
||||
show: true
|
||||
@@ -99,7 +103,12 @@ const WidgetDraggable = memo(({ widgetRef, enableDrag=true, dragElementType="wid
|
||||
// console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer)
|
||||
const dragEleType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
if (droppableTags.length === 0 || droppableTags.includes(dragEleType)) {
|
||||
const allowDrop = (Object.keys(droppableTags).length === 0 ||
|
||||
(droppableTags.include.length > 0 && droppableTags.include.includes(dragEleType)) ||
|
||||
(droppableTags.exclude.length > 0 && !droppableTags.exclude.includes(dragEleType))
|
||||
)
|
||||
|
||||
if (allowDrop) {
|
||||
e.preventDefault() // this is necessary to allow drop to take place
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@ import { memo, useState } from "react"
|
||||
import { useDragContext } from "./draggableContext"
|
||||
|
||||
|
||||
const DroppableWrapper = memo(({onDrop, droppableTags=["widget"], ...props}) => {
|
||||
/**
|
||||
* @param {{include: [], exclude: []} || {}} - droppableTags - if empty object, allows everything to be dropped, define include to allow only included widgets, define exclude to exclude
|
||||
*/
|
||||
const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
|
||||
|
||||
|
||||
const { draggedElement, overElement, setOverElement, widgetClass } = useDragContext()
|
||||
@@ -17,11 +20,16 @@ const DroppableWrapper = memo(({onDrop, droppableTags=["widget"], ...props}) =>
|
||||
|
||||
const dragElementType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
// console.log("Current target: ", e.currentTarget)
|
||||
console.log("Current target: ", droppableTags, Object.keys(droppableTags))
|
||||
|
||||
setOverElement(e.currentTarget)
|
||||
|
||||
if (droppableTags.length === 0 || droppableTags.includes(dragElementType)){
|
||||
const allowDrop = (droppableTags && (Object.keys(droppableTags).length === 0 ||
|
||||
(droppableTags.include?.length > 0 && droppableTags.include?.includes(dragElementType)) ||
|
||||
(droppableTags.exclude?.length > 0 && !droppableTags.exclude?.includes(dragElementType))
|
||||
))
|
||||
|
||||
if (allowDrop){
|
||||
setShowDroppable({
|
||||
allow: true,
|
||||
show: true
|
||||
@@ -37,8 +45,13 @@ const DroppableWrapper = memo(({onDrop, droppableTags=["widget"], ...props}) =>
|
||||
const handleDragOver = (e) => {
|
||||
// console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer)
|
||||
const dragElementType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
if (droppableTags.length === 0 || droppableTags.includes(dragElementType)){
|
||||
|
||||
const allowDrop = (droppableTags && (Object.keys(droppableTags).length === 0 ||
|
||||
(droppableTags.include?.length > 0 && droppableTags.include?.includes(dragElementType)) ||
|
||||
(droppableTags.exclude?.length > 0 && !droppableTags.exclude?.includes(dragElementType))
|
||||
))
|
||||
|
||||
if (allowDrop){
|
||||
e.preventDefault() // this is necessary to allow drop to take place
|
||||
}
|
||||
|
||||
@@ -51,8 +64,15 @@ const DroppableWrapper = memo(({onDrop, droppableTags=["widget"], ...props}) =>
|
||||
allow: false,
|
||||
show: false
|
||||
})
|
||||
const dragElementType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
if(onDrop){
|
||||
|
||||
const allowDrop = (droppableTags && (Object.keys(droppableTags).length === 0 ||
|
||||
(droppableTags.include?.length > 0 && droppableTags.include?.includes(dragElementType)) ||
|
||||
(droppableTags.exclude?.length > 0 && !droppableTags.exclude?.includes(dragElementType))
|
||||
))
|
||||
|
||||
if(onDrop && allowDrop){
|
||||
onDrop(e, draggedElement, widgetClass)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import Widget from "../../canvas/widgets/base"
|
||||
|
||||
import ButtonWidget from "./assets/widgets/button.png"
|
||||
import MainWindow from "./widgets/mainWindow"
|
||||
|
||||
|
||||
const TkinterSidebar = [
|
||||
@@ -9,7 +10,7 @@ const TkinterSidebar = [
|
||||
name: "Main window",
|
||||
img: ButtonWidget,
|
||||
link: "https://github.com",
|
||||
widgetClass: Widget
|
||||
widgetClass: MainWindow
|
||||
},
|
||||
{
|
||||
name: "Top Level",
|
||||
|
||||
46
src/frameworks/tkinter/widgets/mainWindow.js
Normal file
46
src/frameworks/tkinter/widgets/mainWindow.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { CloseCircleFilled, ExpandOutlined, MinusCircleFilled } from "@ant-design/icons"
|
||||
import Widget from "../../../canvas/widgets/base"
|
||||
|
||||
|
||||
class MainWindow extends Widget{
|
||||
|
||||
static widgetType = "main_window"
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.droppableTags = {
|
||||
exclude: ["image", "video", "media"]
|
||||
}
|
||||
|
||||
this.state = {
|
||||
...this.state,
|
||||
|
||||
}
|
||||
this.setAttrValue("styling.backgroundColor", "#E4E2E2")
|
||||
}
|
||||
|
||||
renderContent(){
|
||||
return (
|
||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||
<div className="tw-flex tw-w-full tw-h-[25px] tw-bg-[#adadad] tw-shadow-md">
|
||||
<div className="tw-ml-auto tw-flex tw-gap-1 tw-p-1 tw-place-items-center">
|
||||
<div className="tw-bg-yellow-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
|
||||
</div>
|
||||
<div className="tw-bg-blue-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
|
||||
</div>
|
||||
<div className="tw-bg-red-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start" style={this.state.widgetStyling}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default MainWindow
|
||||
@@ -11,7 +11,7 @@ function Share({children, className=""}){
|
||||
const shareInfo = useMemo(() => {
|
||||
|
||||
return {
|
||||
url: encodeURI("https://github.com/PaulleDemon/font-tester-chrome"),
|
||||
url: encodeURI("https://github.com/PaulleDemon/tkbuilder"),
|
||||
text: "Check out Framework agnostic GUI builder for python"
|
||||
}
|
||||
}, [])
|
||||
|
||||
Reference in New Issue
Block a user