diff --git a/docs/intro.md b/docs/intro.md index 1315df5..28bdc60 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -10,7 +10,7 @@ Let's start with the basics of UI  -1. The sidebar on the left will have multiple buttons, each button will provide you with necessary tools. +1. The sidebar on the left will have multiple tabs, each tabs will provide you with necessary tools. 2. The Place where you drag and drop widgets is the canvas 3. The toolbar will only appear if a widget is selected. @@ -26,7 +26,7 @@ Things you can do on canvas. 4. Delete widgets using `del` key or right clicking on the widget ## Project name -By default all project's are named untitled project, you can change this from the header input next to export code. +By default all project's are named `"untitled project"`, you can change this from the header input next to export code. ## Selecting a UI library You can select the UI library from the header dropdown. Once selected changing the UI library in between your work, will erase the canvas. diff --git a/src/canvas/canvas.js b/src/canvas/canvas.js index e5e1b44..9110c30 100644 --- a/src/canvas/canvas.js +++ b/src/canvas/canvas.js @@ -1259,30 +1259,37 @@ class Canvas extends React.Component { } updateWidgetAndChildren = (widgetId) => { - const serializeWidgetRecursively = (widget) => { - const widgetObj = this.getWidgetById(widget.id)?.current; - if (!widgetObj) return widget; // If no widget reference found, return unchanged - - return { - ...widget, - initialData: { - ...widget.initialData, - ...widgetObj.serialize() - }, - children: widget.children?.map(serializeWidgetRecursively) || [] // Recursively serialize children + const serializeWidgetRecursively = (widget) => { + const widgetObj = this.getWidgetById(widget.id)?.current; + if (!widgetObj) return widget; // If no widget reference found, return unchanged + return { + ...widget, + initialData: { + ...widget.initialData, + ...widgetObj.serialize() + }, + children: widget.children?.map(serializeWidgetRecursively) || [] // Recursively serialize children + }; }; - }; + this.setWidgets(prevWidgets => { const updateWidgets = (widgets) => { - return widgets.map(widget => - widget.id === widgetId ? serializeWidgetRecursively(widget) : widget - ); - }; + return widgets.map(widget => { + if (widget.id === widgetId) { + return serializeWidgetRecursively(widget) + } + // Search inside children recursively + return { + ...widget, + children: updateWidgets(widget.children || []) + } + }) + } - return updateWidgets(prevWidgets); - }); - }; + return updateWidgets(prevWidgets) + }) + } renderWidget = (widget) => { diff --git a/src/canvas/widgets/base.js b/src/canvas/widgets/base.js index 39c7d39..ffe9088 100644 --- a/src/canvas/widgets/base.js +++ b/src/canvas/widgets/base.js @@ -136,7 +136,7 @@ class Widget extends React.Component { label: "Layout", tool: Tools.LAYOUT_MANAGER, // the tool to display, can be either HTML ELement or a constant string value: { - layout: "flex", + layout: Layouts.PLACE, direction: "row", // grid: { // rows: 12, @@ -243,6 +243,7 @@ class Widget extends React.Component { } componentDidUpdate(prevProps, prevState) { + if (prevProps !== this.props) { this.canvasMetaData = this.props.canvasMetaData } @@ -704,7 +705,7 @@ class Widget extends React.Component { getLayout(){ - return this.state?.attrs?.layout?.value || Layouts.FLEX + return this.getAttrValue("layout") || Layouts.PLACE } setLayout(value) { @@ -734,13 +735,13 @@ class Widget extends React.Component { // widgetStyle["placeContent"] = "unset" // } - this.updateState({ - widgetInnerStyling: widgetStyle + this.setAttrValue("layout", value, () => { + this.updateState({ + widgetInnerStyling: widgetStyle + }) + this.props.onLayoutUpdate({parentId: this.__id, parentLayout: value})// inform children about the layout update }) - this.setAttrValue("layout", value) - this.props.onLayoutUpdate({parentId: this.__id, parentLayout: value})// inform children about the layout update - } getWidgetInnerStyle = (key) => { diff --git a/src/frameworks/tkinter/widgets/base.js b/src/frameworks/tkinter/widgets/base.js index a12b3ed..751aa54 100644 --- a/src/frameworks/tkinter/widgets/base.js +++ b/src/frameworks/tkinter/widgets/base.js @@ -8,7 +8,7 @@ import { convertObjectToKeyValueString, isNumeric, removeKeyFromObject } from ". import { randomArrayChoice } from "../../../utils/random" import { Tkinter_TO_WEB_CURSOR_MAPPING } from "../constants/cursor" import { Tkinter_To_GFonts } from "../constants/fontFamily" -import { GRID_STICKY, JUSTIFY, RELIEF } from "../constants/styling" +import { ANCHOR, GRID_STICKY, JUSTIFY, RELIEF } from "../constants/styling" // FIXME: grid sticky may clash with flex sticky when changing layout, check it once @@ -27,7 +27,7 @@ export class TkinterBase extends Widget { ...this.state, packAttrs: { // This is required as during flex layout change remount happens and the state updates my not function as expected side: "top", - anchor: "nw", + anchor: "n", } } @@ -68,6 +68,12 @@ export class TkinterBase extends Widget { const packSide = this.getAttrValue("flexManager.side") + const marginX = this.getAttrValue("margin.marginX") + const marginY = this.getAttrValue("margin.marginY") + + const paddingX = this.getAttrValue("padding.padX") + const paddingY = this.getAttrValue("padding.padY") + const config = {} if (packSide === "" || packSide === "top"){ @@ -86,11 +92,27 @@ export class TkinterBase extends Widget { config['side'] = `tk.BOTTOM` } - if (gap > 0){ - config["padx"] = gap - config["pady"] = gap + // if (gap > 0){ + // config["padx"] = gap + // config["pady"] = gap + // } + + if (marginX){ + config["padx"] = marginX } + if (marginY){ + config["pady"] = marginY + } + + if (paddingX){ + config["ipadx"] = paddingX + } + + if (paddingY){ + config["ipady"] = paddingY + } + // if (align === "start"){ // config["anchor"] = "'nw'" // }else if (align === "center"){ @@ -283,27 +305,6 @@ export class TkinterBase extends Widget { flexManager: { label: "Pack Manager", display: "horizontal", - - // anchor: { - // label: "Anchor", - // tool: Tools.SELECT_DROPDOWN, - // options: ["nw", "ne", "sw", "se", "center"].map(val => ({value: val, label: val})), - // value: this.state.packAttrs.anchor, - // onChange: (value) => { - // this.setAttrValue("flexManager.anchor", value, () => { - // console.log("anchor updated") - // this.updateState((prevState) => ({packAttrs: {...prevState.packAttrs, anchor: value}}), () => { - - // this.props.requestWidgetDataUpdate(this.props.parentWidgetRef.current.__id) - - // // this.props.requestWidgetDataUpdate(this.__id) - // this.stateChangeSubscriberCallback() // call this to notify the toolbar that the widget has changed state - // // this.props.parentWidgetRef.current.forceRerender() - // }) - // // this.props.parentWidgetRef.current.forceRerender() - // }) - // } - // }, fillX: { label: "Fill X", tool: Tools.CHECK_BUTTON, @@ -366,6 +367,26 @@ export class TkinterBase extends Widget { // this.setWidgetOuterStyle(value ? 1 : 0) } }, + anchor: { + label: "Anchor", + tool: Tools.SELECT_DROPDOWN, + options: ANCHOR.map(val => ({value: val, label: val})), + value: this.state.packAttrs.anchor, + onChange: (value) => { + this.setAttrValue("flexManager.anchor", value, () => { + console.log("set anchor: ", this.state.attrs.flexManager) + // this.props.parentWidgetRef.current.forceRerender() + }) + this.updateState((prevState) => ({packAttrs: {...prevState.packAttrs, anchor: value}}), () => { + + // this.props.requestWidgetDataUpdate(this.props.parentWidgetRef.current.__id) + + // this.props.requestWidgetDataUpdate(this.__id) + // this.stateChangeSubscriberCallback() // call this to notify the toolbar that the widget has changed state + // this.props.parentWidgetRef.current.forceRerender() + }) + } + }, } } @@ -582,106 +603,112 @@ export class TkinterBase extends Widget { * @param {*} index * @returns */ - renderPackWidgetsRecursively = (widgets, index = 0, lastSide="", previousExpandValue=0) => { - - if (index >= widgets.length) return null - - - const widget = widgets[index] - const widgetRef = widget.ref?.current - if (!widgetRef) return null // Ensure ref exists before accessing - - const side = widgetRef.getPackAttrs()?.side || "top" - const expand = widgetRef.getPackAttrs()?.expand || false - - console.log("rerendering; ", side, expand) - - const direction = (s) => { - return (s === "bottom" - ? "column-reverse" - : s === "top" - ? "column" - : s === "right" - ? "row-reverse" - : "row") + renderPackWidgetsRecursively = (widgets, index = 0, lastSide = "", previousExpandValue = 0) => { + if (index >= widgets.length) return null; + + const widget = widgets[index]; + const widgetRef = widget.ref?.current; + if (!widgetRef) return null; // Ensure ref exists before accessing + + const { side = "top", expand = false, anchor } = widgetRef.getPackAttrs() || {}; + + console.log("rerendering:", side, expand); + + const directionMap = { + top: "column", + bottom: "column-reverse", + left: "row", + right: "row-reverse", } - - const currentWidgetDirection = direction(side) - - const isSameSide = lastSide === side + + const currentWidgetDirection = directionMap[side] || "column"; // Default to "column" + const isSameSide = lastSide === side; + const isVertical = ["top", "bottom"].includes(side); + + let expandValue = expand ? (isSameSide ? previousExpandValue : widgets.length - index) : 1; + if (expand && !isSameSide) previousExpandValue = expandValue; + + lastSide = side; // Update last side for recursion - let expandValue = 0 // the first element will be given highest priority when expanding if expand is True - - if (expand){ - if (isSameSide){ - expandValue = previousExpandValue // if its the same side then its value is same as the previous else widget length - index - }else{ - expandValue = widgets.length - index - previousExpandValue = expandValue - } - - } + // 🟢 Mapping Tkinter anchors to Flexbox styles + const anchorStyles = { + n: { alignItems: "flex-start", justifyContent: "center" }, // Top-center + s: { alignItems: "flex-end", justifyContent: "center" }, // Bottom-center + e: { alignItems: "center", justifyContent: "flex-end" }, // Right-center + w: { alignItems: "center", justifyContent: "flex-start" }, // Left-center + ne: { alignItems: "flex-start", justifyContent: "flex-end" }, // Top-right + nw: { alignItems: "flex-start", justifyContent: "flex-start" }, // Top-left + se: { alignItems: "flex-end", justifyContent: "flex-end" }, // Bottom-right + sw: { alignItems: "flex-end", justifyContent: "flex-start" }, // Bottom-left + center: { alignItems: "center", justifyContent: "center" }, // Fully centered + }; + const { justifyContent, alignItems } = anchorStyles[anchor] || anchorStyles["n"] - lastSide = side; // Update last side for next recursion - - // console.log("current widget direction: ", isSameSide, currentWidgetDirection) - + const stretchClass = isVertical ? "tw-flex-grow" : "tw-h-full"; // Allow only horizontal growth for top/bottom + if (isSameSide) { return ( <> - {/*