diff --git a/README.md b/README.md index 340fe9b..cba3ea0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,21 @@ # PyUIBuilder - The only Python GUI builder you'll ever need +

+ + + + + + + + + + + + +

- - +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 \ No newline at end of file +## 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 \ No newline at end of file diff --git a/assets/share/1.png b/assets/share/1.png new file mode 100644 index 0000000..124c6a3 Binary files /dev/null and b/assets/share/1.png differ diff --git a/assets/share/2.png b/assets/share/2.png new file mode 100644 index 0000000..1d13164 Binary files /dev/null and b/assets/share/2.png differ diff --git a/assets/share/3.png b/assets/share/3.png new file mode 100644 index 0000000..10adfb1 Binary files /dev/null and b/assets/share/3.png differ diff --git a/assets/share/4.png b/assets/share/4.png new file mode 100644 index 0000000..059716c Binary files /dev/null and b/assets/share/4.png differ diff --git a/notes.md b/notes.md index e0c1c09..a3e9c64 100644 --- a/notes.md +++ b/notes.md @@ -1 +1,2 @@ -### State management in react is a f*king mess \ No newline at end of file +### State management in react is a f*king mess +### Update to TypeScript \ No newline at end of file diff --git a/roadmap.md b/roadmap.md index 786ef74..538f58d 100644 --- a/roadmap.md +++ b/roadmap.md @@ -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 👑 diff --git a/src/canvas/canvas.js b/src/canvas/canvas.js index 9accaab..17c9163 100644 --- a/src/canvas/canvas.js +++ b/src/canvas/canvas.js @@ -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 { {/* */} {/* */} diff --git a/src/canvas/widgets/base.js b/src/canvas/widgets/base.js index 51761fa..3197363 100644 --- a/src/canvas/widgets/base.js +++ b/src/canvas/widgets/base.js @@ -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 ( -
+
{this.props.children}
) diff --git a/src/canvas/widgets/widgetDragDrop.js b/src/canvas/widgets/widgetDragDrop.js index 58557a5..07b4707 100644 --- a/src/canvas/widgets/widgetDragDrop.js +++ b/src/canvas/widgets/widgetDragDrop.js @@ -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 } diff --git a/src/components/draggable/droppable.js b/src/components/draggable/droppable.js index eb7c9bc..259af3d 100644 --- a/src/components/draggable/droppable.js +++ b/src/components/draggable/droppable.js @@ -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) } } diff --git a/src/frameworks/tkinter/sidebarWidgets.js b/src/frameworks/tkinter/sidebarWidgets.js index b60ac4a..c5a5b9b 100644 --- a/src/frameworks/tkinter/sidebarWidgets.js +++ b/src/frameworks/tkinter/sidebarWidgets.js @@ -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", diff --git a/src/frameworks/tkinter/widgets/mainWindow.js b/src/frameworks/tkinter/widgets/mainWindow.js new file mode 100644 index 0000000..726d8a3 --- /dev/null +++ b/src/frameworks/tkinter/widgets/mainWindow.js @@ -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 ( +
+
+
+
+
+
+
+
+
+
+
+
+ {this.props.children} +
+
+ ) + } + +} + + +export default MainWindow \ No newline at end of file diff --git a/src/sidebar/utils/share.js b/src/sidebar/utils/share.js index 705a708..be38308 100644 --- a/src/sidebar/utils/share.js +++ b/src/sidebar/utils/share.js @@ -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" } }, [])