From a9a996e1089960a49e4886eaebd7e09f501c9808 Mon Sep 17 00:00:00 2001 From: paul Date: Sun, 29 Sep 2024 17:51:42 +0530 Subject: [PATCH] fixed fit-content layout issue and inner child swap issue --- docs/intro.md | 22 ++++ src/canvas/canvas.js | 14 ++- src/canvas/widgets/base.js | 110 ++++++++++++------- src/frameworks/tkinter/widgets/base.js | 72 +++++++++++- src/frameworks/tkinter/widgets/frame.js | 5 +- src/frameworks/tkinter/widgets/label.js | 12 +- src/frameworks/tkinter/widgets/mainWindow.js | 4 +- src/frameworks/tkinter/widgets/toplevel.js | 3 + src/sidebar/utils/premium.js | 2 +- 9 files changed, 194 insertions(+), 50 deletions(-) create mode 100644 docs/intro.md diff --git a/docs/intro.md b/docs/intro.md new file mode 100644 index 0000000..2454a62 --- /dev/null +++ b/docs/intro.md @@ -0,0 +1,22 @@ +# PyUIBuilder Documentation + +>[!NOTE] +This is a temporary documentation, the page will be updated as more features are added. + + +## Basics Widgets understanding + +Every widget has its own attributes, some of the attributes may be common. + +1. **MainWindow:** Every UI needs to have one main window. If you don't have any main window, the output will not be generated. + + If you have multiple Main Window you'll be asked to delete one window at the time of code generation. + +2. **Layouts:** Every widget that can hold a child widget has three different layouts. + + 1. Flex(also known as pack) + 2. Grid + 3. Absolute/Place + + The parents of the child widgets controls the layout. The layout properties such as grid position will be available to the child under the grid-manager/flex-manager section. + diff --git a/src/canvas/canvas.js b/src/canvas/canvas.js index b6b01fc..822b332 100644 --- a/src/canvas/canvas.js +++ b/src/canvas/canvas.js @@ -3,7 +3,7 @@ import React from "react" // import { DndContext } from '@dnd-kit/core' import { DeleteOutlined, EditOutlined, FileImageOutlined, ReloadOutlined } from "@ant-design/icons" -import { Button, Tooltip, Dropdown } from "antd" +import { Button, Tooltip, Dropdown, message } from "antd" import domtoimage from "dom-to-image-more" import { saveAs } from 'file-saver' @@ -31,7 +31,7 @@ import ResizeWidgetContainer from "./resizeContainer" // const DotsBackground = require("../assets/background/dots.svg") - +// FIXME: once the items is selected and deleted , the toolbar doesn't disappear const CanvasModes = { DEFAULT: 0, PAN: 1, @@ -401,6 +401,13 @@ class Canvas extends React.Component { const widget = this.state.selectedWidget if (!widget) return + + if (widget.state.fitContent?.width && widget.state.fitContent?.height){ + this.setState({widgetResizing: ""}) // Disable resizing if this is true, since the user will have to uncheck fit width and height + message.warning("both width and height are set to fit-content, unset it to start resizing") + return + } + const resizeCorner = this.state.widgetResizing const size = widget.getSize() const pos = widget.getPos() @@ -783,6 +790,7 @@ class Canvas extends React.Component { x: (clientX - parentRect.left) / this.state.zoom, y: (clientY - parentRect.top) / this.state.zoom, } + console.log("Swapp: ", swap) // TODO: fix swapping for grid layouts if (swap) { // If swapping, we need to find the common parent @@ -808,7 +816,7 @@ class Canvas extends React.Component { // Update the state with the new widget hierarchy this.setState((prevState) => ({ - widgets: this.updateWidgetRecursively(prevState.widgets, updatedGrandParentWidget) + widgets: this.updateWidgetRecursively(prevState.widgets, updatedGrandParentWidget, dragWidgetObj) })) } } diff --git a/src/canvas/widgets/base.js b/src/canvas/widgets/base.js index 352b115..4f17316 100644 --- a/src/canvas/widgets/base.js +++ b/src/canvas/widgets/base.js @@ -11,6 +11,8 @@ import EditableDiv from "../../components/editableDiv" import WidgetContainer from "../constants/containers" import { DragContext } from "../../components/draggable/draggableContext" import { isNumeric, removeKeyFromObject } from "../../utils/common" +import { info } from "autoprefixer" +import { message } from "antd" // TODO: make it possible to apply widgetInnerStyle on load @@ -85,6 +87,7 @@ class Widget extends React.Component { pos: { x: 0, y: 0 }, size: { width: 100, height: 100 }, + fitContent: {width: false, height: false}, positionType: PosType.ABSOLUTE, widgetOuterStyling: { @@ -187,6 +190,9 @@ class Widget extends React.Component { this.hideDroppableIndicator = this.hideDroppableIndicator.bind(this) + this.getRenderSize = this.getRenderSize.bind(this) + this.getInnerRenderStyling = this.getInnerRenderStyling.bind(this) + } componentDidMount() { @@ -265,45 +271,21 @@ class Widget extends React.Component { fitWidth: { label: "Fit width", tool: Tools.CHECK_BUTTON, - value: this.state.size?.width === 'fit-content' || false, + value: this.state.fitContent.width, onChange: (value) => { - if (value === true){ - this.updateState({ - size: { - ...this.state.size, - width: 'fit-content' - } - }) - }else{ - this.updateState({ - size: { - ...this.state.size, - width: Math.floor(this.getBoundingRect().width) - } - }) - } + this.updateState((prev) => ({ + fitContent: {...prev.fitContent, width: value} + })) } }, fitHeight: { label: "Fit height", tool: Tools.CHECK_BUTTON, - value: this.state.size?.height === 'fit-content' || false, + value: this.state.fitContent.height, onChange: (value) => { - if (value === true){ - this.updateState({ - size: { - ...this.state.size, - height: 'fit-content' - } - }) - }else{ - this.updateState({ - size: { - ...this.state.size, - height: Math.floor(this.getBoundingRect().height) - } - }) - } + this.updateState((prev) => ({ + fitContent: {...prev.fitContent, height: value} + })) } }, }, @@ -663,6 +645,14 @@ class Widget extends React.Component { */ setWidgetSize(width, height) { + const fitWidth = this.state.fitContent.width || true + const fitHeight = this.state.fitContent.height || true + + if (fitWidth && fitHeight){ + message.warning("both width and height are set to fit-content, unset it to start resizing") + return + } + const newSize = { width: Math.max(this.minSize.width, Math.min(width || this.state.size.width, this.maxSize.width)), height: Math.max(this.minSize.height, Math.min(height || this.state.size.height, this.maxSize.height)), @@ -1049,6 +1039,42 @@ class Widget extends React.Component { ) } + getInnerRenderStyling(){ + const {width, height, minWidth, minHeight} = this.getRenderSize() + + const styling = { + ...this.state.widgetInnerStyling, + width, + height, + minWidth, + minHeight + } + return styling + } + + getRenderSize(){ + + let width = isNumeric(this.state.size.width) ? `${this.state.size.width}px` : this.state.size.width + let height = isNumeric(this.state.size.height) ? `${this.state.size.height}px` : this.state.size.height + + let fitWidth = this.state.fitContent.width + let fitHeight = this.state.fitContent.height + + if (fitWidth){ + width = "max-content" + } + + if (fitHeight){ + height = "max-content" + } + + // if fit width is enabled then the minsize is the resizable size + let minWidth = fitWidth ? this.state.size.width : this.minSize.width + let minHeight = fitHeight ? this.state.size.height : this.minSize.height + + return {width, height, minWidth, minHeight} + + } /** * This is an internal methods don't override @@ -1056,8 +1082,9 @@ class Widget extends React.Component { */ render() { - const width = isNumeric(this.state.size.width) ? `${this.state.size.width}px` : this.state.size.width - const height = isNumeric(this.state.size.height) ? `${this.state.size.height}px` : this.state.size.height + const {width, height, minWidth, minHeight} = this.getRenderSize() + + // NOTE: first check tkinter behaviour with the width and height let outerStyle = { ...this.state.widgetOuterStyling, @@ -1068,6 +1095,8 @@ class Widget extends React.Component { left: `${this.state.pos.x}px`, width: width, height: height, + minWidth: minWidth, + minHeight: minHeight, opacity: this.state.isDragging ? 0.3 : 1, } @@ -1081,7 +1110,8 @@ class Widget extends React.Component { ({ draggedElement, widgetClass, onDragStart, onDragEnd, overElement, setOverElement }) => { - return (
{/* FIXME: Swappable when the parent layout is flex/grid and gap is more, this trick won't work, add bg color to check */} {/* FIXME: Swappable, when the parent layout is gap is 0, it doesn't work well */} -
+
+
{/* helps with swappable: if the mouse is in this area while hovering/dropping, then swap */}
-
+ +
{this.renderContent()}
{ diff --git a/src/frameworks/tkinter/widgets/base.js b/src/frameworks/tkinter/widgets/base.js index 5374d86..49cc19d 100644 --- a/src/frameworks/tkinter/widgets/base.js +++ b/src/frameworks/tkinter/widgets/base.js @@ -8,6 +8,7 @@ import { JUSTIFY, RELIEF } from "../constants/styling" // TODO: add full width and full height in base widget // TODO: the pack should configure width and height of widgets +// FIXME: the font code is not correctly generated export class TkinterBase extends Widget { @@ -51,6 +52,7 @@ export class TkinterBase extends Widget { } this.removeAttr("gridManager") + this.removeAttr("flexManager") if (parentLayout === Layouts.FLEX || parentLayout === Layouts.GRID) { updates = { @@ -58,7 +60,71 @@ export class TkinterBase extends Widget { positionType: PosType.NONE } - if (parentLayout === Layouts.GRID) { + if (parentLayout === Layouts.FLEX){ + updates = { + ...updates, + attrs: { + ...this.state.attrs, + flexManager: { + label: "Flex Manager", + display: "horizontal", + fillX: { + label: "Fill X", + tool: Tools.CHECK_BUTTON, + value: false, + onChange: (value) => { + this.setAttrValue("flexManager.fillX", value) + const widgetStyle = { + ...this.state.widgetOuterStyling, + flexGrow: value ? 1 : 0, + minWidth: 0 + } + + this.updateState({ + widgetOuterStyling: widgetStyle, + }) + + } + }, + fillY: { + label: "Fill Y", + tool: Tools.CHECK_BUTTON, + value: false, + onChange: (value) => { + this.setAttrValue("flexManager.fillY", value) + + const widgetStyle = { + ...this.state.widgetOuterStyling, + flexGrow: value ? 1 : 0, + } + this.updateState({ + widgetOuterStyling: widgetStyle, + }) + } + }, + expand: { + label: "Expand", + tool: Tools.CHECK_BUTTON, + value: false, + onChange: (value) => { + this.setAttrValue("flexManager.expand", value) + + const widgetStyle = { + ...this.state.widgetOuterStyling, + flexGrow: value ? 1 : 0, + } + this.updateState({ + widgetOuterStyling: widgetStyle, + }) + } + }, + + } + } + } + } + + else if (parentLayout === Layouts.GRID) { // Set attributes related to grid layout manager updates = { ...updates, @@ -351,7 +417,7 @@ export class TkinterWidgetBase extends TkinterBase{ label: "font family", tool: Tools.SELECT_DROPDOWN, options: Object.keys(Tkinter_To_GFonts).map((val) => ({value: val, label: val})), - value: "Arial", + value: "", onChange: (value) => { this.setWidgetInnerStyle("fontFamily", Tkinter_To_GFonts[value]) this.setAttrValue("font.fontFamily", value) @@ -360,7 +426,7 @@ export class TkinterWidgetBase extends TkinterBase{ fontSize: { label: "font size", tool: Tools.NUMBER_INPUT, - toolProps: {min: 1, max: 140}, + toolProps: {min: 3, max: 140}, value: null, onChange: (value) => { this.setWidgetInnerStyle("fontSize", `${value}px`) diff --git a/src/frameworks/tkinter/widgets/frame.js b/src/frameworks/tkinter/widgets/frame.js index 061700e..adf6c21 100644 --- a/src/frameworks/tkinter/widgets/frame.js +++ b/src/frameworks/tkinter/widgets/frame.js @@ -15,6 +15,7 @@ class Frame extends TkinterBase{ this.state = { ...this.state, + fitContent: {width: true, height: true}, widgetName: "Frame" } @@ -39,10 +40,12 @@ class Frame extends TkinterBase{ renderContent(){ + // console.log("bounding rect: ", this.getBoundingRect()) + // console.log("widget styling: ", this.state.widgetInnerStyling) return (
-
+
{this.props.children}
diff --git a/src/frameworks/tkinter/widgets/label.js b/src/frameworks/tkinter/widgets/label.js index 27c512f..a45e0ca 100644 --- a/src/frameworks/tkinter/widgets/label.js +++ b/src/frameworks/tkinter/widgets/label.js @@ -109,11 +109,17 @@ class Label extends TkinterWidgetBase{ renderContent(){ const image = this.getAttrValue("imageUpload") - + return ( -
+
+ style={this.getInnerRenderStyling()}> {/* {this.props.children} */} { image && ( diff --git a/src/frameworks/tkinter/widgets/mainWindow.js b/src/frameworks/tkinter/widgets/mainWindow.js index 4b75860..c208df2 100644 --- a/src/frameworks/tkinter/widgets/mainWindow.js +++ b/src/frameworks/tkinter/widgets/mainWindow.js @@ -20,7 +20,6 @@ class MainWindow extends TkinterBase{ widgetName: "main", attrs: { ...this.state.attrs, - widgetName: "main", title: { label: "Window Title", tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string @@ -42,8 +41,11 @@ class MainWindow extends TkinterBase{ generateCode(variableName, parent){ + const backgroundColor = this.getAttrValue("styling.backgroundColor") + return [ `${variableName} = tk.Tk()`, + `${variableName}.config(bg="${backgroundColor}")`, `${variableName}.title("${this.getAttrValue("title")}")` ] } diff --git a/src/frameworks/tkinter/widgets/toplevel.js b/src/frameworks/tkinter/widgets/toplevel.js index c48b72a..3147837 100644 --- a/src/frameworks/tkinter/widgets/toplevel.js +++ b/src/frameworks/tkinter/widgets/toplevel.js @@ -39,8 +39,11 @@ class TopLevel extends Widget{ generateCode(variableName, parent){ + const backgroundColor = this.getAttrValue("styling.backgroundColor") + return [ `${variableName} = tk.TopLevel(root=${parent})`, + `${variableName}.config(bg="${backgroundColor}")`, `${variableName}.title("${this.getAttrValue("title")}")` ] } diff --git a/src/sidebar/utils/premium.js b/src/sidebar/utils/premium.js index c33b310..ceb2aa9 100644 --- a/src/sidebar/utils/premium.js +++ b/src/sidebar/utils/premium.js @@ -34,7 +34,7 @@ function Premium({ children, className = "" }) { open={premiumModalOpen} >
- I am Paul, a self-funded open-source dev. + I am Paul, a indie open-source dev. If you find this tool useful and want to fund and support it's development, consider buying a one time license.