Merge pull request #4 from PaulleDemon/customtk

Customtk
This commit is contained in:
Art/Paul
2024-09-30 22:27:12 +05:30
committed by GitHub
75 changed files with 3401 additions and 868 deletions

View File

@@ -16,6 +16,9 @@
</a>
</p>
<p><img src="./repo-assets/logo/PyUi.png" alt="font tester logo" width="100" height="100"></p>
Build Python GUI's with the ease of Canva
@@ -32,6 +35,8 @@ Build Python GUI's with the ease of Canva
- [Electron App - Commercial License](#electron-app---commercial-license)
## Docs - Getting started
Read the docs on the [Docs page](https://pyuibuilder-docs.pages.dev/)
## Features

12
docs/_coverpage.md Normal file
View File

@@ -0,0 +1,12 @@
![logo](./assets/logo/logo192.png)
# PyUIBuilder Docs
> A Free and open-source Python GUI builder tool.
- Simple and powerful
- Do more with plugins
- Framework independent, Generate code in multiple frameworks
[GitHub](https://github.com/PaulleDemon/PyUIBuilder)
[Get Started](#pyuibuilder-documentation)

BIN
docs/_media/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="icon" href="_media/favicon.ico">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
@@ -18,21 +19,24 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" integrity="sha512-dPXYcDub/aeb08c63jRq/k6GaKccl256JQy/AnOq7CAnEZ9FzSL9wSbcZkMp4R26vBsMLFYH4kQ67/bbV8XaCQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<nav>
<!-- <nav>
<a href="https://github.com/PaulleDemon/PyUIBuilder" target="_blank" rel="noopener noreferrer">
<!-- Github page -->
<i class="bi bi-github icon"></i>
</a>
</nav>
</nav> -->
<div id="app"></div>
<script>
window.$docsify = {
name: '',
repo: '',
name: 'PyUIBuilder',
repo: 'https://github.com/PaulleDemon/PyUIBuilder',
coverpage: true,
// themeColor: '#3F51B5',
// mergeNavbar: true,
homepage: 'intro.md', // You can set this to any file
// loadSidebar: true, // Ensure _sidebar.md is loaded if you want to use it
loadNavbar: true,
// loadNavbar: true,
autoHeader: true,
search: {
maxAge: 86400, // Expiration time, the default one day
paths: 'auto',
@@ -51,10 +55,21 @@
</script>
<style>
.markdown-section{
max-width: 1000px;
}
.icon{
font-size: 30px;
}
.cover.show {
}
.cover-main, .cover-main h1 span{
color: #000 !important;
}
img{
max-width: 850px;
}
@@ -81,7 +96,7 @@
padding: 15px;
margin: 15px 0;
border-radius: 4px;
/* background-color: #f6f8fa; */
background-color: #f2f2f239;
border-left: 4px solid;
}
@@ -125,7 +140,7 @@
<style>
.app-nav {
position: fixed;
/* position: fixed; */
background-color: var(--background, transparent);
width: 100%;
margin: 0;

View File

@@ -2,7 +2,7 @@
<div class="alert alert-note">
<div class="alert-title">NOTE ⚠️</div>
Work in progress. This page will be updated from time to time. This contains the basic documentation for PYUIBuilder
This page is still under work in progress. This page will be updated from time to time. This contains the basic documentation for PYUIBuilder
</div>
## UI Basics
@@ -66,6 +66,11 @@ or right-click -> delete
![deleted widget](./assets/delete.gif)
### Resizing widgets
You can resize the widgets by dragging the widgets. If the fit-width/fit-height is set to true, make sure to uncheck it before resizing.
### Variable names
To modify variable name, change the widget name attributes, if there are duplicate names,

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -16,7 +16,7 @@
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" integrity="sha512-dPXYcDub/aeb08c63jRq/k6GaKccl256JQy/AnOq7CAnEZ9FzSL9wSbcZkMp4R26vBsMLFYH4kQ67/bbV8XaCQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id="></script>
<script>
@@ -26,7 +26,6 @@
gtag('config', 'G-');
</script>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 21 KiB

BIN
repo-assets/logo/PyUi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -11,13 +11,19 @@ import UploadsContainer from './sidebar/uploadsContainer'
import WidgetsContainer from './sidebar/widgetsContainer'
import { DragProvider } from './components/draggable/draggableContext'
import TkinterWidgets from './frameworks/tkinter/sidebarWidgets'
import PluginsContainer from './sidebar/pluginsContainer'
import TkinterPluginWidgets from './frameworks/tkinter/sidebarPlugins'
import FrameWorks from './constants/frameworks'
import generateTkinterCode from './frameworks/tkinter/engine/code'
import { FileUploadProvider, useFileUploadContext } from './contexts/fileUploadContext'
import TemplatesContainer from './sidebar/templatesContainer'
import FrameWorks from './constants/frameworks'
import CustomTkWidgets from './frameworks/customtk/sidebarWidgets'
import CustomTkPluginWidgets from './frameworks/customtk/sidebarPlugins'
import generateCustomTkCode from './frameworks/customtk/engine/code'
import TkinterWidgets from './frameworks/tkinter/sidebarWidgets'
import TkinterPluginWidgets from './frameworks/tkinter/sidebarPlugins'
import generateTkinterCode from './frameworks/tkinter/engine/code'
function App() {
@@ -33,6 +39,7 @@ function App() {
// const [uploadedAssets, setUploadedAssets] = useState([]) // a global storage for assets, since redux can't store files(serialize files)
const [sidebarWidgets, setSidebarWidgets] = useState(TkinterWidgets || [])
const [sidebarPlugins, setSidebarPlugins] = useState(TkinterPluginWidgets || [])
const {uploadedAssets} = useFileUploadContext()
@@ -48,7 +55,7 @@ function App() {
{
name: "Plugins",
icon: <ProductFilled />,
content: <PluginsContainer sidebarContent={TkinterPluginWidgets}/>
content: <PluginsContainer sidebarContent={sidebarPlugins}/>
},
{
name: "Uploads",
@@ -135,7 +142,6 @@ function App() {
// }
const handleWidgetAddedToCanvas = (widgets) => {
console.log("canvas ref: ", canvasRef)
setCanvasWidgets(widgets)
}
@@ -144,15 +150,28 @@ function App() {
if (UIFramework === FrameWorks.TKINTER){
generateTkinterCode(projectName, canvasRef.current.getWidgets() || [], canvasRef.current.widgetRefs || [], uploadedAssets)
}
else if (UIFramework === FrameWorks.CUSTOMTK){
generateCustomTkCode(projectName, canvasRef.current.getWidgets() || [], canvasRef.current.widgetRefs || [], uploadedAssets)
}
}
const handleFrameworkChange = (framework) => {
if (framework === UIFramework) return
// canvasRef?.current?.closeToolBar()
canvasRef?.current?.clearSelections()
canvasRef?.current?.clearCanvas()
setUIFramework(framework)
if (framework === FrameWorks.TKINTER){
setSidebarPlugins(TkinterPluginWidgets)
setSidebarWidgets(TkinterWidgets)
}else if (framework === FrameWorks.CUSTOMTK){
setSidebarPlugins(CustomTkPluginWidgets)
setSidebarWidgets(CustomTkWidgets)
}
}

View File

@@ -112,6 +112,8 @@ class Canvas extends React.Component {
this.createWidget = this.createWidget.bind(this)
this.closeToolBar = this.closeToolBar.bind(this)
// this.updateCanvasDimensions = this.updateCanvasDimensions.bind(this)
}
@@ -153,6 +155,13 @@ class Canvas extends React.Component {
this.canvasRef.current.style.transform = `translate(${currentTranslate.x}px, ${currentTranslate.y}px) scale(${zoom})`
}
closeToolBar(){
this.setState({
toolbarAttrs: null,
toolbarOpen: false
})
}
/**
*
* @returns {import("./widgets/base").Widget[]}
@@ -770,7 +779,7 @@ class Canvas extends React.Component {
*/
handleAddWidgetChild = ({event, parentWidgetId, dragElementID, swap = false }) => {
console.log("event: ", event)
// console.log("event: ", event)
// widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" }
const dropWidgetObj = this.findWidgetFromListById(parentWidgetId)
// Find the dragged widget object

View File

@@ -165,6 +165,20 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
onChange={(value) => handleChange({ ...val.value, direction: value }, val.onChange)}
/>
</div>
<div className="tw-flex tw-flex-col tw-gap-1">
<span className="tw-text-sm">Align items</span>
<Select
options={[
{ value: "start", label: "Start" },
{ value: "center", label: "Center" },
{ value: "end", label: "End" },
]}
showSearch
value={val.value?.align || "start"}
placeholder={`${val.label}`}
onChange={(value) => handleChange({ ...val.value, align: value }, val.onChange)}
/>
</div>
<div className="tw-flex tw-flex-col">
<span className="tw-text-sm">Gap</span>
<InputNumber

View File

@@ -570,7 +570,7 @@ class Widget extends React.Component {
}
setLayout(value) {
const { layout, direction, grid = { rows: 1, cols: 1 }, gap = 10 } = value
const { layout, direction, grid = { rows: 1, cols: 1 }, gap = 10, align } = value
// console.log("layout value: ", value)
// FIXME: In grid layout the layout doesn't adapt to the size of the child if resized
@@ -586,6 +586,16 @@ class Widget extends React.Component {
// gridAutoCols: 'minmax(100px, auto)', // Cols with minimum height of 100px, and grow to fit content
}
if (align === "start"){
widgetStyle["alignItems"] = "flex-start"
}else if (align === "center"){
widgetStyle["alignItems"] = "center"
}else if (align === "end"){
widgetStyle["alignItems"] = "flex-end"
}else{
widgetStyle["alignItems"] = "unset"
}
this.updateState({
widgetInnerStyling: widgetStyle
})

View File

@@ -4,7 +4,8 @@ import Draggable from "./utils/draggableDnd"
import { GithubOutlined, GitlabOutlined, LinkOutlined,
AudioOutlined, FileTextOutlined,
DeleteFilled,
DeleteOutlined} from "@ant-design/icons"
DeleteOutlined,
GlobalOutlined} from "@ant-design/icons"
import DraggableWrapper from "./draggable/draggable"
import { Button } from "antd"
@@ -20,7 +21,7 @@ export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerR
}else if(host === "gitlab.com"){
return <GitlabOutlined />
}else{
return <LinkOutlined />
return <GlobalOutlined />
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -0,0 +1,43 @@
export const Tkinter_TO_WEB_CURSOR_MAPPING = {
"arrow": "default",
"circle": "wait",
"clock": "wait",
"cross": "crosshair",
"dotbox": "not-allowed",
"exchange": "alias",
"fleur": "move",
"heart": "default", // mapping doesn't exist
"man": "default",
"mouse": "default",
"pirate": "default",
"plus": "zoom-in",
"shuttle": "default",
"sizing": "se-resize",
"spider": "default",
"spraycan": "default",
"star": "default",
"target": "cell",
"tcross": "crosshair",
"trek": "default",
"watch": "wait",
"X_cursor": "not-allowed",
"bottom_left_corner": "sw-resize",
"bottom_right_corner": "se-resize",
"bottom_side": "s-resize",
"top_left_corner": "nw-resize",
"top_right_corner": "ne-resize",
"top_side": "n-resize",
"left_side": "w-resize",
"right_side": "e-resize",
"hand1": "pointer",
"hand2": "pointer",
"sb_h_double_arrow": "ew-resize",
"sb_v_double_arrow": "ns-resize",
"center_ptr": "default",
"question_arrow": "help",
"umbrella": "default",
"pencil": "copy",
}

View File

@@ -0,0 +1,20 @@
export const Tkinter_To_GFonts= {
"Arial": "Roboto",
"Courier": "Courier Prime",
"Comic Sans MS": "Comic Neue",
"Fixedsys": "Cousine",
"Helvetica": "Roboto",
"Times": "Merriweather",
"System": "Noto Sans",
"Verdana": "Open Sans",
"Symbol": "Noto Sans Symbols",
"TkDefaultFont": "Noto Sans",
"TkFixedFont": "Source Code Pro",
"TkMenuFont": "Roboto",
"TkHeadingFont": "Montserrat",
"TkTextFont": "Roboto",
"TkTooltipFont": "Lato",
}

View File

@@ -0,0 +1,24 @@
export const RELIEF = [
"FLAT",
"RAISED",
"SUNKEN",
"GROOVE",
"RIDGE"
]
export const JUSTIFY = [
"LEFT",
"CENTER",
"RIGHT"
]
export const ANCHOR = [
"n",
"s",
"e",
"w",
"center"
]

View File

@@ -0,0 +1,190 @@
import { createFilesAndDownload } from "../../../codeEngine/utils"
import MainWindow from "../widgets/mainWindow"
import { message } from "antd"
import TopLevel from "../widgets/toplevel"
// FIXME: if the toplevel comes first, before the MainWindow in widgetlist the root may become null
// Recursive function to generate the code list, imports, requirements, and track mainVariable
function generateCustomTkCodeList(widgetList = [], widgetRefs = [], parentVariable = null, mainVariable = "", usedVariableNames = new Set()) {
let variableMapping = new Map() // Map widget to variable { widgetId: variableName }
let imports = new Set([])
let requirements = new Set([])
let code = []
for (let widget of widgetList) {
const widgetRef = widgetRefs[widget.id].current
let varName = widgetRef.getVariableName()
// Add imports and requirements to sets
widgetRef.getImports().forEach(importItem => imports.add(importItem))
widgetRef.getRequirements().forEach(requirementItem => requirements.add(requirementItem))
// Set main variable if the widget is MainWindow
if (widget.widgetType === MainWindow) {
mainVariable = varName
}
// Ensure unique variable names across recursion
let originalVarName = varName
let count = 1;
// Check for uniqueness and update varName
while (usedVariableNames.has(varName)) {
varName = `${originalVarName}${count}`
count++
}
usedVariableNames.add(varName)
variableMapping.set(widget.id, varName) // Store the variable name by widget ID
// Determine the current parent variable from variableNames or fallback to parentVariable
let currentParentVariable = parentVariable || (variableMapping.get(widget.id) || null)
if (widget.widgetType === TopLevel){
// for top level set it to the main variable
// TODO: the toplevels parent should be determined other ways, suppose the top level has another toplevel
currentParentVariable = mainVariable
}
let widgetCode = widgetRef.generateCode(varName, currentParentVariable)
if (!(widgetCode instanceof Array)) {
throw new Error("generateCode() function should return array, each new line should be a new item")
}
// Add \n after every line
widgetCode = widgetCode.flatMap((item, index) => index < widgetCode.length - 1 ? [item, "\n"] : [item])
code.push(...widgetCode)
code.push("\n\n")
// Recursively handle child widgets
if (widget.children && widget.children.length > 0) {
// Pass down the unique names for children to prevent duplication
const childResult = generateCustomTkCodeList(widget.children, widgetRefs, varName, mainVariable, usedVariableNames)
// Merge child imports, requirements, and code
imports = new Set([...imports, ...childResult.imports])
requirements = new Set([...requirements, ...childResult.requirements])
code.push(...childResult.code)
mainVariable = childResult.mainVariable || mainVariable // the main variable is the main window variable
}
}
return {
imports: Array.from(imports),
code: code,
requirements: Array.from(requirements),
mainVariable
}
}
async function generateCustomTkCode(projectName, widgetList=[], widgetRefs=[], assetFiles){
// console.log("widgetList and refs", projectName, widgetList, widgetRefs, assetFiles)
let mainWindowCount = 0
// only MainWindow and/or the TopLevel should be on the canvas
const filteredWidgetList = widgetList.filter(widget => widget.widgetType === MainWindow || widget.widgetType === TopLevel)
for (let widget of filteredWidgetList){
if (widget.widgetType === MainWindow){
mainWindowCount += 1
}
if (mainWindowCount > 1){
message.error("Multiple instances of Main window found, delete one and try again.")
return
}
}
if (mainWindowCount === 0){
message.error("Aborting. No instances of Main window found. Add one and try again")
return
}
// widget - {id, widgetType: widgetComponentType, children: [], parent: "", initialData: {}}
const generatedObject = generateCustomTkCodeList(filteredWidgetList, widgetRefs, "", "")
const {code: codeLines, imports, requirements, mainVariable} = generatedObject
// TODO: avoid adding \n inside the list instead rewrite using code.join("\n")
const code = [
"# This code is generated by PyUIbuilder: https://github.com/PaulleDemon/PyUIBuilder",
"\n\n",
...imports.flatMap((item, index) => index < imports.length - 1 ? [item, "\n"] : [item]), //add \n to every line
"\n\n",
...codeLines,
"\n",
`${mainVariable}.mainloop()`,
]
console.log("Code: ", code.join(""), "\n\n requirements:", requirements.join("\n"))
message.info("starting zipping files, download will start in a few seconds")
const createFileList = [
{
fileData: code.join(""),
fileName: "main.py",
folder: ""
}
]
if (requirements.length > 0){
createFileList.push({
fileData: requirements.join("\n"),
fileName: "requirements.txt",
folder: ""
})
}
for (let asset of assetFiles){
if (asset.fileType === "image"){
createFileList.push({
fileData: asset.originFileObj,
fileName: asset.name,
folder: "assets/images"
})
}else if (asset.fileType === "video"){
createFileList.push({
fileData: asset.originFileObj,
fileName: asset.name,
folder: "assets/videos"
})
}else if (asset.fileType === "audio"){
createFileList.push({
fileData: asset.originFileObj,
fileName: asset.name,
folder: "assets/audio"
})
}else{
createFileList.push({
fileData: asset.originFileObj,
fileName: asset.name,
folder: "assets/others"
})
}
}
createFilesAndDownload(createFileList, projectName).then(() => {
message.success("Download complete")
}).catch(() => {
message.error("Error while downloading")
})
}
export default generateCustomTkCode

View File

@@ -0,0 +1,27 @@
import tkinter as tk
class EntryWithPlaceholder(tk.Entry):
def __init__(self, master=None, placeholder="placeholder", *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.placeholder = placeholder
self.placeholder_color = color
self.default_fg_color = self['fg']
self.bind("<FocusIn>", self.foc_in)
self.bind("<FocusOut>", self.foc_out)
self.put_placeholder()
def put_placeholder(self):
self.insert(0, self.placeholder)
self['fg'] = self.placeholder_color
def foc_in(self, *args):
if self['fg'] == self.placeholder_color:
self.delete('0', 'end')
self['fg'] = self.default_fg_color
def foc_out(self, *args):
if not self.get():
self.put_placeholder()

View File

@@ -0,0 +1,265 @@
import React from "react"
import { timePicker } from 'analogue-time-picker'
import Widget from "../../../canvas/widgets/base"
import Tools from "../../../canvas/constants/tools"
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
import { CustomTkBase } from "../widgets/base"
import "./styles/timepickerStyle.css"
const Themes = {
NAVY_BLUE: "Navy Blue",
DRACULA: "Dracula",
PURPLE: "Purple",
NONE: ""
}
class AnalogTimePicker extends CustomTkBase{
static widgetType = "analog_timepicker"
static requiredImports = [
...CustomTkBase.requiredImports,
'from tktimepicker import AnalogPicker, AnalogThemes, constants'
]
static requirements = ["tkTimePicker"]
constructor(props) {
super(props)
this.droppableTags = null
const newAttrs = removeKeyFromObject("layout", this.state.attrs)
this.timePicker = null
this.timePickerRef = React.createRef()
this.minSize = {width: 100, height: 100}
this.state = {
...this.state,
widgetName: "Timepicker",
size: { width: 250, height: 350 },
attrs: {
...newAttrs,
styling: {
theme:{
label: "Theme",
tool: Tools.SELECT_DROPDOWN,
toolProps: {placeholder: "select theme"},
value: "",
options: Object.values(Themes).map(val => ({value: val, label: val})),
onChange: (value) => this.handleThemeChange(value)
},
...newAttrs.styling,
clockColor: {
label: "Clock Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
value: "#EEEEEE",
onChange: (value) => {
this.setAttrValue("styling.clockColor", value)
}
},
displayColor: {
label: "Display Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
value: "#000",
onChange: (value) => {
this.setAttrValue("styling.displayColor", value)
}
},
numberColor: {
label: "Numbers Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
value: "#000",
onChange: (value) => {
this.setAttrValue("styling.numberColor", value)
}
},
handleColor: {
label: "Handle Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
value: "#000000",
onChange: (value) => {
this.setAttrValue("styling.handleColor", value)
}
},
},
clockMode:{
label: "Clock Mode",
tool: Tools.SELECT_DROPDOWN,
toolProps: {placeholder: "select mode", defaultValue: 12},
value: 12,
options: [12, 24].map(val => ({value: val, label: val})),
onChange: (value) => {
this.setAttrValue("clockMode", value)
if (value === 24){
// FIXME: the timepicker for 24 hrs also shows 12 hrs time
this.timePicker.set24h()
}else{
this.timePicker.set12h()
}
}
},
}
}
this.handleThemeChange = this.handleThemeChange.bind(this)
}
componentDidMount(){
super.componentDidMount()
this.timePicker = timePicker({
element: this.timePickerRef.current,
mode: "12",
width: this.state.size.width,
// height: this.state.size.height
})
// used to remove ok and cancel buttons
const timePickerBtns = this.timePickerRef.current.getElementsByClassName("atp-clock-btn")
for (let i = 0; i < timePickerBtns.length; i++) {
timePickerBtns[i].remove()
}
}
componentWillUnmount(){
this.timePicker.dispose()
}
setResize(pos, size){
super.setResize(pos, size)
this.timePicker.setWidth(size.width)
}
handleThemeChange(value){
this.setAttrValue("styling.theme", value)
if (value === Themes.NAVY_BLUE){
this.setAttrValue("styling.handleColor", "#009688")
this.setAttrValue("styling.displayColor", "#009688")
this.setAttrValue("styling.backgroundColor", "#fff")
this.setAttrValue("styling.clockColor", "#EEEEEE")
this.setAttrValue("styling.numberColor", "#000")
}else if (value === Themes.DRACULA){
this.setAttrValue("styling.handleColor", "#863434")
this.setAttrValue("styling.displayColor", "#404040")
this.setAttrValue("styling.backgroundColor", "#404040")
this.setAttrValue("styling.clockColor", "#363636")
this.setAttrValue("styling.numberColor", "#fff")
}else if (value === Themes.PURPLE){
this.setAttrValue("styling.handleColor", "#EE333D")
this.setAttrValue("styling.displayColor", "#71135C")
this.setAttrValue("styling.backgroundColor", "#4E0D3A")
this.setAttrValue("styling.clockColor", "#71135C")
this.setAttrValue("styling.numberColor", "#fff")
}
}
generateCode(variableName, parent){
const theme = this.getAttrValue("styling.theme")
const mode = this.getAttrValue("clockMode")
const bgColor = this.getAttrValue("styling.backgroundColor")
const clockColor = this.getAttrValue("styling.clockColor")
const displayColor = this.getAttrValue("styling.displayColor")
const numColor = this.getAttrValue("styling.numberColor")
const handleColor = this.getAttrValue("styling.handleColor")
const code = [
`${variableName} = AnalogPicker(parent=${parent}, type=${mode===12 ? "constants.HOURS12" : "constants.HOURS24"})`,
]
if (theme){
code.push(`${variableName}_theme = AnalogThemes(${variableName})`)
if (theme === Themes.NAVY_BLUE){
code.push(`${variableName}_theme.setNavyBlue()`)
}else if (theme === Themes.DRACULA){
code.push(`${variableName}_theme.setDracula()`)
}else if (theme === Themes.PURPLE){
code.push(`${variableName}_theme.setPurple()`)
}
}else{
const configAnalog = {
"canvas_bg": `"${bgColor}"`,
"textcolor": `"${numColor}"`,
"bg": `"${clockColor}"`,
"handcolor": `"${handleColor}"`,
"headcolor": `"${handleColor}"`
}
const displayConfig = {
bg: `"${displayColor}"`
}
code.push(`${variableName}.configAnalog(${convertObjectToKeyValueString(configAnalog)})`)
code.push(`${variableName}.configSpin(${convertObjectToKeyValueString(displayConfig)})`)
// code.push(`configAnalog(canvas_bg="${bgColor}", textcolor="${numColor}",
// bg="${clockColor}", handcolor="${handleColor}", headcolor="${handleColor}")`)
// code.push(`configSpin(bg="${displayColor}"`)
}
return [
...code,
`${variableName}.${this.getLayoutCode()}`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
size: toolBarAttrs.size,
...this.state.attrs,
})
}
renderContent(){
const timePickerStyling = {
'--bg': this.getAttrValue("styling.backgroundColor"),
'--clock-color': this.getAttrValue("styling.clockColor"),
'--number-color': this.getAttrValue("styling.numberColor"),
'--time-display': this.getAttrValue("styling.displayColor"),
'--main-handle-color': this.getAttrValue("styling.handleColor"),
}
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-flex tw-content-start tw-pointer-events-none"
style={timePickerStyling}
ref={this.timePickerRef}>
</div>
</div>
)
}
}
export default AnalogTimePicker

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -0,0 +1,96 @@
import React from "react"
import Widget from "../../../canvas/widgets/base"
import Tools from "../../../canvas/constants/tools"
import { removeKeyFromObject } from "../../../utils/common"
import MapImage from "./assets/map.png"
import { MinusOutlined, PlayCircleFilled, PlusOutlined } from "@ant-design/icons"
import { CustomTkBase } from "../widgets/base"
class MapView extends CustomTkBase{
static widgetType = "map_view"
static requiredImports = [
...CustomTkBase.requiredImports,
"import tkintermapview"
]
static requirements = ["tkintermapview"]
constructor(props) {
super(props)
this.droppableTags = null
const newAttrs = removeKeyFromObject("layout", this.state.attrs)
this.state = {
...this.state,
widgetName: "Map viewer",
size: { width: 400, height: 250 },
}
}
// componentDidMount(){
// super.componentDidMount()
// }
generateCode(variableName, parent){
return [
`${variableName} = tkintermapview.TkinterMapView(master=${parent})`,
`${variableName}.${this.getLayoutCode()}`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
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-h-full tw-content-start tw-pointer-events-none"
style={this.state.widgetInnerStyling}>
<div className="tw-relative tw-w-full tw-h-full">
<div className="tw-absolute tw-left-5 tw-top-3 tw-flex tw-flex-col tw-gap-2">
<div className="tw-text-white tw-bg-black tw-text-center
tw-p-[2px]
tw-w-[25px] tw-h-[25px] tw-rounded-md">
<PlusOutlined className="tw-text-xl"/>
</div>
<div className="tw-text-white tw-bg-black tw-text-center
tw-p-1
tw-w-[25px] tw-h-[25px] tw-rounded-md">
<MinusOutlined className="tw-text-xl"/>
</div>
</div>
<img src={MapImage} className="tw-w-full tw-h-full" />
</div>
</div>
</div>
)
}
}
export default MapView

View File

@@ -0,0 +1,237 @@
import React, { useEffect, useRef, useState } from "react"
import Widget from "../../../canvas/widgets/base"
import { removeKeyFromObject } from "../../../utils/common"
import MapImage from "./assets/map.png"
import { MinusOutlined, PlusOutlined } from "@ant-design/icons"
import { CustomTkBase } from "../widgets/base"
import Tools from "../../../canvas/constants/tools"
import { getPythonAssetPath } from "../../utils/pythonFilePath"
const ResizableTable = ({minRows=5, minCols=5}) => {
const [rows, setRows] = useState(minRows)
const [cols, setCols] = useState(minCols)
const containerRef = useRef(null)
useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
const { width, height } = entry.contentRect
// Set number of columns and rows based on widget width and height
const newCols = Math.max(minCols, Math.floor(width / 100)) // each column is 100px wide
const newRows = Math.max(minRows, Math.floor(height / 50)) // each row is 50px high
setCols(newCols)
setRows(newRows)
}
})
if (containerRef.current) {
resizeObserver.observe(containerRef.current); // Start observing the widget
}
return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current); // Stop observing when component unmounts
}
}
}, [containerRef])
return (
<div ref={containerRef} className="tw-w-full tw-h-full tw-rounded-md tw-border tw-border-solid tw-overflow-hidden">
<table className="tw-w-full tw-h-full">
<tbody className="">
{Array.from({ length: rows }).map((_, rowIndex) => (
<tr key={rowIndex} className="">
{Array.from({ length: cols }).map((_, colIndex) => (
<td key={colIndex} className="tw-border tw-border-solid tw-border-gray-400 tw-p-2">
{/* Row {rowIndex + 1}, Col {colIndex + 1} */}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
class PandasTable extends CustomTkBase{
static widgetType = "pandas_table"
static requiredImports = [
...CustomTkBase.requiredImports,
"import os",
"from pandastable import Table",
]
static requirements = ["pandastable"]
constructor(props) {
super(props)
this.droppableTags = null
const newAttrs = removeKeyFromObject("layout", this.state.attrs)
this.state = {
...this.state,
widgetName: "Pandas Table",
size: { width: 400, height: 250 },
attrs: {
...newAttrs,
styling: {
...newAttrs.styling,
textColor: {
label: "Text Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
value: "",
onChange: (value) => {
this.setAttrValue("styling.textColor", value)
}
},
cellBg: {
label: "Cell background",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
value: "",
onChange: (value) => {
this.setAttrValue("styling.textColor", value)
}
},
outlineColor: {
label: "Box outline color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
value: "",
onChange: (value) => {
this.setAttrValue("styling.textColor", value)
}
},
},
defaultTable: {
label: "Default table",
tool: Tools.UPLOADED_LIST,
toolProps: {filterOptions: ["text/csv"]},
value: "",
onChange: (value) => this.setAttrValue("defaultTable", value)
},
enableEdit: {
label: "Enable editing",
tool: Tools.CHECK_BUTTON,
value: false,
onChange: (value) => {
this.setAttrValue("enableEdit", value)
}
},
}
}
}
// componentDidMount(){
// super.componentDidMount()
// this.setWidgetName("Pandas Table")
// this.setAttrValue("styling.backgroundColor", "#E4E2E2")
// }
generateCode(variableName, parent){
const defaultTable = this.getAttrValue("defaultTable")
const textColor = this.getAttrValue("styling.textColor")
const cellBg = this.getAttrValue("styling.cellBg")
const outlineColor = this.getAttrValue("styling.outlineColor")
const enableEdit = this.getAttrValue("enableEdit")
const code = [
`${variableName}_table_frame = ctk.CTkFrame(master=${parent})`,
`${variableName}_table_frame.${this.getLayoutCode()}`,
`${variableName} = Table(parent=${variableName}_table_frame)`,
`${variableName}.editable = ${enableEdit ? "True" : "False"}`,
]
if (textColor){
code.push(`${variableName}.textColor = "${textColor}"`)
}
if (cellBg){
code.push(`${variableName}.cellbackgr = "${cellBg}"`)
}
if (outlineColor){
code.push(`${variableName}.boxoutlinecolor = "${outlineColor}"`)
}
if (defaultTable){
code.push(`${variableName}.importCSV(${getPythonAssetPath(defaultTable.name, "text/csv")})`)
}
return [
...code,
`${variableName}.show()`,
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
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-h-full tw-content-start tw-pointer-events-none"
style={this.state.widgetInnerStyling}>
<ResizableTable />
</div>
</div>
)
}
}
export default PandasTable
/**
* {'align': 'w',
'cellbackgr': '#F4F4F3',
'cellwidth': 80,
'floatprecision': 2,
'thousandseparator': '',
'font': 'Arial',
'fontsize': 12,
'fontstyle': '',
'grid_color': '#ABB1AD',
'linewidth': 1,
'rowheight': 22,
'rowselectedcolor': '#E4DED4',
'textcolor': 'black'}
*/

View File

@@ -0,0 +1,28 @@
.atp-time {
background-color: var(--time-display, black) !important;
text-align: center !important;
font-size: 35px;
}
.atp-clock-cnt {
background-color: var(--bg, white) !important;
}
.atp-face-color {
background-color: var(--clock-color, #EEEEEE) !important;
}
.atp-n {
color: var(--number-color, #000) !important;
}
/* .atp-color--primary {
background-color: var(--main-handle-color, #000000c0) !important;
} */
.atp-h-cnt-cnt .atp-h-ctn, .atp-h, .atp-b, .atp-h-dot{
/* affects only the handle*/
background-color: var(--main-handle-color, #000000c0) !important;
}

View File

@@ -0,0 +1,126 @@
import React from "react"
import Tools from "../../../canvas/constants/tools"
import { removeKeyFromObject } from "../../../utils/common"
import VideoImage from "./assets/video.jpg"
import { PlayCircleFilled } from "@ant-design/icons"
import { CustomTkBase } from "../widgets/base"
import { getPythonAssetPath } from "../../utils/pythonFilePath"
class VideoPlayer extends CustomTkBase{
static widgetType = "video_player"
static requiredImports = [
...CustomTkBase.requiredImports,
"import os",
"from tkVideoPlayer import TkinterVideo"
]
static requirements = ["tkvideoplayer"]
constructor(props) {
super(props)
this.droppableTags = null
const newAttrs = removeKeyFromObject("layout", this.state.attrs)
this.state = {
...this.state,
size: { width: 350, height: 200 },
widgetName: "Video player",
attrs: {
...newAttrs,
play: {
label: "Start playing",
tool: Tools.CHECK_BUTTON,
value: false,
onChange: (value) => {
this.setAttrValue("play", value)
}
},
defaultVideo: {
label: "Video",
tool: Tools.UPLOADED_LIST,
toolProps: {filterOptions: ["video/mp4", "video/webm", "video/m4v"]},
value: "",
onChange: (value) => this.setAttrValue("defaultVideo", value)
},
}
}
}
componentDidMount(){
super.componentDidMount()
// this.setAttrValue("styling.backgroundColor", "#E4E2E2")
}
generateCode(variableName, parent){
const defaultVideo = this.getAttrValue("defaultVideo")
const play = this.getAttrValue("play")
const code = [
`${variableName} = TkinterVideo(master=${parent}, scaled=True)`,
]
if (defaultVideo){
code.push(`${variableName}.load(${getPythonAssetPath(defaultVideo.name, "video")})`)
}
if (play){
code.push(`${variableName}.play()`)
}
return [
...code,
`${variableName}.${this.getLayoutCode()}`
]
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
size: toolBarAttrs.size,
...this.state.attrs,
})
}
renderContent(){
// const defaultVideo = this.getAttrValue("defaultVideo")
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 tw-pointer-events-none"
style={this.state.widgetInnerStyling}>
<div className="tw-relative tw-w-full tw-h-full">
<div className="tw-absolute tw-text-white tw-left-1/2 tw-top-1/2
tw--translate-x-1/2 tw--translate-y-1/2">
<PlayCircleFilled className="tw-text-4xl"/>
</div>
<img src={VideoImage} className="tw-w-full tw-h-full" />
</div>
</div>
</div>
)
}
}
export default VideoPlayer

View File

@@ -0,0 +1,58 @@
import ClockImage from "./assets/widgets/plugins/clock.png"
import VideoImage from "./assets/widgets/plugins/video.png"
import MapImage from "./assets/widgets/plugins/map.png"
import DataTableImage from "./assets/widgets/plugins/tables.png"
import AnalogTimePicker from "./plugins/analogTimepicker"
import VideoPlayer from "./plugins/videoPlayer"
import MapView from "./plugins/mapview"
import PandasTable from "./plugins/pandasTable"
const CustomTkPluginWidgets = [
{
name: "Analog TimePicker",
img: ClockImage,
link: "https://github.com/PaulleDemon/tkTimePicker",
widgetClass: AnalogTimePicker,
license: {
name: "MIT",
url: ""
}
},
{
name: "Video Player",
img: VideoImage,
link: "https://github.com/PaulleDemon/tkVideoPlayer",
widgetClass: VideoPlayer,
license: {
name: "MIT",
url: ""
}
},
{
name: "Map viewer",
img: MapImage,
link: "https://github.com/TomSchimansky/TkinterMapView",
widgetClass: MapView,
license: {
name: "CC0 1.0",
url: "https://github.com/TomSchimansky/TkinterMapView/blob/main/LICENSE.txt"
}
},
{
name: "Pandas Table",
img: DataTableImage,
link: "https://github.com/dmnfarrell/pandastable",
widgetClass: PandasTable,
license: {
name: "GPL v3",
url: "https://github.com/dmnfarrell/pandastable/blob/master/LICENSE"
}
},
]
export default CustomTkPluginWidgets

View File

@@ -0,0 +1,131 @@
import MainWindow from "./widgets/mainWindow"
import TopLevel from "./widgets/toplevel"
import Frame from "./widgets/frame"
import Label from "./widgets/label"
import Button from "./widgets/button"
import OptionMenu from "./widgets/optionMenu"
import Slider from "./widgets/slider"
import { CheckBox, RadioButton } from "./widgets/ checkButton"
import { Input, Text } from "./widgets/input"
import SpinBox from "./widgets/spinBox"
import MainWindowImage from "./assets/widgets/main/mainwindow2.png"
import TopLevelImage from "./assets/widgets/main/Toplevel2.png"
import FrameImage from "./assets/widgets/main/frame2.png"
import LabelImage from "./assets/widgets/main/label.png"
import ButtonImage from "./assets/widgets/main/button2.png"
import InputImage from "./assets/widgets/main/input.png"
import TextAreaImage from "./assets/widgets/main/textarea.png"
import SliderImage from "./assets/widgets/main/slider.png"
import DropDownImage from "./assets/widgets/main/dropdown.png"
import CheckButtonImage from "./assets/widgets/main/check.png"
import RadioButtonImage from "./assets/widgets/main/radio.png"
import SpinBoxImage from "./assets/widgets/main/spinbox.png"
const CustomTkWidgets = [
{
name: "Main window",
img: MainWindowImage,
link: "https://customtkinter.tomschimansky.com/documentation/windows",
widgetClass: MainWindow
},
{
name: "Top Level",
img: TopLevelImage,
link: "https://customtkinter.tomschimansky.com/documentation/windows",
widgetClass: TopLevel
},
{
name: "Frame",
img: FrameImage,
link: "https://customtkinter.tomschimansky.com/documentation/widgets/frame",
widgetClass: Frame
},
{
name: "Label",
img: LabelImage,
link: "https://customtkinter.tomschimansky.com/documentation/widgets/label",
widgetClass: Label
},
{
name: "Button",
img: ButtonImage,
link: "https://customtkinter.tomschimansky.com/documentation/widgets/button",
widgetClass: Button
},
{
name: "Entry",
img: InputImage,
link: "https://customtkinter.tomschimansky.com/documentation/widgets/entry",
widgetClass: Input
},
{
name: "Text",
img: TextAreaImage,
link: "https://customtkinter.tomschimansky.com/documentation/widgets/textbox",
widgetClass: Text
},
{
name: "CheckBox",
img: CheckButtonImage,
link: "https://customtkinter.tomschimansky.com/documentation/widgets/checkbox",
widgetClass: CheckBox
},
{
name: "Radio button",
img: RadioButtonImage,
link: "https://customtkinter.tomschimansky.com/documentation/widgets/radiobutton",
widgetClass: RadioButton
},
{
name: "Slider",
img: SliderImage,
link: "https://customtkinter.tomschimansky.com/documentation/widgets/slider",
widgetClass: Slider
},
{
name: "Option Menu",
img: DropDownImage,
link: "https://customtkinter.tomschimansky.com/documentation/widgets/optionmenu",
widgetClass: OptionMenu
},
// {
// name: "Spinbox",
// img: SpinBoxImage,
// link: "https://github.com",
// widgetClass: SpinBox
// },
]
export default CustomTkWidgets
/**
* widgets = {
"Tk": set(),
"Label": set(),
"Button": set(),
"Entry": set(),
"CheckButton": set(),
"RadioButton": set(),
"Scale": set(),
"ListBox": set(),
"Frame": set(),
"LabelFrame": set(),
"PanedWindow": set(),
"SpinBox": set(),
"OptionMenu": set(),
"Canvas": set(),
"TopLevel": set(),
"Message": set(),
"Menu": set(),
"MenuButton": set(),
"ScrollBar": set(),
"Text": set()
}
*/

View File

@@ -0,0 +1,246 @@
import Tools from "../../../canvas/constants/tools"
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
import { CheckSquareFilled } from "@ant-design/icons"
import { CustomTkWidgetBase } from "./base"
export class CheckBox extends CustomTkWidgetBase{
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 = this.getConfigCode()
const code = [
`${variableName} = ctk.CTkCheckBox(master=${parent}, text="${labelText}")`,
`${variableName}.configure(${convertObjectToKeyValueString(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 CustomTkWidgetBase{
// 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 {border_width, ...config} = this.getConfigCode()
if (border_width){
// there is no border width in RadioButton
config["border_width_checked"] = border_width
}
const code = [
`${variableName}_var = ctk.IntVar()`,
]
const radios = this.getAttrValue("radios")
// FIXME: Error: ValueError: ['value'] are not supported arguments. Look at the documentation for supported arguments.
radios.inputs.forEach((radio_text, idx) => {
const radioBtnVariable = `${variableName}_${idx}`
code.push(`\n`)
code.push(`${radioBtnVariable} = ctk.CTkRadioButton(master=${parent}, variable=${variableName}_var, text="${radio_text}", value=${idx})`)
code.push(`${radioBtnVariable}.configure(${convertObjectToKeyValueString(config)})`)
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,622 @@
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 customtkinter as ctk']
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" ? "ctk.LEFT" : "ctk.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)
}
},
borderColor: {
label: "Border Color",
tool: Tools.COLOR_PICKER,
value: "#000",
onChange: (value) => {
this.setWidgetInnerStyle("borderColor", value)
this.setAttrValue("styling.borderColor", 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")}"`,
}
if (this.getAttrValue("styling.foregroundColor")){
config["text_color"] = `"${this.getAttrValue("styling.foregroundColor")}"`
}
if (this.getAttrValue("styling.borderRadius")){
config["corner_radius"] = this.getAttrValue("styling.borderRadius")
}
if (this.getAttrValue("styling.borderColor")){
config["border_color"] = `"${this.getAttrValue("styling.borderColor")}"`
}
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,85 @@
import Tools from "../../../canvas/constants/tools"
import { convertObjectToKeyValueString } from "../../../utils/common"
import { CustomTkWidgetBase } from "./base"
class Button extends CustomTkWidgetBase{
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", "#029CFF")
this.setAttrValue("styling.foregroundColor", "#fff")
}
generateCode(variableName, parent){
const labelText = this.getAttrValue("buttonLabel")
const config = convertObjectToKeyValueString(this.getConfigCode())
return [
`${variableName} = ctk.CTkButton(master=${parent}, text="${labelText}")`,
`${variableName}.configure(${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 { CustomTkBase } from "./base"
class Frame extends CustomTkBase{
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} = ctk.CTkFrame(master=${parent})`,
`${variableName}.configure(fg_color="${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 { CustomTkWidgetBase } from "./base"
export class Input extends CustomTkWidgetBase{
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} = ctk.CTkEntry(master=${parent}, placeholder_text="${placeHolderText}")`,
`${variableName}.configure(${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 CustomTkWidgetBase{
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} = ctk.CTkTextbox(master=${parent})`,
`${variableName}.configure(${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,150 @@
import Tools from "../../../canvas/constants/tools"
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
import { getPythonAssetPath } from "../../utils/pythonFilePath"
import { CustomTkWidgetBase } from "./base"
class Label extends CustomTkWidgetBase{
static widgetType = "label"
constructor(props) {
super(props)
// border color and width is not available for label in customtkinter
let newAttrs = removeKeyFromObject("styling.borderColor", this.state.attrs)
newAttrs = removeKeyFromObject("styling.borderWidth", newAttrs)
this.state = {
...this.state,
widgetName: "Label",
size: { width: 80, height: 40 },
attrs: {
...newAttrs,
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")
// Border color and border width are not implemented for label
const {border_color, border_width, ...config} = this.getConfigCode()
const image = this.getAttrValue("imageUpload")
// console.log("Object: ", config)
let labelInitialization = `${variableName} = ctk.CTkLabel(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} = ctk.CTkLabel(master=${parent}, image=${variableName}_img, text="${labelText}", compound=ctk.TOP)`
}
// code.push("\n")
code.push(labelInitialization)
return [
...code,
`${variableName}.configure(${convertObjectToKeyValueString(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", "#23272D")
// 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} = ctk.CTk()`,
`${variableName}.configure(fg_color="${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,143 @@
import Tools from "../../../canvas/constants/tools"
import { DownOutlined } from "@ant-design/icons"
import { CustomTkWidgetBase} from "./base"
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
class OptionMenu extends CustomTkWidgetBase{
static widgetType = "option_menu"
constructor(props) {
super(props)
// const {layout, ...newAttrs} = this.state.attrs // Removes the layout attribute
this.minSize = {width: 50, height: 30}
let newAttrs = removeKeyFromObject("styling.borderColor", this.state.attrs)
newAttrs = removeKeyFromObject("styling.borderWidth", newAttrs)
this.state = {
...this.state,
isDropDownOpen: false,
widgetName: "Option menu",
size: { width: 120, height: 30 },
fitContent: { width: true, height: true },
attrs: {
...newAttrs,
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 = this.getConfigCode()
const defaultValue = this.getAttrValue("defaultValue")
const options = this.getAttrValue("widgetOptions").inputs
const code = [
`${variableName}_options = ${JSON.stringify(options)}`,
`${variableName}_var = ctk.StringVar(value="${options.at(1) || defaultValue || ''}")`,
`${variableName} = ctk.CTkOptionMenu(${parent}, variable=${variableName}_var, values=${variableName}_options)`
]
return [
...code,
`${variableName}.configure(${convertObjectToKeyValueString(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,165 @@
import Widget from "../../../canvas/widgets/base"
import Tools from "../../../canvas/constants/tools"
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
import { CustomTkWidgetBase } from "./base"
class Slider extends CustomTkWidgetBase{
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)
let newAttrs = removeKeyFromObject("styling.foregroundColor", this.state.attrs)
this.state = {
...this.state,
widgetName: "Scale",
size: { width: 120, height: 10 },
fitContent: {width: true, height: true},
attrs: {
...newAttrs,
styling: {
...newAttrs.styling,
// TODO: trough color
progressColor: {
label: "Progress Color",
tool: Tools.COLOR_PICKER,
value: "#029CFF",
onChange: (value) => {
// this.setWidgetInnerStyle("color", value)
this.setAttrValue("styling.progressColor", 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["number_of_steps"] = this.getAttrValue("scale.step")
config["progress_color"] = `"${this.getAttrValue("styling.progressColor")}"`
if (this.getAttrValue("orientation")){
config["orientation"] = this.getAttrValue("orientation")
}
const defaultValue = this.getAttrValue("scale.default")
return [
`${variableName}_var = ctk.DoubleVar(value=${defaultValue})`,
`${variableName} = ctk.CTkSlider(master=${parent}, variable=${variableName}_var)`,
`${variableName}.configure(${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 { CustomTkWidgetBase } from "./base"
// TODO: https://github.com/TomSchimansky/CustomTkinter/wiki/Create-new-widgets-(Spinbox)
class SpinBox extends CustomTkWidgetBase{
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}.configure(${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", "#23272D")
}
generateCode(variableName, parent){
const backgroundColor = this.getAttrValue("styling.backgroundColor")
return [
`${variableName} = ctk.CTkToplevel(master=${parent})`,
`${variableName}.configure(fg_color="${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

View File

@@ -12,4 +12,13 @@ export const JUSTIFY = [
"LEFT",
"CENTER",
"RIGHT"
]
export const ANCHOR = [
"n",
"s",
"e",
"w",
"center"
]

View File

@@ -85,7 +85,7 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[], assetFiles){
console.log("widgetList and refs", projectName, widgetList, widgetRefs, assetFiles)
// console.log("widgetList and refs", projectName, widgetList, widgetRefs, assetFiles)
let mainWindowCount = 0
@@ -138,7 +138,6 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[], as
}
]
console.log("requirements: ", requirements)
if (requirements.length > 0){
createFileList.push({

View File

@@ -84,7 +84,7 @@ class AnalogTimePicker extends TkinterBase{
handleColor: {
label: "Handle Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
value: "#000000c0",
value: "#000000",
onChange: (value) => {
this.setAttrValue("styling.handleColor", value)
}
@@ -178,7 +178,7 @@ class AnalogTimePicker extends TkinterBase{
const handleColor = this.getAttrValue("styling.handleColor")
const code = [
`${variableName} = AnalogPicker(master=${parent}, type=${mode===12 ? "constants.HOURS12" : "constants.HOURS24"})`,
`${variableName} = AnalogPicker(parent=${parent}, type=${mode===12 ? "constants.HOURS12" : "constants.HOURS24"})`,
]
if (theme){
@@ -198,7 +198,7 @@ class AnalogTimePicker extends TkinterBase{
"canvas_bg": `"${bgColor}"`,
"textcolor": `"${numColor}"`,
"bg": `"${clockColor}"`,
"handlecolor": `"${handleColor}"`,
"handcolor": `"${handleColor}"`,
"headcolor": `"${handleColor}"`
}

View File

@@ -157,7 +157,10 @@ class PandasTable extends TkinterBase{
const enableEdit = this.getAttrValue("enableEdit")
const code = [
`${variableName} = Table(master=${parent})`,
`${variableName}_table_frame = tk.Frame(master=${parent})`,
`${variableName}_table_frame.${this.getLayoutCode()}`,
`${variableName} = Table(parent=${variableName}_table_frame)`,
`${variableName}.editable = ${enableEdit ? "True" : "False"}`,
]
@@ -179,7 +182,6 @@ class PandasTable extends TkinterBase{
return [
...code,
`${variableName}.show()`,
`${variableName}.${this.getLayoutCode()}`
]
}

View File

@@ -29,73 +29,73 @@ const TkinterWidgets = [
{
name: "Main window",
img: MainWindowImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: MainWindow
},
{
name: "Top Level",
img: TopLevelImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: TopLevel
},
{
name: "Frame",
img: FrameImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: Frame
},
{
name: "Label",
img: LabelImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: Label
},
{
name: "Button",
img: ButtonImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: Button
},
{
name: "Entry",
img: InputImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: Input
},
{
name: "Text",
img: TextAreaImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: Text
},
{
name: "CheckBox",
img: CheckButtonImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: CheckBox
},
{
name: "Radio button",
img: RadioButtonImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: RadioButton
},
{
name: "Scale",
img: SliderImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: Slider
},
{
name: "Option Menu",
img: DropDownImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: OptionMenu
},
{
name: "Spinbox",
img: SpinBoxImage,
link: "https://github.com",
link: "https://github.com/PaulleDemon/PyUIBuilder",
widgetClass: SpinBox
},

View File

@@ -19,7 +19,7 @@ export class TkinterBase extends Widget {
}
getLayoutCode(){
const {layout: parentLayout, direction, gap} = this.getParentLayout()
const {layout: parentLayout, direction, gap, align="start"} = this.getParentLayout()
const absolutePositioning = this.getAttrValue("positioning")
@@ -32,23 +32,39 @@ export class TkinterBase extends Widget {
y: this.state.pos.y,
}
if (!this.state.fitContent.width){
config["width"] = this.state.size.width
}
if (!this.state.fitContent.height){
config["height"] = this.state.size.height
}
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})`
}if (parentLayout === Layouts.FLEX){
}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")
@@ -73,8 +89,8 @@ export class TkinterBase extends Widget {
}else if (parentLayout === Layouts.GRID){
const row = this.getAttrValue("gridManager.row")
const col = this.getAttrValue("gridManager.col")
layoutManager = `grid(row=${row}, col=${col})`
const col = this.getAttrValue("gridManager.column")
layoutManager = `grid(row=${row}, column=${col})`
}
return layoutManager
@@ -544,33 +560,41 @@ export class TkinterWidgetBase extends TkinterBase{
getConfigCode(){
const code = {
const config = {
bg: `"${this.getAttrValue("styling.backgroundColor")}"`,
fg: `"${this.getAttrValue("styling.foregroundColor")}"`,
}
if (this.getAttrValue("styling.borderWidth"))
code["bd"] = this.getAttrValue("styling.borderWidth")
config["bd"] = this.getAttrValue("styling.borderWidth")
if (this.getAttrValue("styling.relief"))
code["relief"] = `"${this.getAttrValue("styling.relief")}"`
config["relief"] = `"${this.getAttrValue("styling.relief")}"`
if (this.getAttrValue("font.fontFamily") || this.getAttrValue("font.fontSize")){
code["font"] = `("${this.getAttrValue("font.fontFamily")}", ${this.getAttrValue("font.fontSize") || 12}, )`
config["font"] = `("${this.getAttrValue("font.fontFamily")}", ${this.getAttrValue("font.fontSize") || 12}, )`
}
if (this.getAttrValue("cursor"))
code["cursor"] = `"${this.getAttrValue("cursor")}"`
config["cursor"] = `"${this.getAttrValue("cursor")}"`
if (this.getAttrValue("padding.padX")){
code["padx"] = this.getAttrValue("padding.padX")
config["padx"] = this.getAttrValue("padding.padX")
}
if (this.getAttrValue("padding.padY")){
code["pady"] = this.getAttrValue("padding.padY")
config["pady"] = this.getAttrValue("padding.padY")
}
return code
// 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

@@ -89,13 +89,13 @@ export class Text extends TkinterWidgetBase{
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)
}
// 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)
// }
}
}
@@ -114,7 +114,7 @@ export class Text extends TkinterWidgetBase{
const config = convertObjectToKeyValueString(this.getConfigCode())
return [
`${variableName} = tk.Text(master=${parent}, text="${placeHolderText}")`,
`${variableName} = tk.Text(master=${parent})`,
`${variableName}.config(${config})`,
`${variableName}.${this.getLayoutCode()}`
]

View File

@@ -81,7 +81,7 @@ class Label extends TkinterWidgetBase{
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}")`
labelInitialization = `${variableName} = tk.Label(master=${parent}, image=${variableName}_img, text="${labelText}", compound=tk.TOP)`
}
// code.push("\n")

View File

@@ -19,7 +19,8 @@ class OptionMenu extends TkinterWidgetBase{
...this.state,
isDropDownOpen: false,
widgetName: "Option menu",
size: { width: 120, height: 'fit' },
size: { width: 120, height: 30 },
fitContent: { width: true, height: true },
attrs: {
...this.state.attrs,
defaultValue: {
@@ -55,11 +56,17 @@ class OptionMenu extends TkinterWidgetBase{
const config = convertObjectToKeyValueString(this.getConfigCode())
const defaultValue = this.getAttrValue("defaultValue")
const options = JSON.stringify(this.getAttrValue("widgetOptions").inputs)
const options = this.getAttrValue("widgetOptions").inputs
const code = [
`${variableName}_options = ${JSON.stringify(options)}`,
`${variableName}_var = tk.StringVar(value="${options.at(1) || defaultValue || ''}")`,
`${variableName} = tk.OptionMenu(${parent}, ${variableName}_var, *${variableName}_options)`
]
return [
`${variableName}_options = ${options}`,
`${variableName} = tk.OptionMenu(master=${parent}, ${defaultValue}, *${variableName}_options)`,
...code,
`${variableName}.config(${config})`,
`${variableName}.${this.getLayoutCode()}`
]

View File

@@ -7,14 +7,15 @@ 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' },
size: { width: 120, height: 10 },
fitContent: {width: true, height: true},
attrs: {
...this.state.attrs,
styling: {
@@ -98,7 +99,7 @@ class Slider extends TkinterWidgetBase{
const config = this.getConfigCode()
config["_from"] = this.getAttrValue("scale.min")
config["from_"] = this.getAttrValue("scale.min")
config["to"] = this.getAttrValue("scale.max")
config["resolution"] = this.getAttrValue("scale.step")
@@ -106,12 +107,12 @@ class Slider extends TkinterWidgetBase{
config["orientation"] = this.getAttrValue("orientation")
}
const defaultValue = this.getAttrValue("defaultValue")
const defaultValue = this.getAttrValue("scale.default")
return [
`${variableName}_var = tk.DoubleVar(${defaultValue})`,
`${variableName}_var = tk.DoubleVar(value=${defaultValue})`,
`${variableName} = tk.Scale(master=${parent}, variable=${variableName}_var)`,
`${variableName}.config(${config})`,
`${variableName}.config(${convertObjectToKeyValueString(config)})`,
`${variableName}.${this.getLayoutCode()}`
]
}

View File

@@ -42,7 +42,7 @@ class TopLevel extends Widget{
const backgroundColor = this.getAttrValue("styling.backgroundColor")
return [
`${variableName} = tk.TopLevel(root=${parent})`,
`${variableName} = tk.Toplevel(master=${parent})`,
`${variableName}.config(bg="${backgroundColor}")`,
`${variableName}.title("${this.getAttrValue("title")}")`
]

View File

@@ -1,6 +1,6 @@
import { useEffect, useRef, useMemo, useState } from "react";
import { CloseCircleFilled, CrownFilled, GithubFilled, ShareAltOutlined } from "@ant-design/icons";
import { BookOutlined, CloseCircleFilled, CrownFilled, GithubFilled, ShareAltOutlined } from "@ant-design/icons";
import KO_FI from "../assets/logo/ko-fi.png"
import Premium from "./utils/premium";
@@ -94,7 +94,13 @@ function Sidebar({tabs}){
<Share className="tw-cursor-pointer tw-text-xl">
<ShareAltOutlined />
</Share>
<a href="https://github.com/PaulleDemon/PyUIBuilder" className="tw-text-2xl tw-cursor-pointer tw-text-black">
<a className="tw-cursor-pointer tw-text-xl tw-text-gray-700"
href="https://pyuibuilder-docs.pages.dev/"
target="_blank" rel="noopener noreferrer">
<i className="bi bi-book-half"></i>
</a>
<a href="https://github.com/PaulleDemon/PyUIBuilder" target="_blank"
rel="noopener noreferrer" className="tw-text-2xl tw-cursor-pointer tw-text-black">
<GithubFilled />
</a>
<a href="https://ko-fi.com/artpaul" className="tw-cursor-pointer ">