fixed fit-content layout issue and inner child swap issue
This commit is contained in:
22
docs/intro.md
Normal file
22
docs/intro.md
Normal file
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ( <div data-widget-id={this.__id}
|
||||
return (
|
||||
<div data-widget-id={this.__id}
|
||||
ref={this.elementRef}
|
||||
className="tw-shadow-xl tw-w-fit tw-h-fit"
|
||||
style={outerStyle}
|
||||
@@ -1103,8 +1133,10 @@ class Widget extends React.Component {
|
||||
>
|
||||
{/* 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 */}
|
||||
<div className="tw-relative tw-w-full tw-h-full tw-top-0 tw-left-0"
|
||||
>
|
||||
<div className="tw-relative tw-w-full tw-bg-red-500 tw-h-full tw-top-0 tw-left-0"
|
||||
|
||||
>
|
||||
|
||||
<div className={`tw-absolute tw-top-[-5px] tw-left-[-5px]
|
||||
tw-border-1 tw-opacity-0 tw-border-solid tw-border-black
|
||||
tw-w-full tw-h-full tw-bg-red-400
|
||||
@@ -1118,7 +1150,9 @@ class Widget extends React.Component {
|
||||
>
|
||||
{/* helps with swappable: if the mouse is in this area while hovering/dropping, then swap */}
|
||||
</div>
|
||||
<div className="tw-relative tw-w-full tw-h-full" ref={this.innerAreaRef}>
|
||||
|
||||
<div className="tw-relative tw-top-0 tw-left-0 tw-bg-blue-500 tw-w-full tw-h-full" ref={this.innerAreaRef}
|
||||
>
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
{
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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 (
|
||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-relative tw-rounded-md tw-overflow-hidden">
|
||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start" style={this.state.widgetInnerStyling}>
|
||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start" style={this.getInnerRenderStyling()}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -109,11 +109,17 @@ class Label extends TkinterWidgetBase{
|
||||
renderContent(){
|
||||
|
||||
const image = this.getAttrValue("imageUpload")
|
||||
|
||||
|
||||
return (
|
||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-content-start tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-content-start tw-h-full tw-rounded-md tw-overflow-hidden"
|
||||
style={{
|
||||
flexGrow: 1, // Ensure the content grows to fill the parent
|
||||
minWidth: '100%', // Force the width to 100% of the parent
|
||||
minHeight: '100%', // Force the height to 100% of the parent
|
||||
}}
|
||||
>
|
||||
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-content-center tw-place-items-center "
|
||||
style={this.state.widgetInnerStyling}>
|
||||
style={this.getInnerRenderStyling()}>
|
||||
{/* {this.props.children} */}
|
||||
{
|
||||
image && (
|
||||
|
||||
@@ -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")}")`
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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")}")`
|
||||
]
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ function Premium({ children, className = "" }) {
|
||||
open={premiumModalOpen}
|
||||
>
|
||||
<div className="tw-mt-5 tw-text-lg tw-max-w-[850px] tw-w-full ">
|
||||
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 <b>one time license</b>.
|
||||
<br />
|
||||
<br />
|
||||
|
||||
Reference in New Issue
Block a user