fixed fit-content layout issue and inner child swap issue

This commit is contained in:
paul
2024-09-29 17:51:42 +05:30
parent dc0c6ef12c
commit a9a996e108
9 changed files with 194 additions and 50 deletions

22
docs/intro.md Normal file
View 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.

View File

@@ -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)
}))
}
}

View File

@@ -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>
{

View File

@@ -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`)

View File

@@ -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>

View File

@@ -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 && (

View File

@@ -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")}")`
]
}

View File

@@ -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")}")`
]
}

View File

@@ -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 />