working on custom tk

This commit is contained in:
paul
2024-09-30 15:54:09 +05:30
parent b8bd0c74a6
commit b2b6eb0b75
46 changed files with 3146 additions and 10 deletions

View File

@@ -0,0 +1,240 @@
import Tools from "../../../canvas/constants/tools"
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
import { CheckSquareFilled } from "@ant-design/icons"
import { TkinterWidgetBase } from "./base"
export class CheckBox extends TkinterWidgetBase{
static widgetType = "check_button"
constructor(props) {
super(props)
// const {layout, ...newAttrs} = this.state.attrs // Removes the layout attribute
let newAttrs = removeKeyFromObject("layout", this.state.attrs)
this.minSize = {width: 50, height: 30}
this.state = {
...this.state,
size: { width: 120, height: 30 },
widgetName: "Check box",
attrs: {
...newAttrs,
styling: {
...newAttrs.styling,
foregroundColor: {
label: "Foreground Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
value: "#000",
onChange: (value) => {
this.setWidgetInnerStyle("color", value)
this.setAttrValue("styling.foregroundColor", value)
}
}
},
checkLabel: {
label: "Check Label",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: {placeholder: "Button label", maxLength: 100},
value: "Checkbox",
onChange: (value) => this.setAttrValue("checkLabel", value)
},
defaultChecked: {
label: "Checked",
tool: Tools.CHECK_BUTTON, // the tool to display, can be either HTML ELement or a constant string
value: true,
onChange: (value) => this.setAttrValue("defaultChecked", value)
}
}
}
}
componentDidMount(){
super.componentDidMount()
// this.setAttrValue("styling.backgroundColor", "#fff")
this.setWidgetInnerStyle("backgroundColor", "#fff0")
}
generateCode(variableName, parent){
const labelText = this.getAttrValue("checkLabel")
const config = convertObjectToKeyValueString(this.getConfigCode())
const code = [
`${variableName} = tk.Checkbutton(master=${parent}, text="${labelText}")`,
`${variableName}.config(${config})`,
]
if (this.getAttrValue("defaultChecked")){
code.push(`${variableName}.select()`)
}
code.push(`${variableName}.${this.getLayoutCode()}`)
return code
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
const attrs = this.state.attrs
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
checkLabel: attrs.checkLabel,
size: toolBarAttrs.size,
...attrs,
})
}
renderContent(){
return (
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
style={this.getInnerRenderStyling()}
>
<div className="tw-flex tw-gap-2 tw-w-full tw-h-full tw-place-items-center tw-place-content-center">
<div className="tw-border-solid tw-border-[#D9D9D9] tw-border-2
tw-min-w-[20px] tw-min-h-[20px] tw-w-[20px] tw-h-[20px]
tw-text-blue-600 tw-flex tw-items-center tw-justify-center
tw-rounded-md tw-overflow-hidden">
{
this.getAttrValue("defaultChecked") === true &&
<CheckSquareFilled className="tw-text-[20px]" />
}
</div>
{this.getAttrValue("checkLabel")}
</div>
</div>
)
}
}
export class RadioButton extends TkinterWidgetBase{
// FIXME: the radio buttons are not visible because of the default heigh provided
static widgetType = "radio_button"
constructor(props) {
super(props)
this.minSize = {width: 50, height: 30}
this.state = {
...this.state,
size: { width: 80, height: 30 },
fitContent: { width: true, height: true },
widgetName: "Radio button",
attrs: {
...this.state.attrs,
radios: {
label: "Radio Group",
tool: Tools.INPUT_RADIO_LIST,
value: {inputs: ["default"], selectedRadio: -1},
onChange: ({inputs, selectedRadio}) => {
this.setAttrValue("radios", {inputs, selectedRadio})
}
}
}
}
}
componentDidMount(){
super.componentDidMount()
// this.setAttrValue("styling.backgroundColor", "#fff")
this.setWidgetInnerStyle("backgroundColor", "#fff0")
}
generateCode(variableName, parent){
const config = convertObjectToKeyValueString(this.getConfigCode())
const code = [
`${variableName}_var = tk.IntVar()`,
]
const radios = this.getAttrValue("radios")
radios.inputs.forEach((radio_text, idx) => {
const radioBtnVariable = `${variableName}_${idx}`
code.push(`\n`)
code.push(`${radioBtnVariable} = tk.Radiobutton(master=${parent}, variable=${variableName}_var, text="${radio_text}")`)
code.push(`${radioBtnVariable}.config(${config}, value=${idx})`)
code.push(`${radioBtnVariable}.${this.getLayoutCode()}`)
})
const defaultSelected = radios.selectedRadio
if (defaultSelected !== -1){
code.push(`${variableName}_var.set(${defaultSelected})`)
}
return code
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
const attrs = this.state.attrs
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
checkLabel: attrs.checkLabel,
size: toolBarAttrs.size,
...attrs,
})
}
renderContent(){
const {inputs, selectedRadio} = this.getAttrValue("radios")
return (
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
style={this.getInnerRenderStyling()}
>
<div className="tw-flex tw-flex-col tw-gap-2 tw-w-fit tw-h-fit">
{
inputs.map((value, index) => {
return (
<div key={index} className="tw-flex tw-gap-2 tw-w-full tw-h-full tw-place-items-center ">
<div className="tw-border-solid tw-border-[#D9D9D9] tw-border-2
tw-min-w-[20px] tw-min-h-[20px] tw-w-[20px] tw-h-[20px]
tw-text-blue-600 tw-flex tw-items-center tw-justify-center
tw-rounded-full tw-overflow-hidden tw-p-1">
{
selectedRadio === index &&
<div className="tw-rounded-full tw-bg-blue-600 tw-w-full tw-h-full">
</div>
}
</div>
<span className="tw-text-base" style={{color: this.state.widgetInnerStyling.foregroundColor}}>
{value}
</span>
</div>
)
})
}
</div>
</div>
)
}
}

View File

@@ -0,0 +1,601 @@
import { Layouts, PosType } from "../../../canvas/constants/layouts"
import Tools from "../../../canvas/constants/tools"
import Widget from "../../../canvas/widgets/base"
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
import { Tkinter_TO_WEB_CURSOR_MAPPING } from "../constants/cursor"
import { Tkinter_To_GFonts } from "../constants/fontFamily"
import { JUSTIFY, RELIEF } from "../constants/styling"
export class CustomTkBase extends Widget {
static requiredImports = ['import tkinter as tk']
static requirements = ['customtkinter']
constructor(props) {
super(props)
this.getLayoutCode = this.getLayoutCode.bind(this)
}
getLayoutCode(){
const {layout: parentLayout, direction, gap, align="start"} = this.getParentLayout()
const absolutePositioning = this.getAttrValue("positioning")
let layoutManager = `pack()`
if (parentLayout === Layouts.PLACE || absolutePositioning){
const config = {
x: this.state.pos.x,
y: this.state.pos.y,
}
config["width"] = this.state.size.width
config["height"] = this.state.size.height
// if (!this.state.fitContent.width){
// config["width"] = this.state.size.width
// }
// if (!this.state.fitContent.height){
// config["height"] = this.state.size.height
// }
const configStr = convertObjectToKeyValueString(config)
layoutManager = `place(${configStr})`
}else if (parentLayout === Layouts.FLEX){
const config = {
side: direction === "row" ? "tk.LEFT" : "tk.TOP",
}
if (gap > 0){
config["padx"] = gap
config["pady"] = gap
}
if (align === "start"){
config["anchor"] = "'nw'"
}else if (align === "center"){
config["anchor"] = "'center'"
}else if (align === "end"){
config["anchor"] = "'se'"
}
const fillX = this.getAttrValue("flexManager.fillX")
const fillY = this.getAttrValue("flexManager.fillY")
const expand = this.getAttrValue("flexManager.expand")
if (fillX){
config['fill'] = `"x"`
}
if (fillY){
config['fill'] = `"y"`
}
if (fillX && fillY){
config['fill'] = `"both"`
}
if (expand){
config['expand'] = "True"
}
layoutManager = `pack(${convertObjectToKeyValueString(config)})`
}else if (parentLayout === Layouts.GRID){
const row = this.getAttrValue("gridManager.row")
const col = this.getAttrValue("gridManager.column")
layoutManager = `grid(row=${row}, column=${col})`
}
return layoutManager
}
setParentLayout(layout){
if (!layout){
return {}
}
const {layout: parentLayout, direction, gap} = layout
// show attributes related to the layout manager
let updates = {
parentLayout: layout,
}
// this.removeAttr("gridManager")
// this.removeAttr("flexManager")
// this.removeAttr("positioning")
// remove gridManager, flexManager positioning
const {gridManager, flexManager, positioning, ...restAttrs} = this.state.attrs
if (parentLayout === Layouts.FLEX || parentLayout === Layouts.GRID) {
updates = {
...updates,
positionType: PosType.NONE,
}
// Allow optional absolute positioning if the parent layout is flex or grid
const updateAttrs = {
...restAttrs,
positioning: {
label: "Absolute positioning",
tool: Tools.CHECK_BUTTON,
value: false,
onChange: (value) => {
this.setAttrValue("positioning", value)
this.updateState({
positionType: value ? PosType.ABSOLUTE : PosType.NONE,
})
}
}
}
if (parentLayout === Layouts.FLEX){
updates = {
...updates,
attrs: {
...updateAttrs,
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,
}
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,
attrs: {
...updateAttrs,
gridManager: {
label: "Grid manager",
display: "horizontal",
row: {
label: "Row",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "width", max: 1000, min: 1 },
value: 1,
onChange: (value) => {
const previousRow = this.getWidgetOuterStyle("gridRow") || "1/1"
let [_row=1, rowSpan=1] = previousRow.replace(/\s+/g, '').split("/").map(Number)
if (value > rowSpan){
// rowSpan should always be greater than or eq to row
rowSpan = value
this.setAttrValue("gridManager.rowSpan", rowSpan)
}
this.setAttrValue("gridManager.row", value)
this.setWidgetOuterStyle("gridRow", `${value+' / '+rowSpan}`)
}
},
rowSpan: {
label: "Row span",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "height", max: 1000, min: 1 },
value: 1,
onChange: (value) => {
const previousRow = this.getWidgetOuterStyle("gridRow") || "1/1"
const [row=1, _rowSpan=1] = previousRow.replace(/\s+/g, '').split("/").map(Number)
if (value < row){
value = row + 1
}
this.setAttrValue("gridManager.rowSpan", value)
this.setWidgetOuterStyle("gridRow", `${row + ' / ' +value}`)
}
},
column: {
label: "Column",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "height", max: 1000, min: 1 },
value: 1,
onChange: (value) => {
const previousRow = this.getWidgetOuterStyle("gridColumn") || "1/1"
let [_col=1, colSpan=1] = previousRow.replace(/\s+/g, '').split("/").map(Number)
if (value > colSpan){
// The colSpan has always be equal or greater than col
colSpan = value
this.setAttrValue("gridManager.columnSpan", colSpan)
}
this.setAttrValue("gridManager.column", value)
this.setWidgetOuterStyle("gridColumn", `${value +' / ' + colSpan}`)
}
},
columnSpan: {
label: "Column span",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "height", max: 1000, min: 1 },
value: 1,
onChange: (value) => {
const previousCol = this.getWidgetOuterStyle("gridColumn") || "1/1"
const [col=1, _colSpan=1] = previousCol.replace(/\s+/g, '').split("/").map(Number)
if (value < col){
value = col + 1
}
this.setAttrValue("gridManager.columnSpan", value)
this.setWidgetOuterStyle("gridColumn", `${col + ' / ' + value}`)
}
},
}
}
}
}
} else if (parentLayout === Layouts.PLACE) {
updates = {
...updates,
positionType: PosType.ABSOLUTE
}
}
this.updateState(updates)
return updates
}
getInnerRenderStyling(){
let {width, height, minWidth, minHeight} = this.getRenderSize()
const {layout: parentLayout, direction, gap} = this.getParentLayout() || {}
if (parentLayout === Layouts.FLEX){
const fillX = this.getAttrValue("flexManager.fillX")
const fillY = this.getAttrValue("flexManager.fillY")
// This is needed if fillX or fillY is true, as the parent is applied flex-grow
if (fillX || fillY){
width = "100%"
height = "100%"
}
}
const styling = {
...this.state.widgetInnerStyling,
width,
height,
minWidth,
minHeight
}
return styling
}
/**
* loads the data
* @param {object} data
*/
load(data, callback=null){
if (Object.keys(data).length === 0) return // no data to load
data = {...data} // create a shallow copy
const {attrs, parentLayout=null, ...restData} = data
let layoutUpdates = {
parentLayout: parentLayout
}
if (parentLayout){
if (parentLayout.layout === Layouts.FLEX || parentLayout.layout === Layouts.GRID){
layoutUpdates = {
...layoutUpdates,
positionType: PosType.NONE
}
}else if (parentLayout.layout === Layouts.PLACE){
layoutUpdates = {
...layoutUpdates,
positionType: PosType.ABSOLUTE
}
}
}
const newData = {
...restData,
...layoutUpdates
}
this.setState(newData, () => {
let layoutAttrs = this.setParentLayout(parentLayout).attrs || {}
// UPdates attrs
let newAttrs = { ...this.state.attrs, ...layoutAttrs }
// Iterate over each path in the updates object
Object.entries(attrs).forEach(([path, value]) => {
const keys = path.split('.')
const lastKey = keys.pop()
// Traverse the nested object within attrs
let nestedObject = newAttrs
keys.forEach(key => {
nestedObject[key] = { ...nestedObject[key] } // Ensure immutability for each nested level
nestedObject = nestedObject[key]
})
// Set the value at the last key
if (nestedObject[lastKey])
nestedObject[lastKey].value = value
})
if (newAttrs?.styling?.backgroundColor){
// TODO: find a better way to apply innerStyles
this.setWidgetInnerStyle("backgroundColor", newAttrs.styling.backgroundColor.value)
}
this.updateState({ attrs: newAttrs }, callback)
})
}
}
// base for widgets that have common base properties such as bg, fg, cursor etc
export class CustomTkWidgetBase extends CustomTkBase{
constructor(props) {
super(props)
this.droppableTags = null // disables drops
const newAttrs = removeKeyFromObject("layout", this.state.attrs)
this.state = {
...this.state,
attrs: {
...newAttrs,
styling: {
...newAttrs.styling,
foregroundColor: {
label: "Foreground Color",
tool: Tools.COLOR_PICKER,
value: "#000",
onChange: (value) => {
this.setWidgetInnerStyle("color", value)
this.setAttrValue("styling.foregroundColor", value)
}
},
borderWidth: {
label: "Border thickness",
tool: Tools.NUMBER_INPUT,
toolProps: {min: 0, max: 10},
value: 0,
onChange: (value) => {
this.setWidgetInnerStyle("border", `${value}px solid black`)
this.setAttrValue("styling.borderWidth", value)
}
},
borderRadius: {
label: "Border radius",
tool: Tools.NUMBER_INPUT,
toolProps: {min: 0, max: 140},
value: 0,
onChange: (value) => {
this.setWidgetInnerStyle("borderRadius", `${value}px`)
this.setAttrValue("styling.borderRadius", value)
}
}
// justify: {
// label: "Justify",
// tool: Tools.SELECT_DROPDOWN,
// options: JUSTIFY.map((val) => ({value: val, label: val})),
// value: "",
// onChange: (value) => {
// this.setWidgetInnerStyle("text-align", value)
// this.setAttrValue("styling.justify", value)
// }
// }
},
padding: {
label: "padding",
padX: {
label: "Pad X",
tool: Tools.NUMBER_INPUT,
toolProps: {min: 1, max: 140},
value: null,
onChange: (value) => {
// this.setWidgetInnerStyle("paddingLeft", `${value}px`)
// this.setWidgetInnerStyle("paddingRight", `${value}px`)
const widgetStyle = {
...this.state.widgetInnerStyling,
paddingLeft: `${value}px`,
paddingRight: `${value}px`
}
this.setState({
widgetInnerStyling: widgetStyle
})
this.setAttrValue("padding.padX", value)
}
},
padY: {
label: "Pad Y",
tool: Tools.NUMBER_INPUT,
toolProps: {min: 1, max: 140},
value: null,
onChange: (value) => {
const widgetStyle = {
...this.state.widgetInnerStyling,
paddingTop: `${value}px`,
paddingBottom: `${value}px`
}
this.setState({
widgetInnerStyling: widgetStyle
})
this.setAttrValue("padding.padX", value)
}
},
},
font: {
label: "font",
fontFamily: {
label: "font family",
tool: Tools.SELECT_DROPDOWN,
options: Object.keys(Tkinter_To_GFonts).map((val) => ({value: val, label: val})),
value: "",
onChange: (value) => {
this.setWidgetInnerStyle("fontFamily", Tkinter_To_GFonts[value])
this.setAttrValue("font.fontFamily", value)
}
},
fontSize: {
label: "font size",
tool: Tools.NUMBER_INPUT,
toolProps: {min: 3, max: 140},
value: null,
onChange: (value) => {
this.setWidgetInnerStyle("fontSize", `${value}px`)
this.setAttrValue("font.fontSize", value)
}
}
},
cursor: {
label: "Cursor",
tool: Tools.SELECT_DROPDOWN,
toolProps: {placeholder: "select cursor"},
value: "",
options: Object.keys(Tkinter_TO_WEB_CURSOR_MAPPING).map((val) => ({value: val, label: val})),
onChange: (value) => {
this.setWidgetInnerStyle("cursor", Tkinter_TO_WEB_CURSOR_MAPPING[value])
this.setAttrValue("cursor", value)
}
},
}
}
this.getConfigCode = this.getConfigCode.bind(this)
}
getConfigCode(){
const config = {
fg_color: `"${this.getAttrValue("styling.backgroundColor")}"`,
text_color: `"${this.getAttrValue("styling.foregroundColor")}"`,
}
if (this.getAttrValue("styling.borderWidth"))
config["border_width"] = this.getAttrValue("styling.borderWidth")
if (this.getAttrValue("font.fontFamily") || this.getAttrValue("font.fontSize")){
config["font"] = `("${this.getAttrValue("font.fontFamily")}", ${this.getAttrValue("font.fontSize") || 12}, )`
}
if (this.getAttrValue("cursor"))
config["cursor"] = `"${this.getAttrValue("cursor")}"`
if (this.getAttrValue("padding.padX")){
config["padx"] = this.getAttrValue("padding.padX")
}
if (this.getAttrValue("padding.padY")){
config["pady"] = this.getAttrValue("padding.padY")
}
// FIXME: add width and height, the scales may not be correct as the width and height are based on characters in pack and grid not pixels
// if (!this.state.fitContent.width){
// config["width"] = this.state.size.width
// }
// if (!this.state.fitContent.height){
// config["height"] = this.state.size.height
// }
return config
}
}

View File

@@ -0,0 +1,84 @@
import Tools from "../../../canvas/constants/tools"
import { convertObjectToKeyValueString } from "../../../utils/common"
import { TkinterWidgetBase } from "./base"
class Button extends TkinterWidgetBase{
static widgetType = "button"
constructor(props) {
super(props)
this.state = {
...this.state,
size: { width: 80, height: 40 },
widgetName: "Button",
attrs: {
...this.state.attrs,
buttonLabel: {
label: "Button Label",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: {placeholder: "Button label", maxLength: 100},
value: "Button",
onChange: (value) => this.setAttrValue("buttonLabel", value)
}
}
}
}
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#E4E2E2")
}
generateCode(variableName, parent){
const labelText = this.getAttrValue("buttonLabel")
const config = convertObjectToKeyValueString(this.getConfigCode())
return [
`${variableName} = tk.Button(master=${parent}, text="${labelText}")`,
`${variableName}.config(${config})`,
`${variableName}.${this.getLayoutCode()}`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
buttonLabel: this.state.attrs.buttonLabel,
size: toolBarAttrs.size,
...this.state.attrs,
})
}
renderContent(){
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-flex tw-place-content-center tw-place-items-center tw-h-full tw-text-center"
style={this.getInnerRenderStyling()}>
{/* {this.props.children} */}
<div className="tw-text-sm" style={{color: this.getAttrValue("styling.foregroundColor")}}>
{this.getAttrValue("buttonLabel")}
</div>
</div>
</div>
)
}
}
export default Button

View File

@@ -0,0 +1,58 @@
import Widget from "../../../canvas/widgets/base"
import {TkinterBase} from "./base"
class Frame extends TkinterBase{
static widgetType = "frame"
constructor(props) {
super(props)
this.droppableTags = {
exclude: ["image", "video", "media", "toplevel", "main_window"]
}
this.state = {
...this.state,
fitContent: {width: true, height: true},
widgetName: "Frame"
}
}
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#EDECEC")
}
generateCode(variableName, parent){
const bg = this.getAttrValue("styling.backgroundColor")
return [
`${variableName} = tk.Frame(master=${parent})`,
`${variableName}.config(bg="${bg}")`,
`${variableName}.${this.getLayoutCode()}`
]
}
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.getInnerRenderStyling()}>
{this.props.children}
</div>
</div>
)
}
}
export default Frame

View File

@@ -0,0 +1,150 @@
import Tools from "../../../canvas/constants/tools"
import { convertObjectToKeyValueString } from "../../../utils/common"
import { TkinterWidgetBase } from "./base"
export class Input extends TkinterWidgetBase{
static widgetType = "entry"
constructor(props) {
super(props)
this.state = {
...this.state,
size: { width: 120, height: 40 },
widgetName: "Entry",
attrs: {
...this.state.attrs,
placeHolder: {
label: "PlaceHolder",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: {placeholder: "text", maxLength: 100},
value: "placeholder text",
onChange: (value) => this.setAttrValue("placeHolder", value)
}
}
}
}
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#fff")
}
generateCode(variableName, parent){
const placeHolderText = this.getAttrValue("placeHolder")
const config = convertObjectToKeyValueString(this.getConfigCode())
return [
`${variableName} = tk.Entry(master=${parent}, text="${placeHolderText}")`,
`${variableName}.config(${config})`,
`${variableName}.${this.getLayoutCode()}`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
placeHolder: this.state.attrs.placeHolder,
size: toolBarAttrs.size,
...this.state.attrs,
})
}
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-flex tw-place-items-center"
style={this.getInnerRenderStyling()}>
<div className="tw-text-sm tw-text-gray-300">
{this.getAttrValue("placeHolder")}
</div>
</div>
</div>
)
}
}
export class Text extends TkinterWidgetBase{
static widgetType = "Text"
constructor(props) {
super(props)
this.state = {
...this.state,
size: { width: 120, height: 80 },
attrs: {
...this.state.attrs,
// placeHolder: {
// label: "PlaceHolder",
// tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
// toolProps: {placeholder: "text", maxLength: 100},
// value: "placeholder text",
// onChange: (value) => this.setAttrValue("placeHolder", value)
// }
}
}
}
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#fff")
this.setWidgetName("text")
}
generateCode(variableName, parent){
const placeHolderText = this.getAttrValue("placeHolder")
const config = convertObjectToKeyValueString(this.getConfigCode())
return [
`${variableName} = tk.Text(master=${parent})`,
`${variableName}.config(${config})`,
`${variableName}.${this.getLayoutCode()}`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
placeHolder: this.state.attrs.placeHolder,
size: toolBarAttrs.size,
...this.state.attrs,
})
}
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.getInnerRenderStyling()}>
<div className="tw-text-sm tw-text-gray-300">
{this.getAttrValue("placeHolder")}
</div>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,142 @@
import Tools from "../../../canvas/constants/tools"
import { convertObjectToKeyValueString } from "../../../utils/common"
import { getPythonAssetPath } from "../../utils/pythonFilePath"
import { TkinterWidgetBase } from "./base"
class Label extends TkinterWidgetBase{
static widgetType = "label"
constructor(props) {
super(props)
this.state = {
...this.state,
widgetName: "Label",
size: { width: 80, height: 40 },
attrs: {
...this.state.attrs,
labelWidget: {
label: "Text",
tool: Tools.INPUT,
toolProps: {placeholder: "text", maxLength: 100},
value: "Label",
onChange: (value) => this.setAttrValue("labelWidget", value)
},
imageUpload: {
label: "Image",
tool: Tools.UPLOADED_LIST,
toolProps: {filterOptions: ["image/jpg", "image/jpeg", "image/png"]},
value: "",
onChange: (value) => this.setAttrValue("imageUpload", value)
},
}
}
}
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#E4E2E2")
// this.setWidgetName("label") // Don't do this this causes issues while loading data
}
getImports(){
const imports = super.getImports()
if (this.getAttrValue("imageUpload"))
imports.push("import os", "from PIL import Image, ImageTk", )
return imports
}
getRequirements(){
const requirements = super.getRequirements()
if (this.getAttrValue("imageUpload"))
requirements.push("pillow")
return requirements
}
generateCode(variableName, parent){
const labelText = this.getAttrValue("labelWidget")
const config = convertObjectToKeyValueString(this.getConfigCode())
const image = this.getAttrValue("imageUpload")
let labelInitialization = `${variableName} = tk.Label(master=${parent}, text="${labelText}")`
const code = []
if (image?.name){
code.push(`${variableName}_img = Image.open(${getPythonAssetPath(image.name, "image")})`)
code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
// code.push("\n")
labelInitialization = `${variableName} = tk.Label(master=${parent}, image=${variableName}_img, text="${labelText}", compound=tk.TOP)`
}
// code.push("\n")
code.push(labelInitialization)
return [
...code,
`${variableName}.config(${config})`,
`${variableName}.${this.getLayoutCode()}`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
labelWidget: this.state.attrs.labelWidget,
size: toolBarAttrs.size,
...this.state.attrs,
})
}
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"
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.getInnerRenderStyling()}>
{/* {this.props.children} */}
{
image && (
<img src={image.previewUrl} className="tw-bg-contain tw-w-full tw-h-full" />
)
}
<div className="" style={{color: this.getAttrValue("styling.foregroundColor")}}>
{this.getAttrValue("labelWidget")}
</div>
</div>
</div>
)
}
}
export default Label

View File

@@ -0,0 +1,93 @@
import Widget from "../../../canvas/widgets/base"
import Tools from "../../../canvas/constants/tools"
import { CustomTkBase } from "./base"
class MainWindow extends CustomTkBase{
static widgetType = "main_window"
constructor(props) {
super(props)
this.droppableTags = {
exclude: ["image", "video", "media", "main_window", "toplevel"]
}
this.state = {
...this.state,
size: { width: 700, height: 400 },
widgetName: "main",
attrs: {
...this.state.attrs,
title: {
label: "Window Title",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: {placeholder: "Window title", maxLength: 40},
value: "Main Window",
onChange: (value) => this.setAttrValue("title", value)
}
}
}
}
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#E4E2E2")
// this.setWidgetName("main") // Don't do this as this will cause conflicts while loading names
}
generateCode(variableName, parent){
const backgroundColor = this.getAttrValue("styling.backgroundColor")
return [
`${variableName} = tk.Tk()`,
`${variableName}.config(bg="${backgroundColor}")`,
`${variableName}.title("${this.getAttrValue("title")}")`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
title: this.state.attrs.title,
size: toolBarAttrs.size,
...this.state.attrs,
})
}
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-flex tw-w-full tw-h-[25px] tw-bg-[#c7c7c7] tw-p-1
tw-overflow-hidden tw-shadow-xl tw-place-items-center">
<div className="tw-text-sm">{this.getAttrValue("title")}</div>
<div className="tw-ml-auto tw-flex tw-gap-1 tw-place-items-center">
<div className="tw-bg-yellow-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
</div>
<div className="tw-bg-blue-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
</div>
<div className="tw-bg-red-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
</div>
</div>
</div>
<div className="tw-p-2 tw-w-full tw-relative tw-h-full tw-overflow-hidden tw-content-start"
style={this.state.widgetInnerStyling}>
{this.props.children}
</div>
</div>
)
}
}
export default MainWindow

View File

@@ -0,0 +1,139 @@
import Tools from "../../../canvas/constants/tools"
import { DownOutlined } from "@ant-design/icons"
import { TkinterWidgetBase} from "./base"
import { convertObjectToKeyValueString } from "../../../utils/common"
class OptionMenu extends TkinterWidgetBase{
static widgetType = "option_menu"
constructor(props) {
super(props)
// const {layout, ...newAttrs} = this.state.attrs // Removes the layout attribute
this.minSize = {width: 50, height: 30}
this.state = {
...this.state,
isDropDownOpen: false,
widgetName: "Option menu",
size: { width: 120, height: 'fit' },
attrs: {
...this.state.attrs,
defaultValue: {
label: "Default Value",
tool: Tools.INPUT,
value: "Select option",
onChange: ({inputs, selectedRadio}) => {
this.setAttrValue("options", {inputs, selectedRadio})
}
},
widgetOptions: {
label: "Options",
tool: Tools.INPUT_RADIO_LIST,
value: {inputs: ["option 1"], selectedRadio: -1},
onChange: ({inputs, selectedRadio}) => {
this.setAttrValue("widgetOptions", {inputs, selectedRadio})
}
}
}
}
}
componentDidMount(){
super.componentDidMount()
this.setWidgetInnerStyle("backgroundColor", "#fff")
}
generateCode(variableName, parent){
const config = convertObjectToKeyValueString(this.getConfigCode())
const defaultValue = this.getAttrValue("defaultValue")
const options = JSON.stringify(this.getAttrValue("widgetOptions").inputs)
const code = [
`${variableName}_options = ${options}`,
`${variableName}_var = tk.StringVar(value="${defaultValue || options.at(1) || ""}")`,
`${variableName} = tk.OptionMenu(${parent}, ${variableName}_var, *${variableName}_options)`
]
return [
...code,
`${variableName}.config(${config})`,
`${variableName}.${this.getLayoutCode()}`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
const attrs = this.state.attrs
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
checkLabel: attrs.checkLabel,
size: toolBarAttrs.size,
...attrs,
})
}
toggleDropDownOpen = () => {
this.setState((prev) => ({
isDropDownOpen: !prev.isDropDownOpen
}))
}
renderContent(){
const {inputs, selectedRadio} = this.getAttrValue("widgetOptions")
return (
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
style={this.getInnerRenderStyling()}
onClick={this.toggleDropDownOpen}
>
<div className="tw-flex tw-justify-between tw-gap-1">
{this.getAttrValue("defaultValue")}
<div className="tw-text-sm">
<DownOutlined />
</div>
</div>
{this.state.isDropDownOpen &&
<div className="tw-absolute tw-p-1 tw-bg-white tw-rounded-md tw-shadow-md tw-left-0
tw-w-full tw-h-fit "
style={{top: "calc(100% + 5px)"}}
>
{
inputs.map((value, index) => {
return (
<div key={index} className="tw-flex tw-gap-2 tw-w-full tw-h-full tw-place-items-center
hover:tw-bg-[#c5c5c573] tw-p-1">
<span className="tw-text-base" style={{color: this.state.widgetInnerStyling.foregroundColor}}>
{value}
</span>
</div>
)
})
}
</div>
}
</div>
)
}
}
export default OptionMenu

View File

@@ -0,0 +1,159 @@
import Widget from "../../../canvas/widgets/base"
import Tools from "../../../canvas/constants/tools"
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
import {TkinterBase, TkinterWidgetBase} from "./base"
class Slider extends TkinterWidgetBase{
static widgetType = "scale"
// FIXME: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use
constructor(props) {
super(props)
this.state = {
...this.state,
widgetName: "Scale",
size: { width: 'fit', height: 'fit' },
attrs: {
...this.state.attrs,
styling: {
...this.state.attrs.styling,
// TODO: trough color
troughColor: {
label: "Trough Color",
tool: Tools.COLOR_PICKER,
value: "#fff",
onChange: (value) => {
// this.setWidgetInnerStyle("color", value)
this.setAttrValue("styling.troughColor", value)
}
}
},
scale: {
label: "Scale",
display: "horizontal",
min: {
label: "Min",
tool: Tools.NUMBER_INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: { placeholder: "min" },
value: 0,
onChange: (value) => this.setAttrValue("scale.min", value)
},
max: {
label: "Max",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "max"},
value: 100,
onChange: (value) => this.setAttrValue("scale.max", value)
},
step: {
label: "Step",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "max", stringMode: true, step: "0.1"},
value: 1,
onChange: (value) => this.setAttrValue("scale.step", value)
},
default: {
label: "Default",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "max", stringMode: true, step: "0.1"},
value: 0,
onChange: (value) => this.setAttrValue("scale.default", value)
}
},
orientation: {
label: "Orientation",
tool: Tools.SELECT_DROPDOWN,
toolProps: {placeholder: "select orientation"},
value: "",
options: [{value: "horizontal", label: "horizontal"}, {value: "vertical", label: "vertical"}],
onChange: (value) => {
// const widgetStyling = {
// transformOrigin: "0 0",
// transform: value === "horizontal" ? "rotate(0deg)" : "rotate(90deg)"
// }
// this.setState((prev) => ({
// widgetOuterStyling: {...prev, ...widgetStyling}
// }))
// this.setWidgetOuterStyle("transform-origin", "0 0")
// this.setWidgetOuterStyle("transform", value === "horizontal" ? "rotate(0deg)" : "rotate(90deg)")
this.setAttrValue("orientation", value)
}
},
}
}
}
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#fff")
}
generateCode(variableName, parent){
// TODO: add orientation
const config = this.getConfigCode()
config["from_"] = this.getAttrValue("scale.min")
config["to"] = this.getAttrValue("scale.max")
config["resolution"] = this.getAttrValue("scale.step")
if (this.getAttrValue("orientation")){
config["orientation"] = this.getAttrValue("orientation")
}
const defaultValue = this.getAttrValue("scale.default")
return [
`${variableName}_var = tk.DoubleVar(value=${defaultValue})`,
`${variableName} = tk.Scale(master=${parent}, variable=${variableName}_var)`,
`${variableName}.config(${convertObjectToKeyValueString(config)})`,
`${variableName}.${this.getLayoutCode()}`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
placeHolder: this.state.attrs.placeHolder,
size: toolBarAttrs.size,
...this.state.attrs,
})
}
renderContent(){
return (
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
<div className="flex flex-col items-center justify-center h-screen
bg-gray-100" style={this.getInnerRenderStyling()}>
<div className="w-full max-w-md">
<input
type="range"
min={this.getAttrValue("scale.min")}
max={this.getAttrValue("scale.max")}
step={this.getAttrValue("scale.step")}
value={this.getAttrValue("scale.default")}
style={{backgroundColor: this.getAttrValue("styling.troughColor") }}
className="tw-pointer-events-none w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-500"
/>
</div>
</div>
</div>
)
}
}
export default Slider

View File

@@ -0,0 +1,128 @@
import Widget from "../../../canvas/widgets/base"
import Tools from "../../../canvas/constants/tools"
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
import { DownOutlined, UpOutlined } from "@ant-design/icons"
import {TkinterBase, TkinterWidgetBase} from "./base"
class SpinBox extends TkinterWidgetBase{
static widgetType = "spin_box"
constructor(props) {
super(props)
this.state = {
...this.state,
size: { width: 70, height: 'fit' },
widgetName: "Spin box",
attrs: {
...this.state.attrs,
spinProps: {
label: "Properties",
display: "horizontal",
min: {
label: "Min",
tool: Tools.NUMBER_INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: { placeholder: "min" },
value: 0,
onChange: (value) => this.setAttrValue("spinProps.min", value)
},
max: {
label: "Max",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "max"},
value: 100,
onChange: (value) => this.setAttrValue("spinProps.max", value)
},
step: {
label: "Step",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "max", stringMode: true, step: "0.1"},
value: 1,
onChange: (value) => this.setAttrValue("spinProps.step", value)
},
default: {
label: "Default",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "max", stringMode: true, step: "0.1"},
value: 0,
onChange: (value) => this.setAttrValue("spinProps.default", value)
}
},
}
}
}
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#fff")
}
generateCode(variableName, parent){
const min = this.getAttrValue("spinProps.min")
const max = this.getAttrValue("spinProps.max")
const step = this.getAttrValue("spinProps.step")
const defaultValue = this.getAttrValue("spinProps.default")
const config = {
from_: min,
to: max,
increment: step,
value: defaultValue,
...this.getConfigCode()
}
const code = []
let spinBox = `${variableName} = tk.Spinbox(master=${parent})`
if (defaultValue){
code.push(`${variableName}_var = tk.IntVar(${defaultValue})`)
spinBox = `${variableName} = tk.Spinbox(master=${parent}, textvariable=${variableName}_var)`
}
code.push(spinBox)
return [
...code,
`${variableName}.config(${convertObjectToKeyValueString(config)})`,
`${variableName}.${this.getLayoutCode()}`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
placeHolder: this.state.attrs.placeHolder,
size: toolBarAttrs.size,
...this.state.attrs,
})
}
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-flex tw-place-items-center tw-justify-between"
style={this.getInnerRenderStyling()}>
<div className="tw-text-sm ">
{this.getAttrValue("spinProps.default")}
</div>
<div className="tw-flex tw-flex-col tw-text-black tw-gap-1 tw-text-sm">
<UpOutlined />
<DownOutlined />
</div>
</div>
</div>
)
}
}
export default SpinBox

View File

@@ -0,0 +1,89 @@
import Widget from "../../../canvas/widgets/base"
import Tools from "../../../canvas/constants/tools"
class TopLevel extends Widget{
static widgetType = "toplevel"
constructor(props) {
super(props)
this.droppableTags = {
exclude: ["image", "video", "media", "main_window", "toplevel"]
}
this.maxSize = { width: 2000, height: 2000 } // disables resizing above this number
this.state = {
...this.state,
size: { width: 450, height: 200 },
widgetName: "top level",
attrs: {
...this.state.attrs,
title: {
label: "Window Title",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: {placeholder: "Window title", maxLength: 40},
value: "Top level",
onChange: (value) => this.setAttrValue("title", value)
}
}
}
}
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#E4E2E2")
}
generateCode(variableName, parent){
const backgroundColor = this.getAttrValue("styling.backgroundColor")
return [
`${variableName} = tk.Toplevel(master=${parent})`,
`${variableName}.config(bg="${backgroundColor}")`,
`${variableName}.title("${this.getAttrValue("title")}")`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
widgetName: toolBarAttrs.widgetName,
title: this.state.attrs.title,
size: toolBarAttrs.size,
...this.state.attrs,
})
}
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-flex tw-w-full tw-h-[25px] tw-bg-[#c7c7c7] tw-p-1
tw-overflow-hidden tw-shadow-xl tw-place-items-center">
<div className="tw-text-sm">{this.getAttrValue("title")}</div>
<div className="tw-ml-auto tw-flex tw-gap-1 tw-place-items-center">
<div className="tw-bg-yellow-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
</div>
<div className="tw-bg-blue-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
</div>
<div className="tw-bg-red-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
</div>
</div>
</div>
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start" style={this.state.widgetInnerStyling}>
{this.props.children}
</div>
</div>
)
}
}
export default TopLevel