fix: added fit-content for width and height.

fixed button and label centering for tkinter widgets
This commit is contained in:
paul
2024-09-25 23:29:50 +05:30
parent 2c093c2d2d
commit 37e7bea0fa
7 changed files with 119 additions and 40 deletions

View File

@@ -774,7 +774,6 @@ class Canvas extends React.Component {
const parentWidget = this.widgetRefs[parentWidgetId].current
const parentRect = parentWidget.getBoundingRect()
const canvasRect = this.canvasRef.current.getBoundingClientRect()
const { clientX, clientY } = event
@@ -782,7 +781,7 @@ class Canvas extends React.Component {
x: (clientX - parentRect.left) / this.state.zoom,
y: (clientY - parentRect.top) / this.state.zoom,
}
// TODO: fix swapping for grid layouts
if (swap) {
// If swapping, we need to find the common parent
const grandParentWidgetObj = this.findWidgetFromListById(dropWidgetObj.parent)

View File

@@ -12,10 +12,11 @@ import { DragWidgetProvider } from "./draggableWidgetContext"
import WidgetDraggable from "./widgetDragDrop"
import WidgetContainer from "../constants/containers"
import { DragContext } from "../../components/draggable/draggableContext"
import { removeKeyFromObject } from "../../utils/common"
import { isNumeric, removeKeyFromObject } from "../../utils/common"
// FIXME: make it possible to have fit-width and height
// FIXME: widget styling not being applied if directly added to child instead of the canvas
// TODO: make it possible to apply widgetInnerStyle on load
const ATTRS_KEYS = ['value', 'label', 'tool', 'onChange', 'toolProps'] // these are attrs keywords, don't use these keywords as keys while defining the attrs property
@@ -63,12 +64,12 @@ class Widget extends React.Component {
// This indicates if the draggable can be dropped on this widget, set this to null to disable drops
this.droppableTags = {}
this.boundingRect = {
x: 0,
y: 0,
height: 100,
width: 100
}
// this.boundingRect = {
// x: 0,
// y: 0,
// height: 100,
// width: 100
// }
this.state = {
zIndex: 0,
@@ -178,6 +179,10 @@ class Widget extends React.Component {
this.setPosType = this.setPosType.bind(this)
this.setParentLayout = this.setParentLayout.bind(this)
this.load = this.load.bind(this)
this.serialize = this.serialize.bind(this)
this.serializeAttrsValues = this.serializeAttrsValues.bind(this)
}
componentDidMount() {
@@ -186,7 +191,7 @@ class Widget extends React.Component {
if (this.state.attrs.layout){
this.setLayout(this.state.attrs.layout.value)
console.log("prior layout: ", this.state.attrs.layout.value)
// console.log("prior layout: ", this.state.attrs.layout.value)
}
if (this.state.attrs.styling.backgroundColor)
@@ -230,8 +235,6 @@ class Widget extends React.Component {
getToolbarAttrs() {
console.log("Current attributes: ", this.state.attrs)
return ({
id: this.__id,
widgetName: {
@@ -258,6 +261,50 @@ class Widget extends React.Component {
value: this.state.size.height || 100,
onChange: (value) => this.setWidgetSize(null, value)
},
fitWidth: {
label: "Fit width",
tool: Tools.CHECK_BUTTON,
value: this.state.size?.width === 'fit-content' || false,
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)
}
})
}
}
},
fitHeight: {
label: "Fit height",
tool: Tools.CHECK_BUTTON,
value: this.state.size?.height === 'fit-content' || false,
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.state.attrs,
@@ -440,7 +487,7 @@ class Widget extends React.Component {
* this is a helper function to remove any non-serializable data associated with attrs
* eg: {"styling.backgroundColor": "#ffff", "layout": {layout: "flex", direction: "", grid: }}
*/
serializeAttrsValues = () => {
serializeAttrsValues(){
const serializeValues = (obj, currentPath = "") => {
const result = {}
@@ -524,11 +571,10 @@ class Widget extends React.Component {
}
setLayout(value) {
// FIXME: when the parent layout is place, the child widgets should have position absolute
const { layout, direction, grid = { rows: 1, cols: 1 }, gap = 10 } = value
console.log("layout value: ", value)
// console.log("layout value: ", value)
// FIXME: In grid layout the layout doesn't adapt to the size of the child if resized
let widgetStyle = {
...this.state.widgetInnerStyling,
display: layout !== Layouts.PLACE ? layout : "block",
@@ -537,7 +583,8 @@ class Widget extends React.Component {
flexWrap: "wrap",
gridTemplateColumns: "repeat(auto-fill, minmax(100px, 1fr))",
gridTemplateRows: "repeat(auto-fill, minmax(100px, 1fr))",
// gridAutoRows: "auto"
// gridAutoRows: 'minmax(100px, auto)', // Rows with minimum height of 100px, and grow to fit content
// gridAutoCols: 'minmax(100px, auto)', // Cols with minimum height of 100px, and grow to fit content
}
this.updateState({
@@ -647,7 +694,7 @@ class Widget extends React.Component {
*
* serialize data for saving
*/
serialize = () => {
serialize(){
// NOTE: when serializing make sure, you are only passing serializable objects not functions or other
return ({
zIndex: this.state.zIndex,
@@ -667,8 +714,9 @@ class Widget extends React.Component {
/**
* loads the data
* @param {object} data
* @param {() => void | undefined} callback - optional callback that will be called after load
*/
load = (data) => {
load(data, callback){
if (Object.keys(data).length === 0) return // no data to load
@@ -699,10 +747,9 @@ class Widget extends React.Component {
...restData,
...layoutUpdates
}
console.log("loaded layout2: ", newData)
this.setState(newData, () => {
// UPdates attrs
// Updates attrs
let newAttrs = { ...this.state.attrs }
// Iterate over each path in the updates object
@@ -723,7 +770,12 @@ class Widget extends React.Component {
nestedObject[lastKey].value = value
})
this.updateState({ attrs: newAttrs })
if (newAttrs?.styling?.backgroundColor){
// some widgets don't have background color
this.setWidgetInnerStyle("backgroundColor", newAttrs.styling.backgroundColor)
}
this.updateState({ attrs: newAttrs }, callback)
})
@@ -785,7 +837,7 @@ class Widget extends React.Component {
return
}
setOverElement(e.currentTarget) // provide context to the provider
setOverElement(this.elementRef.current) // provide context to the provider
let showDrop = {
allow: true,
@@ -858,8 +910,6 @@ class Widget extends React.Component {
allow: false,
show: false
}
}, () => {
console.log("droppable cleared: ", this.elementRef.current, this.state.showDroppableStyle)
})
@@ -923,16 +973,29 @@ class Widget extends React.Component {
}
handleDragLeave = (e, draggedElement) => {
handleDragLeave = (e, draggedElement, overElement) => {
// console.log("Left: ", e.currentTarget, e.relatedTarget, draggedElement)
e.preventDefault()
e.stopPropagation()
if (!e.currentTarget.contains(draggedElement)) {
const rect = this.getBoundingRect()
const {clientX, clientY} = e
const isInBoundingBox = (clientX >= rect.left && clientX <= rect.right &&
clientY >= rect.top && clientY <= rect.bottom)
// if (!e.currentTarget.contains(draggedElement)) {
if (!isInBoundingBox) {
// FIXME: if the mouse pointer is over this widget's child, then droppable from here
// only if the dragging element is not within the rect of this element remove the dragging rect
this.setState({
showDroppableStyle: {
allow: false,
show: false
}
}, () => {
// console.log("Drag left", this.state.showDroppableStyle)
})
}
@@ -977,6 +1040,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
let outerStyle = {
...this.state.widgetOuterStyling,
cursor: this.cursor,
@@ -984,8 +1050,8 @@ class Widget extends React.Component {
position: this.state.positionType, // don't change this if it has to be movable on the canvas
top: `${this.state.pos.y}px`,
left: `${this.state.pos.x}px`,
width: `${this.state.size.width}px`,
height: `${this.state.size.height}px`,
width: width,
height: height,
opacity: this.state.isDragging ? 0.3 : 1,
}
@@ -996,7 +1062,7 @@ class Widget extends React.Component {
<DragContext.Consumer>
{
({ draggedElement, widgetClass, onDragStart, onDragEnd, setOverElement }) => (
({ draggedElement, widgetClass, onDragStart, onDragEnd, overElement, setOverElement }) => (
<div data-widget-id={this.__id}
ref={this.elementRef}
@@ -1013,7 +1079,7 @@ class Widget extends React.Component {
onDrop={(e) => this.handleDropEvent(e, draggedElement, widgetClass)}
onDragEnter={(e) => this.handleDragEnter(e, draggedElement, setOverElement)}
onDragLeave={(e) => this.handleDragLeave(e, draggedElement)}
onDragLeave={(e) => this.handleDragLeave(e, draggedElement, overElement)}
onDragStart={(e) => this.handleDragStart(e, onDragStart)}
onDragEnd={(e) => this.handleDragEnd(onDragEnd)}

View File

@@ -132,7 +132,7 @@ class TkinterBase extends Widget {
* loads the data
* @param {object} data
*/
load = (data) => {
load(data, callback=null){
if (Object.keys(data).length === 0) return // no data to load
@@ -188,7 +188,13 @@ class TkinterBase extends Widget {
nestedObject[lastKey].value = value
})
this.updateState({ attrs: newAttrs })
if (newAttrs?.styling?.backgroundColor){
// TODO: find a better way to apply innerStyles
this.setWidgetInnerStyle("backgroundColor", newAttrs.styling.backgroundColor.value)
}
this.updateState({ attrs: newAttrs }, callback)
})

View File

@@ -69,7 +69,7 @@ class Button extends TkinterBase{
return (
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md
tw-border tw-border-solid tw-border-gray-400 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-flex tw-place-content-center tw-place-items-center tw-h-full tw-text-center" style={this.state.widgetInnerStyling}>
{/* {this.props.children} */}
<div className="tw-text-sm" style={{color: this.getAttrValue("styling.foregroundColor")}}>
{this.getAttrValue("buttonLabel")}

View File

@@ -10,7 +10,7 @@ class Frame extends TkinterBase{
super(props)
this.droppableTags = {
exclude: ["image", "video", "media"]
exclude: ["image", "video", "media", "toplevel"]
}
this.state = {

View File

@@ -47,7 +47,7 @@ class Label extends TkinterBase{
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#fff")
this.setAttrValue("styling.backgroundColor", "#fff0")
this.setWidgetInnerStyle("backgroundColor", "#fff0")
}
@@ -68,7 +68,7 @@ class Label extends TkinterBase{
renderContent(){
return (
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full 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-flex tw-place-content-center tw-place-items-center " style={this.state.widgetInnerStyling}>
{/* {this.props.children} */}
<div className="tw-text-sm" style={{color: this.getAttrValue("styling.foregroundColor")}}>
{this.getAttrValue("labelWidget")}

View File

@@ -50,4 +50,12 @@ export function removeKeyFromObject(path, _object) {
delete nestedObject[lastKey]
return newAttrs
}
}
export function isNumeric(str) {
if (typeof str != "string") return false // we only process strings!
return !isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
!isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail
}