Compare commits
13 Commits
layout-fix
...
code-edito
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdd3bab3a5 | ||
|
|
53aaa8a670 | ||
|
|
64631caaaa | ||
|
|
8e1f042350 | ||
|
|
128a7c49b9 | ||
|
|
e0fa421459 | ||
|
|
0438480620 | ||
|
|
d64f87847c | ||
|
|
c6fd4a8275 | ||
|
|
bfeb1c55b9 | ||
|
|
9949fb8335 | ||
|
|
86f84ef998 | ||
|
|
fc68d407b7 |
@@ -30,7 +30,7 @@ https://github.com/user-attachments/assets/ac91aa98-843d-4578-b646-88e66bc113de
|
|||||||
<sub>**Don't like background music? fell free to mute it**</sub>
|
<sub>**Don't like background music? fell free to mute it**</sub>
|
||||||
|
|
||||||
## Try PyUIBuilder
|
## Try PyUIBuilder
|
||||||
Try [PyUIBuilder](https://pyuibuilder.pages.dev/)
|
Try [PyUIBuilder](https://pyuibuilder.com)
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ Try [PyUIBuilder](https://pyuibuilder.pages.dev/)
|
|||||||
|
|
||||||
|
|
||||||
## Docs - Getting started
|
## Docs - Getting started
|
||||||
Read the docs on the [Docs page](https://pyuibuilder-docs.pages.dev/)
|
Read the docs on the [Docs page](https://docs.pyuibuilder.com/)
|
||||||
|
|
||||||
## Example app
|
## Example app
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ While there are a lot of features, here are few you need to know.
|
|||||||
* Framework agnostic - Can outputs code in multiple frameworks.
|
* Framework agnostic - Can outputs code in multiple frameworks.
|
||||||
* Pre-built UI widgets
|
* Pre-built UI widgets
|
||||||
* Plugins to extend 3rd party UI libraries
|
* Plugins to extend 3rd party UI libraries
|
||||||
* Supports layout managers, such as flex, grid and absolute positioning [read docs](https://pyuibuilder-docs.pages.dev/)
|
* Supports layout managers, such as flex, grid and absolute positioning [read docs](https://docs.pyuibuilder.com/)
|
||||||
* Generates python Code.
|
* Generates python Code.
|
||||||
* Support to upload local assets.
|
* Support to upload local assets.
|
||||||
* Generates requirements.txt file when needed
|
* Generates requirements.txt file when needed
|
||||||
|
|||||||
@@ -249,7 +249,7 @@
|
|||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<a
|
<a
|
||||||
href="https://pyuibuilder.pages.dev/"
|
href="https://pyuibuilder.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
class=" !tw-bg-[#0F1727] tw-duration-[0.3s] hover:tw-transition-transform hover:tw-scale-[1.01] !tw-mt-auto !tw-text-white tw-gap-2 tw-text-lg tw-rounded-md tw-w-full tw-flex tw-place-content-center tw-p-2 tw-mx-2"
|
class=" !tw-bg-[#0F1727] tw-duration-[0.3s] hover:tw-transition-transform hover:tw-scale-[1.01] !tw-mt-auto !tw-text-white tw-gap-2 tw-text-lg tw-rounded-md tw-w-full tw-flex tw-place-content-center tw-p-2 tw-mx-2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"short_name": "React App",
|
"short_name": "PyUiBuilder",
|
||||||
"name": "Create React App Sample",
|
"name": "PyUiBuilder - Python GUI Builder",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "favicon.ico",
|
"src": "favicon.ico",
|
||||||
|
|||||||
28
src/App.js
28
src/App.js
@@ -61,21 +61,23 @@ function App() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
if (shownNotChromiumAlert){
|
if (!shownNotChromiumAlert){
|
||||||
return
|
// this modal may rerender twice only in dev mode because of how react works
|
||||||
}
|
|
||||||
|
|
||||||
isChromium().then((isChrome) => {
|
isChromium().then((isChrome) => {
|
||||||
|
|
||||||
if (!isChrome){
|
if (!isChrome){
|
||||||
Modal.warning({
|
Modal.warning({
|
||||||
title: "Not Chromium browser",
|
title: "Use Chromium browser",
|
||||||
content: "We recommend using Chromium based browser such as Chrome, Brave, Edge etc."
|
onOk: () => setShownNotChromiumAlert(true),
|
||||||
|
content: (<span>We recommend using Chromium based browser such as Chrome, Brave, Edge etc for best results.
|
||||||
|
<br />
|
||||||
|
Join us on
|
||||||
|
<a href="https://discord.gg/dHXjrrCA7G" target='_blank' rel='noreferrer noopener'> Discord</a> for help and updates</span>)
|
||||||
})
|
})
|
||||||
setShownNotChromiumAlert(true)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
setShownNotChromiumAlert(true)
|
||||||
|
|
||||||
}, [shownNotChromiumAlert])
|
}, [shownNotChromiumAlert])
|
||||||
|
|
||||||
@@ -123,7 +125,6 @@ function App() {
|
|||||||
const widgetCenterX = (TkMainWindow.initialSize.width - canvasBoundingBox.left) / 2
|
const widgetCenterX = (TkMainWindow.initialSize.width - canvasBoundingBox.left) / 2
|
||||||
const widgetCenterY = (TkMainWindow.initialSize.height - canvasBoundingBox.top) / 2
|
const widgetCenterY = (TkMainWindow.initialSize.height - canvasBoundingBox.top) / 2
|
||||||
|
|
||||||
|
|
||||||
canvasRef?.current?.createWidget(TkMainWindow, {x: canvasCenterX - widgetCenterX, y: canvasCenterY - widgetCenterY}, ({id, widgetRef}) => {
|
canvasRef?.current?.createWidget(TkMainWindow, {x: canvasCenterX - widgetCenterX, y: canvasCenterY - widgetCenterY}, ({id, widgetRef}) => {
|
||||||
|
|
||||||
// center the widget when adding to canvas
|
// center the widget when adding to canvas
|
||||||
@@ -140,7 +141,12 @@ function App() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
}else if (UIFramework === FrameWorks.CUSTOMTK){
|
}else if (UIFramework === FrameWorks.CUSTOMTK){
|
||||||
canvasRef?.current?.createWidget(CTkMainWindow, ({id, widgetRef}) => {
|
|
||||||
|
const widgetCenterX = (TkMainWindow.initialSize.width - canvasBoundingBox.left) / 2
|
||||||
|
const widgetCenterY = (TkMainWindow.initialSize.height - canvasBoundingBox.top) / 2
|
||||||
|
|
||||||
|
|
||||||
|
canvasRef?.current?.createWidget(CTkMainWindow, {x: canvasCenterX - widgetCenterX, y: canvasCenterY - widgetCenterY}, ({id, widgetRef}) => {
|
||||||
|
|
||||||
// center the widget when adding to canvas
|
// center the widget when adding to canvas
|
||||||
if (!widgetRef.current){
|
if (!widgetRef.current){
|
||||||
@@ -152,7 +158,7 @@ function App() {
|
|||||||
const widgetCenterY = (widgetBoundingBox.height - widgetBoundingBox.top) / 2
|
const widgetCenterY = (widgetBoundingBox.height - widgetBoundingBox.top) / 2
|
||||||
|
|
||||||
|
|
||||||
widgetRef.current?.setPos(canvasCenterX-widgetCenterX, canvasCenterY-widgetCenterY)
|
// widgetRef.current?.setPos(canvasCenterX-widgetCenterX, canvasCenterY-widgetCenterY)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ class Widget extends React.Component {
|
|||||||
this.resizeObserver = null
|
this.resizeObserver = null
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -704,8 +705,7 @@ class Widget extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getLayout(){
|
getLayout(){
|
||||||
|
return this.getAttrValue("layout") || {layout: Layouts.PLACE}
|
||||||
return this.getAttrValue("layout") || Layouts.PLACE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLayout(value) {
|
setLayout(value) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
export const Tkinter_TO_WEB_CURSOR_MAPPING = {
|
export const Tkinter_TO_WEB_CURSOR_MAPPING = {
|
||||||
|
|
||||||
|
"": "",
|
||||||
"arrow": "default",
|
"arrow": "default",
|
||||||
"circle": "wait",
|
"circle": "wait",
|
||||||
"clock": "wait",
|
"clock": "wait",
|
||||||
|
|||||||
@@ -20,5 +20,25 @@ export const ANCHOR = [
|
|||||||
"s",
|
"s",
|
||||||
"e",
|
"e",
|
||||||
"w",
|
"w",
|
||||||
"center"
|
"center",
|
||||||
|
"ne",
|
||||||
|
"se",
|
||||||
|
"sw",
|
||||||
|
"nw",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
export const GRID_STICKY = {
|
||||||
|
N: "n",
|
||||||
|
S: "s",
|
||||||
|
E: "e",
|
||||||
|
W: "w",
|
||||||
|
WE: "we",
|
||||||
|
NS: "ns",
|
||||||
|
NW: "nw",
|
||||||
|
NE: "ne",
|
||||||
|
SW: "sw",
|
||||||
|
SE: "se",
|
||||||
|
NEWS: "news",
|
||||||
|
NONE: "",
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
|||||||
|
import { Layouts } from "../../../canvas/constants/layouts"
|
||||||
|
import Tools from "../../../canvas/constants/tools"
|
||||||
import Widget from "../../../canvas/widgets/base"
|
import Widget from "../../../canvas/widgets/base"
|
||||||
import { CustomTkBase } from "./base"
|
import { CustomTkBase } from "./base"
|
||||||
|
|
||||||
@@ -17,7 +19,99 @@ class Frame extends CustomTkBase{
|
|||||||
this.state = {
|
this.state = {
|
||||||
...this.state,
|
...this.state,
|
||||||
fitContent: {width: true, height: true},
|
fitContent: {width: true, height: true},
|
||||||
widgetName: "Frame"
|
widgetName: "Frame",
|
||||||
|
attrs: {
|
||||||
|
...this.state.attrs,
|
||||||
|
padding: {
|
||||||
|
label: "padding",
|
||||||
|
padX: {
|
||||||
|
label: "Pad X",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
// this.setWidgetInnerStyle("paddingLeft", `${value}px`)
|
||||||
|
// this.setWidgetInnerStyle("paddingRight", `${value}px`)
|
||||||
|
|
||||||
|
// const widgetStyle = {
|
||||||
|
|
||||||
|
// }
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
|
||||||
|
widgetInnerStyling: {
|
||||||
|
...prevState.widgetInnerStyling,
|
||||||
|
paddingLeft: `${value}px`,
|
||||||
|
paddingRight: `${value}px`
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
this.setAttrValue("padding.padX", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
padY: {
|
||||||
|
label: "Pad Y",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
|
||||||
|
widgetInnerStyling: {
|
||||||
|
...prevState.widgetInnerStyling,
|
||||||
|
paddingTop: `${value}px`,
|
||||||
|
paddingBottom: `${value}px`
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
// this.setState({
|
||||||
|
|
||||||
|
// widgetInnerStyling: widgetStyle
|
||||||
|
// })
|
||||||
|
this.setAttrValue("padding.padY", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
label: "Margin",
|
||||||
|
marginX: {
|
||||||
|
label: "Margin X",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
|
||||||
|
this.updateState((prev) => ({
|
||||||
|
widgetOuterStyling: {
|
||||||
|
...prev.widgetOuterStyling,
|
||||||
|
marginLeft: `${value}px`,
|
||||||
|
marginRight: `${value}px`
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
this.setAttrValue("margin.marginX", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
marginY: {
|
||||||
|
label: "Margin Y",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
this.updateState((prev) => ({
|
||||||
|
widgetOuterStyling: {
|
||||||
|
...prev.widgetOuterStyling,
|
||||||
|
marginTop: `${value}px`,
|
||||||
|
marginBottom: `${value}px`
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
this.setAttrValue("margin.marginY", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -27,6 +121,34 @@ class Frame extends CustomTkBase{
|
|||||||
this.setAttrValue("styling.backgroundColor", "#EDECEC")
|
this.setAttrValue("styling.backgroundColor", "#EDECEC")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getConfigCode(){
|
||||||
|
const bg = this.getAttrValue("styling.backgroundColor")
|
||||||
|
|
||||||
|
const fitWidth = this.state.fitContent.width
|
||||||
|
const fitHeight = this.state.fitContent.height
|
||||||
|
|
||||||
|
const {width, height} = this.getSize()
|
||||||
|
|
||||||
|
const {layout} = this.getParentLayout()
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
bg: `"${bg}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layout !== Layouts.PLACE){
|
||||||
|
if (!fitWidth){
|
||||||
|
config['width'] = width
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fitHeight){
|
||||||
|
config['height'] = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
generateCode(variableName, parent){
|
generateCode(variableName, parent){
|
||||||
|
|
||||||
const bg = this.getAttrValue("styling.backgroundColor")
|
const bg = this.getAttrValue("styling.backgroundColor")
|
||||||
@@ -34,11 +156,25 @@ class Frame extends CustomTkBase{
|
|||||||
return [
|
return [
|
||||||
`${variableName} = ctk.CTkFrame(master=${parent})`,
|
`${variableName} = ctk.CTkFrame(master=${parent})`,
|
||||||
`${variableName}.configure(fg_color="${bg}")`,
|
`${variableName}.configure(fg_color="${bg}")`,
|
||||||
`${variableName}.${this.getLayoutCode()}`
|
`${variableName}.${this.getLayoutCode()}`,
|
||||||
|
...this.getGridLayoutConfigurationCode(variableName)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getToolbarAttrs(){
|
||||||
|
const {layout, gridConfig, gridWeights, ...toolBarAttrs} = super.getToolbarAttrs()
|
||||||
|
|
||||||
|
// places layout at the end
|
||||||
|
return ({
|
||||||
|
id: this.__id,
|
||||||
|
...toolBarAttrs,
|
||||||
|
padding: this.state.attrs.padding,
|
||||||
|
margin: this.state.attrs.margin,
|
||||||
|
layout,
|
||||||
|
gridConfig,
|
||||||
|
gridWeights
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
renderContent(){
|
renderContent(){
|
||||||
// console.log("bounding rect: ", this.getBoundingRect())
|
// console.log("bounding rect: ", this.getBoundingRect())
|
||||||
@@ -49,7 +185,7 @@ class Frame extends CustomTkBase{
|
|||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start"
|
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start"
|
||||||
ref={this.styleAreaRef}
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}>
|
style={this.getInnerRenderStyling()}>
|
||||||
{this.props.children}
|
{this.renderTkinterLayout()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -116,6 +116,22 @@ class Label extends CustomTkWidgetBase{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAnchorStyle = (anchor) => {
|
||||||
|
const anchorStyles = {
|
||||||
|
n: { justifyContent: 'center', alignItems: 'flex-start' },
|
||||||
|
s: { justifyContent: 'center', alignItems: 'flex-end' },
|
||||||
|
e: { justifyContent: 'flex-end', alignItems: 'center' },
|
||||||
|
w: { justifyContent: 'flex-start', alignItems: 'center' },
|
||||||
|
ne: { justifyContent: 'flex-end', alignItems: 'flex-start' },
|
||||||
|
se: { justifyContent: 'flex-end', alignItems: 'flex-end' },
|
||||||
|
nw: { justifyContent: 'flex-start', alignItems: 'flex-start' },
|
||||||
|
sw: { justifyContent: 'flex-start', alignItems: 'flex-end' },
|
||||||
|
center: { justifyContent: 'center', alignItems: 'center' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return anchorStyles[anchor] || anchorStyles["w"];
|
||||||
|
}
|
||||||
|
|
||||||
renderContent(){
|
renderContent(){
|
||||||
|
|
||||||
const image = this.getAttrValue("imageUpload")
|
const image = this.getAttrValue("imageUpload")
|
||||||
@@ -137,7 +153,10 @@ class Label extends CustomTkWidgetBase{
|
|||||||
<img src={image.previewUrl} className="tw-bg-contain tw-w-full tw-h-full" />
|
<img src={image.previewUrl} className="tw-bg-contain tw-w-full tw-h-full" />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<div className="" style={{color: this.getAttrValue("styling.foregroundColor")}}>
|
<div className={`tw-flex ${!image ? "tw-w-full tw-h-full" : ""}`} style={{
|
||||||
|
color: this.getAttrValue("styling.foregroundColor"),
|
||||||
|
...this.getAnchorStyle(this.getAttrValue("styling.anchor"))
|
||||||
|
}}>
|
||||||
{this.getAttrValue("labelWidget")}
|
{this.getAttrValue("labelWidget")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Widget from "../../../canvas/widgets/base"
|
import Widget from "../../../canvas/widgets/base"
|
||||||
import Tools from "../../../canvas/constants/tools"
|
import Tools from "../../../canvas/constants/tools"
|
||||||
import { CustomTkBase } from "./base"
|
import { CustomTkBase } from "./base"
|
||||||
|
import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
||||||
|
|
||||||
|
|
||||||
class MainWindow extends CustomTkBase{
|
class MainWindow extends CustomTkBase{
|
||||||
@@ -8,6 +9,13 @@ class MainWindow extends CustomTkBase{
|
|||||||
static widgetType = "main_window"
|
static widgetType = "main_window"
|
||||||
static displayName = "Main Window"
|
static displayName = "Main Window"
|
||||||
|
|
||||||
|
|
||||||
|
static initialSize = {
|
||||||
|
width: 700,
|
||||||
|
height: 400
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@@ -27,6 +35,13 @@ class MainWindow extends CustomTkBase{
|
|||||||
toolProps: {placeholder: "Window title", maxLength: 40},
|
toolProps: {placeholder: "Window title", maxLength: 40},
|
||||||
value: "Main Window",
|
value: "Main Window",
|
||||||
onChange: (value) => this.setAttrValue("title", value)
|
onChange: (value) => this.setAttrValue("title", value)
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
label: "Window Logo",
|
||||||
|
tool: Tools.UPLOADED_LIST,
|
||||||
|
toolProps: {filterOptions: ["image/jpg", "image/jpeg", "image/png"]},
|
||||||
|
value: "",
|
||||||
|
onChange: (value) => this.setAttrValue("logo", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -43,12 +58,48 @@ class MainWindow extends CustomTkBase{
|
|||||||
generateCode(variableName, parent){
|
generateCode(variableName, parent){
|
||||||
|
|
||||||
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
||||||
|
const logo = this.getAttrValue("logo")
|
||||||
|
|
||||||
return [
|
const {width, height} = this.getSize()
|
||||||
|
|
||||||
|
const code = [
|
||||||
`${variableName} = ctk.CTk()`,
|
`${variableName} = ctk.CTk()`,
|
||||||
`${variableName}.configure(fg_color="${backgroundColor}")`,
|
`${variableName}.configure(fg_color="${backgroundColor}")`,
|
||||||
`${variableName}.title("${this.getAttrValue("title")}")`
|
`${variableName}.title("${this.getAttrValue("title")}")`,
|
||||||
|
`${variableName}.geometry("${width}x${height}")`,
|
||||||
|
...this.getGridLayoutConfigurationCode(variableName)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (logo?.name){
|
||||||
|
|
||||||
|
// code.push(`\n`)
|
||||||
|
code.push(`${variableName}_img = Image.open(${getPythonAssetPath(logo.name, "image")})`)
|
||||||
|
code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||||
|
code.push(`${variableName}.iconphoto(False, ${variableName}_img)`)
|
||||||
|
// code.push("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
getImports(){
|
||||||
|
const imports = super.getImports()
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
imports.push("import os", "from PIL import Image, ImageTk", )
|
||||||
|
|
||||||
|
return imports
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getRequirements(){
|
||||||
|
const requirements = super.getRequirements()
|
||||||
|
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
requirements.push("pillow")
|
||||||
|
|
||||||
|
return requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
getToolbarAttrs(){
|
getToolbarAttrs(){
|
||||||
@@ -58,8 +109,8 @@ class MainWindow extends CustomTkBase{
|
|||||||
id: this.__id,
|
id: this.__id,
|
||||||
widgetName: toolBarAttrs.widgetName,
|
widgetName: toolBarAttrs.widgetName,
|
||||||
title: this.state.attrs.title,
|
title: this.state.attrs.title,
|
||||||
|
logo: this.state.attrs.logo,
|
||||||
size: toolBarAttrs.size,
|
size: toolBarAttrs.size,
|
||||||
|
|
||||||
...this.state.attrs,
|
...this.state.attrs,
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -83,7 +134,7 @@ class MainWindow extends CustomTkBase{
|
|||||||
<div className="tw-p-2 tw-w-full tw-relative tw-h-full tw-overflow-hidden tw-content-start"
|
<div className="tw-p-2 tw-w-full tw-relative tw-h-full tw-overflow-hidden tw-content-start"
|
||||||
ref={this.styleAreaRef}
|
ref={this.styleAreaRef}
|
||||||
style={this.state.widgetInnerStyling}>
|
style={this.state.widgetInnerStyling}>
|
||||||
{this.props.children}
|
{this.renderTkinterLayout()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import Widget from "../../../canvas/widgets/base"
|
import Widget from "../../../canvas/widgets/base"
|
||||||
import Tools from "../../../canvas/constants/tools"
|
import Tools from "../../../canvas/constants/tools"
|
||||||
|
import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
||||||
|
import { CustomTkBase } from "./base"
|
||||||
|
|
||||||
|
|
||||||
class TopLevel extends Widget{
|
class TopLevel extends CustomTkBase{
|
||||||
|
|
||||||
static widgetType = "toplevel"
|
static widgetType = "toplevel"
|
||||||
static displayName = "Top Level"
|
static displayName = "Top Level"
|
||||||
@@ -27,6 +29,13 @@ class TopLevel extends Widget{
|
|||||||
toolProps: {placeholder: "Window title", maxLength: 40},
|
toolProps: {placeholder: "Window title", maxLength: 40},
|
||||||
value: "Top level",
|
value: "Top level",
|
||||||
onChange: (value) => this.setAttrValue("title", value)
|
onChange: (value) => this.setAttrValue("title", value)
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
label: "Toplevel Logo",
|
||||||
|
tool: Tools.UPLOADED_LIST,
|
||||||
|
toolProps: {filterOptions: ["image/jpg", "image/jpeg", "image/png"]},
|
||||||
|
value: "",
|
||||||
|
onChange: (value) => this.setAttrValue("logo", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -42,11 +51,49 @@ class TopLevel extends Widget{
|
|||||||
|
|
||||||
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
||||||
|
|
||||||
return [
|
const logo = this.getAttrValue("logo")
|
||||||
|
|
||||||
|
const {width, height} = this.getSize()
|
||||||
|
|
||||||
|
const code = [
|
||||||
`${variableName} = ctk.CTkToplevel(master=${parent})`,
|
`${variableName} = ctk.CTkToplevel(master=${parent})`,
|
||||||
`${variableName}.configure(fg_color="${backgroundColor}")`,
|
`${variableName}.configure(fg_color="${backgroundColor}")`,
|
||||||
`${variableName}.title("${this.getAttrValue("title")}")`
|
`${variableName}.title("${this.getAttrValue("title")}")`,
|
||||||
|
`${variableName}.geometry("${width}x${height}")`,
|
||||||
|
|
||||||
|
...this.getGridLayoutConfigurationCode(variableName)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (logo?.name){
|
||||||
|
|
||||||
|
// code.push(`\n`)
|
||||||
|
code.push(`${variableName}_img = Image.open(${getPythonAssetPath(logo.name, "image")})`)
|
||||||
|
code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||||
|
code.push(`${variableName}.iconphoto(False, ${variableName}_img)`)
|
||||||
|
// code.push("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
getImports(){
|
||||||
|
const imports = super.getImports()
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
imports.push("import os", "from PIL import Image, ImageTk", )
|
||||||
|
|
||||||
|
return imports
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getRequirements(){
|
||||||
|
const requirements = super.getRequirements()
|
||||||
|
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
requirements.push("pillow")
|
||||||
|
|
||||||
|
return requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
getToolbarAttrs(){
|
getToolbarAttrs(){
|
||||||
@@ -55,8 +102,8 @@ class TopLevel extends Widget{
|
|||||||
return ({
|
return ({
|
||||||
widgetName: toolBarAttrs.widgetName,
|
widgetName: toolBarAttrs.widgetName,
|
||||||
title: this.state.attrs.title,
|
title: this.state.attrs.title,
|
||||||
|
logo: this.state.attrs.logo,
|
||||||
size: toolBarAttrs.size,
|
size: toolBarAttrs.size,
|
||||||
|
|
||||||
...this.state.attrs,
|
...this.state.attrs,
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -80,7 +127,7 @@ class TopLevel extends Widget{
|
|||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start"
|
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start"
|
||||||
ref={this.styleAreaRef}
|
ref={this.styleAreaRef}
|
||||||
style={this.state.widgetInnerStyling}>
|
style={this.state.widgetInnerStyling}>
|
||||||
{this.props.children}
|
{this.renderTkinterLayout()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
export const Tkinter_TO_WEB_CURSOR_MAPPING = {
|
export const Tkinter_TO_WEB_CURSOR_MAPPING = {
|
||||||
|
|
||||||
|
"": "",
|
||||||
"arrow": "default",
|
"arrow": "default",
|
||||||
"circle": "wait",
|
"circle": "wait",
|
||||||
"clock": "wait",
|
"clock": "wait",
|
||||||
|
|||||||
@@ -659,7 +659,7 @@ export class TkinterBase extends Widget {
|
|||||||
{this.renderPackWidgetsRecursively(widgets, index + 1, side, previousExpandValue)}
|
{this.renderPackWidgetsRecursively(widgets, index + 1, side, previousExpandValue)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -687,9 +687,13 @@ export class TkinterBase extends Widget {
|
|||||||
setLayout(value) {
|
setLayout(value) {
|
||||||
const { layout, direction, grid = { rows: 1, cols: 1 }, gap = 10, align } = value
|
const { layout, direction, grid = { rows: 1, cols: 1 }, gap = 10, align } = value
|
||||||
|
|
||||||
|
const gridRow = this.getAttrValue("gridConfig.noOfRows") || 3 // suppose its loaded using this.load, we need this
|
||||||
|
const gridCol = this.getAttrValue("gridConfig.noOfCols") || 3
|
||||||
|
|
||||||
if (layout === Layouts.GRID){
|
if (layout === Layouts.GRID){
|
||||||
|
|
||||||
|
const rowWeight = this.getAttrValue("gridWeights.rowWeights") || undefined // suppose its loaded via this.load
|
||||||
|
const colWeight = this.getAttrValue("gridWeights.colWeights") || undefined // suppose its loaded via this.load
|
||||||
|
|
||||||
const {...restAttrs} = this.state.attrs
|
const {...restAttrs} = this.state.attrs
|
||||||
|
|
||||||
@@ -703,7 +707,7 @@ export class TkinterBase extends Widget {
|
|||||||
label: "No of rows",
|
label: "No of rows",
|
||||||
tool: Tools.NUMBER_INPUT,
|
tool: Tools.NUMBER_INPUT,
|
||||||
toolProps: { placeholder: "no of rows", max: 1000, min: 1 },
|
toolProps: { placeholder: "no of rows", max: 1000, min: 1 },
|
||||||
value: 3,
|
value: gridRow,
|
||||||
onChange: (value) => {
|
onChange: (value) => {
|
||||||
this.setAttrValue("gridConfig.noOfRows", value)
|
this.setAttrValue("gridConfig.noOfRows", value)
|
||||||
|
|
||||||
@@ -729,7 +733,7 @@ export class TkinterBase extends Widget {
|
|||||||
label: "No of cols",
|
label: "No of cols",
|
||||||
tool: Tools.NUMBER_INPUT,
|
tool: Tools.NUMBER_INPUT,
|
||||||
toolProps: { placeholder: "no of cols", max: 1000, min: 1 },
|
toolProps: { placeholder: "no of cols", max: 1000, min: 1 },
|
||||||
value: 3,
|
value: gridCol,
|
||||||
onChange: (value) => {
|
onChange: (value) => {
|
||||||
this.setAttrValue("gridConfig.noOfCols", value)
|
this.setAttrValue("gridConfig.noOfCols", value)
|
||||||
|
|
||||||
@@ -753,7 +757,7 @@ export class TkinterBase extends Widget {
|
|||||||
// placeholder: "weight",
|
// placeholder: "weight",
|
||||||
// defaultWeightMapping: this.getAttrValue("gridWeights.rowWeights"),
|
// defaultWeightMapping: this.getAttrValue("gridWeights.rowWeights"),
|
||||||
},
|
},
|
||||||
value: undefined,
|
value: rowWeight,
|
||||||
Component: DynamicGridWeightInput,
|
Component: DynamicGridWeightInput,
|
||||||
onChange: (value) => {
|
onChange: (value) => {
|
||||||
|
|
||||||
@@ -781,7 +785,7 @@ export class TkinterBase extends Widget {
|
|||||||
// placeholder: "weight",
|
// placeholder: "weight",
|
||||||
// defaultWeightMapping: {0: {weight: 0, gridNo: 0}}
|
// defaultWeightMapping: {0: {weight: 0, gridNo: 0}}
|
||||||
},
|
},
|
||||||
value: undefined,
|
value: colWeight,
|
||||||
Component: DynamicGridWeightInput,
|
Component: DynamicGridWeightInput,
|
||||||
onChange: (value) => {
|
onChange: (value) => {
|
||||||
|
|
||||||
@@ -812,8 +816,6 @@ export class TkinterBase extends Widget {
|
|||||||
}else if (layout === Layouts.FLEX){
|
}else if (layout === Layouts.FLEX){
|
||||||
const {gridConfig, gridWeights, ...restAttrs} = this.state.attrs
|
const {gridConfig, gridWeights, ...restAttrs} = this.state.attrs
|
||||||
|
|
||||||
console.log("Flex: ", restAttrs)
|
|
||||||
|
|
||||||
this.updateState((prevState) => ({attrs: {...restAttrs}}))
|
this.updateState((prevState) => ({attrs: {...restAttrs}}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,8 +829,8 @@ export class TkinterBase extends Widget {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
// flexDirection: direction,
|
// flexDirection: direction,
|
||||||
gap: `${gap}px`,
|
gap: `${gap}px`,
|
||||||
gridTemplateColumns: "repeat(3, max-content)",
|
gridTemplateColumns: `repeat(${gridRow}, max-content)`,
|
||||||
gridTemplateRows: "repeat(3, max-content)",
|
gridTemplateRows: `repeat(${gridCol}, max-content)`,
|
||||||
// gridTemplateColumns: "repeat(auto-fill, minmax(100px, auto))",
|
// gridTemplateColumns: "repeat(auto-fill, minmax(100px, auto))",
|
||||||
// gridTemplateRows: "repeat(auto-fill, minmax(100px, auto))",
|
// gridTemplateRows: "repeat(auto-fill, minmax(100px, auto))",
|
||||||
}
|
}
|
||||||
@@ -842,18 +844,6 @@ export class TkinterBase extends Widget {
|
|||||||
|
|
||||||
const {layout: parentLayout, direction, gap} = this.getParentLayout() || {}
|
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 = {
|
const styling = {
|
||||||
...this.state.widgetInnerStyling,
|
...this.state.widgetInnerStyling,
|
||||||
@@ -900,6 +890,19 @@ export class TkinterBase extends Widget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getToolbarAttrs(){
|
||||||
|
const {layout, gridConfig, gridWeights, ...toolBarAttrs} = super.getToolbarAttrs()
|
||||||
|
|
||||||
|
// places layout at the end
|
||||||
|
return ({
|
||||||
|
id: this.__id,
|
||||||
|
...toolBarAttrs,
|
||||||
|
layout,
|
||||||
|
gridConfig,
|
||||||
|
gridWeights
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
serialize(){
|
serialize(){
|
||||||
return ({
|
return ({
|
||||||
...super.serialize(),
|
...super.serialize(),
|
||||||
@@ -951,6 +954,8 @@ export class TkinterBase extends Widget {
|
|||||||
pos
|
pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {layout} = attrs
|
||||||
|
|
||||||
|
|
||||||
this.setState(newData, () => {
|
this.setState(newData, () => {
|
||||||
let layoutAttrs = this.setParentLayout(parentLayout).attrs || {}
|
let layoutAttrs = this.setParentLayout(parentLayout).attrs || {}
|
||||||
@@ -977,15 +982,21 @@ export class TkinterBase extends Widget {
|
|||||||
|
|
||||||
|
|
||||||
if (newAttrs?.styling?.backgroundColor){
|
if (newAttrs?.styling?.backgroundColor){
|
||||||
// TODO: find a better way to apply innerStyles
|
// TODO: find a better way to apply innerStyles (we may not need this anymore)
|
||||||
this.setWidgetInnerStyle("backgroundColor", newAttrs.styling.backgroundColor.value)
|
this.setWidgetInnerStyle("backgroundColor", newAttrs.styling.backgroundColor.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateState({ attrs: newAttrs }, callback)
|
this.updateState({ attrs: newAttrs }, callback)
|
||||||
|
|
||||||
// FIXME: when changing layouts all the widgets are being selected
|
// FIXME: when changing layouts all the widgets are being selected
|
||||||
if (selected){
|
if (selected){
|
||||||
this.select()
|
this.select()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (layout){
|
||||||
|
this.setLayout(layout)
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,20 @@ class Frame extends TkinterBase{
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getToolbarAttrs(){
|
||||||
|
const {layout, gridConfig, gridWeights, ...toolBarAttrs} = super.getToolbarAttrs()
|
||||||
|
|
||||||
|
// places layout at the end
|
||||||
|
return ({
|
||||||
|
id: this.__id,
|
||||||
|
...toolBarAttrs,
|
||||||
|
padding: this.state.attrs.padding,
|
||||||
|
margin: this.state.attrs.margin,
|
||||||
|
layout,
|
||||||
|
gridConfig,
|
||||||
|
gridWeights
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
renderContent(){
|
renderContent(){
|
||||||
// console.log("bounding rect: ", this.getBoundingRect())
|
// console.log("bounding rect: ", this.getBoundingRect())
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { useEffect, useRef, useMemo, useState } from "react";
|
import { useEffect, useRef, useMemo, useState } from "react";
|
||||||
|
|
||||||
import { BookOutlined, CloseCircleFilled, CrownFilled, GithubFilled, ShareAltOutlined } from "@ant-design/icons";
|
import { BookOutlined, CloseCircleFilled, CrownFilled, DiscordFilled, GithubFilled, ShareAltOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
import KO_FI from "../assets/logo/ko-fi.png"
|
import KO_FI from "../assets/logo/ko-fi.png"
|
||||||
import Premium from "./utils/premium";
|
import Premium from "./utils/premium";
|
||||||
@@ -88,6 +88,11 @@ function Sidebar({tabs}){
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div className="tw-flex tw-flex-col tw-place-content-items tw-place-items-center tw-gap-3 tw-mt-auto">
|
<div className="tw-flex tw-flex-col tw-place-content-items tw-place-items-center tw-gap-3 tw-mt-auto">
|
||||||
|
<a href="https://discord.gg/dHXjrrCA7G" target="_blank"
|
||||||
|
title="discord invite"
|
||||||
|
rel="noopener noreferrer" className="tw-text-3xl tw-cursor-pointer tw-text-[#5562EA]">
|
||||||
|
<DiscordFilled />
|
||||||
|
</a>
|
||||||
<Premium className="tw-text-2xl tw-bg-purple-700 tw-text-center
|
<Premium className="tw-text-2xl tw-bg-purple-700 tw-text-center
|
||||||
tw-w-[35px] tw-h-[35px] tw-rounded-md
|
tw-w-[35px] tw-h-[35px] tw-rounded-md
|
||||||
tw-cursor-pointer tw-text-white
|
tw-cursor-pointer tw-text-white
|
||||||
@@ -107,9 +112,9 @@ function Sidebar({tabs}){
|
|||||||
rel="noopener noreferrer" className="tw-text-2xl tw-cursor-pointer tw-text-black">
|
rel="noopener noreferrer" className="tw-text-2xl tw-cursor-pointer tw-text-black">
|
||||||
<GithubFilled />
|
<GithubFilled />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://ko-fi.com/artpaul" className="tw-cursor-pointer ">
|
{/* <a href="https://ko-fi.com/artpaul" className="tw-cursor-pointer ">
|
||||||
<img src={KO_FI} alt="ko-fi" className="tw-w-[30px] tw-h-[30px]"/>
|
<img src={KO_FI} alt="ko-fi" className="tw-w-[30px] tw-h-[30px]"/>
|
||||||
</a>
|
</a> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -38,11 +38,7 @@ function Premium({ children, className = "" }) {
|
|||||||
If you find this tool useful and want to fund and support it's development, consider buying a <b>one time license</b>.
|
If you find this tool useful and want to fund and support it's development, consider buying a <b>one time license</b>.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
I am working on PYUI builder full time to help you easily build GUI's in python. However as a solo-dev, its super hard
|
By pre-ordering license, you get discounted price, advance features, priority support, early access, upcoming features, and
|
||||||
to keep working on it without enough funding.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
By buying pre-order license, I get to complete this faster and you get discounted price, advance features, priority support, early access, upcoming features, and
|
|
||||||
<a
|
<a
|
||||||
href="https://github.com/PaulleDemon/PyUIBuilder/blob/main/roadmap.md"
|
href="https://github.com/PaulleDemon/PyUIBuilder/blob/main/roadmap.md"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -145,7 +141,7 @@ function Premium({ children, className = "" }) {
|
|||||||
Support open-source development 🚀. Plus, get added benefits.
|
Support open-source development 🚀. Plus, get added benefits.
|
||||||
</p>
|
</p>
|
||||||
<hr />
|
<hr />
|
||||||
<ul className="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-gray-600">
|
<ul className="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-black">
|
||||||
<li className="tw-flex tw-place-items-center tw-gap-2">
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Access to web-based editor</span>
|
<span>Access to web-based editor</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user