snakeflex 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snakeflex-0.1.0/LICENSE +21 -0
- snakeflex-0.1.0/PKG-INFO +183 -0
- snakeflex-0.1.0/README.md +157 -0
- snakeflex-0.1.0/pyproject.toml +38 -0
- snakeflex-0.1.0/setup.cfg +4 -0
- snakeflex-0.1.0/src/snakeflex/__init__.py +48 -0
- snakeflex-0.1.0/src/snakeflex/_base.py +71 -0
- snakeflex-0.1.0/src/snakeflex/flex.py +295 -0
- snakeflex-0.1.0/src/snakeflex/grid_frame.py +303 -0
- snakeflex-0.1.0/src/snakeflex/responsive.py +99 -0
- snakeflex-0.1.0/src/snakeflex/spacer.py +48 -0
- snakeflex-0.1.0/src/snakeflex.egg-info/PKG-INFO +183 -0
- snakeflex-0.1.0/src/snakeflex.egg-info/SOURCES.txt +16 -0
- snakeflex-0.1.0/src/snakeflex.egg-info/dependency_links.txt +1 -0
- snakeflex-0.1.0/src/snakeflex.egg-info/top_level.txt +1 -0
- snakeflex-0.1.0/tests/test_flex.py +154 -0
- snakeflex-0.1.0/tests/test_grid_frame.py +126 -0
- snakeflex-0.1.0/tests/test_responsive.py +53 -0
snakeflex-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew Toomey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
snakeflex-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: snakeflex
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Flexbox and CSS Grid-inspired layout utilities for tkinter and customtkinter
|
|
5
|
+
Author: SnakePlayer contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/toomeydev/snakeflex
|
|
8
|
+
Project-URL: Repository, https://github.com/toomeydev/snakeflex
|
|
9
|
+
Project-URL: Issues, https://github.com/toomeydev/snakeflex/issues
|
|
10
|
+
Keywords: tkinter,customtkinter,layout,flexbox,css-grid,gui
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# snakeflex
|
|
28
|
+
|
|
29
|
+
**Flexbox and CSS Grid-inspired layout utilities for tkinter and customtkinter.**
|
|
30
|
+
|
|
31
|
+
`snakeflex` implements Flexbox inspired functionality in the context of building user interfaces with Tkinter and/or CustomTkinter and Python. It works as
|
|
32
|
+
a drop-in layout layer over standard `tkinter` and `customtkinter` widgets, without any external
|
|
33
|
+
dependencies.
|
|
34
|
+
```bash
|
|
35
|
+
pip install snakeflex
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import tkinter as tk
|
|
44
|
+
from snakeflex import FlexRow, FlexCol, GridFrame, Spacer
|
|
45
|
+
|
|
46
|
+
root = tk.Tk()
|
|
47
|
+
|
|
48
|
+
# Horizontal bar
|
|
49
|
+
bar = FlexRow(root, justify="space-between", align="center", gap=8)
|
|
50
|
+
bar.pack(fill="x")
|
|
51
|
+
|
|
52
|
+
title = tk.Label(bar, text="My App")
|
|
53
|
+
bar.add(title)
|
|
54
|
+
bar.add(Spacer()) # pushes right-hand items to the edge
|
|
55
|
+
bar.add(tk.Button(bar, text="Settings"))
|
|
56
|
+
bar.add(tk.Button(bar, text="Quit", command=root.quit))
|
|
57
|
+
|
|
58
|
+
# Vertical panel
|
|
59
|
+
panel = FlexCol(root, align="stretch", gap=4)
|
|
60
|
+
panel.pack(fill="both", expand=True)
|
|
61
|
+
|
|
62
|
+
panel.add(tk.Label(panel, text="Header"))
|
|
63
|
+
panel.add(tk.Text(panel), grow=1) # Text widget fills remaining height
|
|
64
|
+
panel.add(tk.Label(panel, text="Status"))
|
|
65
|
+
|
|
66
|
+
root.mainloop()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## FlexRow / FlexCol
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from snakeflex import FlexRow, FlexCol
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Parameters
|
|
78
|
+
|
|
79
|
+
| Parameter | Type | Default | Description |
|
|
80
|
+
|-------------|--------|---------------|-------------|
|
|
81
|
+
| `justify` | str | `"start"` | Main-axis alignment: `"start"` `"end"` `"center"` `"space-between"` `"space-around"` `"space-evenly"` |
|
|
82
|
+
| `align` | str | `"stretch"` | Cross-axis alignment: `"stretch"` `"start"` `"end"` `"center"` |
|
|
83
|
+
| `gap` | int | `0` | Pixels of space between children |
|
|
84
|
+
| `fg_color` | str | `"transparent"` | Background colour (customtkinter-compatible alias for `bg`) |
|
|
85
|
+
| `direction` | str | `"row"` | `FlexFrame` only: `"row"` `"column"` `"row-reverse"` `"column-reverse"` |
|
|
86
|
+
|
|
87
|
+
### `.add(widget, *, grow=0, align=None)`
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
row = FlexRow(parent, justify="space-between", gap=8)
|
|
91
|
+
row.add(logo)
|
|
92
|
+
row.add(slider, grow=1) # slider expands to fill spare space
|
|
93
|
+
row.add(mute_btn)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
| Parameter | Default | Description |
|
|
97
|
+
|-----------|---------|-------------|
|
|
98
|
+
| `grow` | `0` | Proportional share of remaining space (like `flex-grow`) |
|
|
99
|
+
| `align` | `None` | Per-child cross-axis override |
|
|
100
|
+
|
|
101
|
+
Returns the widget for chaining.
|
|
102
|
+
|
|
103
|
+
<hr>
|
|
104
|
+
|
|
105
|
+
## GridFrame
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from snakeflex import GridFrame
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Template syntax
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
"200px 1fr 2fr" - fixed 200 px | 1 share | 2 shares of remaining space
|
|
115
|
+
"1fr 1fr 1fr" - three equal columns
|
|
116
|
+
"auto 1fr" - content-sized column + fills the rest
|
|
117
|
+
"48px 1fr 28px" - fixed header | flexible content | fixed footer
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Usage
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
layout = GridFrame(parent,
|
|
124
|
+
columns="220px 1fr",
|
|
125
|
+
rows="48px 1fr 28px",
|
|
126
|
+
gap=4,
|
|
127
|
+
)
|
|
128
|
+
layout.add(topbar, col=0, row=0, colspan=2)
|
|
129
|
+
layout.add(sidebar, col=0, row=1)
|
|
130
|
+
layout.add(content, col=1, row=1)
|
|
131
|
+
layout.add(statusbar, col=0, row=2, colspan=2)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Responsive breakpoints
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
layout.on_resize(800,
|
|
138
|
+
narrow=lambda: layout.hide_col(2), # collapse detail panel
|
|
139
|
+
wide=lambda: layout.show_col(2),
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
<hr>
|
|
144
|
+
|
|
145
|
+
## Spacer
|
|
146
|
+
|
|
147
|
+
An invisible, expanding spacer — equivalent to `<div style="flex: 1" />`.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
bar = FlexRow(parent, justify="start", gap=8)
|
|
151
|
+
bar.add(logo)
|
|
152
|
+
bar.add(Spacer()) # everything after this is pushed right
|
|
153
|
+
bar.add(btn_a)
|
|
154
|
+
bar.add(btn_b)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
When added to a `FlexRow`/`FlexCol`, `Spacer` defaults to `grow=1`.
|
|
158
|
+
|
|
159
|
+
<br>
|
|
160
|
+
|
|
161
|
+
## customtkinter compatibility
|
|
162
|
+
|
|
163
|
+
All containers accept `fg_color` as an alias for `bg` and default to
|
|
164
|
+
`"transparent"` so they blend seamlessly into CTk windows:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
import customtkinter as ctk
|
|
168
|
+
from snakeflex import FlexRow
|
|
169
|
+
|
|
170
|
+
app = ctk.CTk()
|
|
171
|
+
bar = FlexRow(app, fg_color="transparent", gap=8)
|
|
172
|
+
bar.add(ctk.CTkLabel(bar, text="Hello"))
|
|
173
|
+
bar.add(ctk.CTkButton(bar, text="Go"), grow=1)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
<hr>
|
|
177
|
+
|
|
178
|
+
## Installation
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
pip install snakeflex
|
|
182
|
+
```
|
|
183
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# snakeflex
|
|
2
|
+
|
|
3
|
+
**Flexbox and CSS Grid-inspired layout utilities for tkinter and customtkinter.**
|
|
4
|
+
|
|
5
|
+
`snakeflex` implements Flexbox inspired functionality in the context of building user interfaces with Tkinter and/or CustomTkinter and Python. It works as
|
|
6
|
+
a drop-in layout layer over standard `tkinter` and `customtkinter` widgets, without any external
|
|
7
|
+
dependencies.
|
|
8
|
+
```bash
|
|
9
|
+
pip install snakeflex
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
import tkinter as tk
|
|
18
|
+
from snakeflex import FlexRow, FlexCol, GridFrame, Spacer
|
|
19
|
+
|
|
20
|
+
root = tk.Tk()
|
|
21
|
+
|
|
22
|
+
# Horizontal bar
|
|
23
|
+
bar = FlexRow(root, justify="space-between", align="center", gap=8)
|
|
24
|
+
bar.pack(fill="x")
|
|
25
|
+
|
|
26
|
+
title = tk.Label(bar, text="My App")
|
|
27
|
+
bar.add(title)
|
|
28
|
+
bar.add(Spacer()) # pushes right-hand items to the edge
|
|
29
|
+
bar.add(tk.Button(bar, text="Settings"))
|
|
30
|
+
bar.add(tk.Button(bar, text="Quit", command=root.quit))
|
|
31
|
+
|
|
32
|
+
# Vertical panel
|
|
33
|
+
panel = FlexCol(root, align="stretch", gap=4)
|
|
34
|
+
panel.pack(fill="both", expand=True)
|
|
35
|
+
|
|
36
|
+
panel.add(tk.Label(panel, text="Header"))
|
|
37
|
+
panel.add(tk.Text(panel), grow=1) # Text widget fills remaining height
|
|
38
|
+
panel.add(tk.Label(panel, text="Status"))
|
|
39
|
+
|
|
40
|
+
root.mainloop()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## FlexRow / FlexCol
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from snakeflex import FlexRow, FlexCol
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Parameters
|
|
52
|
+
|
|
53
|
+
| Parameter | Type | Default | Description |
|
|
54
|
+
|-------------|--------|---------------|-------------|
|
|
55
|
+
| `justify` | str | `"start"` | Main-axis alignment: `"start"` `"end"` `"center"` `"space-between"` `"space-around"` `"space-evenly"` |
|
|
56
|
+
| `align` | str | `"stretch"` | Cross-axis alignment: `"stretch"` `"start"` `"end"` `"center"` |
|
|
57
|
+
| `gap` | int | `0` | Pixels of space between children |
|
|
58
|
+
| `fg_color` | str | `"transparent"` | Background colour (customtkinter-compatible alias for `bg`) |
|
|
59
|
+
| `direction` | str | `"row"` | `FlexFrame` only: `"row"` `"column"` `"row-reverse"` `"column-reverse"` |
|
|
60
|
+
|
|
61
|
+
### `.add(widget, *, grow=0, align=None)`
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
row = FlexRow(parent, justify="space-between", gap=8)
|
|
65
|
+
row.add(logo)
|
|
66
|
+
row.add(slider, grow=1) # slider expands to fill spare space
|
|
67
|
+
row.add(mute_btn)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
| Parameter | Default | Description |
|
|
71
|
+
|-----------|---------|-------------|
|
|
72
|
+
| `grow` | `0` | Proportional share of remaining space (like `flex-grow`) |
|
|
73
|
+
| `align` | `None` | Per-child cross-axis override |
|
|
74
|
+
|
|
75
|
+
Returns the widget for chaining.
|
|
76
|
+
|
|
77
|
+
<hr>
|
|
78
|
+
|
|
79
|
+
## GridFrame
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from snakeflex import GridFrame
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Template syntax
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
"200px 1fr 2fr" - fixed 200 px | 1 share | 2 shares of remaining space
|
|
89
|
+
"1fr 1fr 1fr" - three equal columns
|
|
90
|
+
"auto 1fr" - content-sized column + fills the rest
|
|
91
|
+
"48px 1fr 28px" - fixed header | flexible content | fixed footer
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Usage
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
layout = GridFrame(parent,
|
|
98
|
+
columns="220px 1fr",
|
|
99
|
+
rows="48px 1fr 28px",
|
|
100
|
+
gap=4,
|
|
101
|
+
)
|
|
102
|
+
layout.add(topbar, col=0, row=0, colspan=2)
|
|
103
|
+
layout.add(sidebar, col=0, row=1)
|
|
104
|
+
layout.add(content, col=1, row=1)
|
|
105
|
+
layout.add(statusbar, col=0, row=2, colspan=2)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Responsive breakpoints
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
layout.on_resize(800,
|
|
112
|
+
narrow=lambda: layout.hide_col(2), # collapse detail panel
|
|
113
|
+
wide=lambda: layout.show_col(2),
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
<hr>
|
|
118
|
+
|
|
119
|
+
## Spacer
|
|
120
|
+
|
|
121
|
+
An invisible, expanding spacer — equivalent to `<div style="flex: 1" />`.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
bar = FlexRow(parent, justify="start", gap=8)
|
|
125
|
+
bar.add(logo)
|
|
126
|
+
bar.add(Spacer()) # everything after this is pushed right
|
|
127
|
+
bar.add(btn_a)
|
|
128
|
+
bar.add(btn_b)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
When added to a `FlexRow`/`FlexCol`, `Spacer` defaults to `grow=1`.
|
|
132
|
+
|
|
133
|
+
<br>
|
|
134
|
+
|
|
135
|
+
## customtkinter compatibility
|
|
136
|
+
|
|
137
|
+
All containers accept `fg_color` as an alias for `bg` and default to
|
|
138
|
+
`"transparent"` so they blend seamlessly into CTk windows:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
import customtkinter as ctk
|
|
142
|
+
from snakeflex import FlexRow
|
|
143
|
+
|
|
144
|
+
app = ctk.CTk()
|
|
145
|
+
bar = FlexRow(app, fg_color="transparent", gap=8)
|
|
146
|
+
bar.add(ctk.CTkLabel(bar, text="Hello"))
|
|
147
|
+
bar.add(ctk.CTkButton(bar, text="Go"), grow=1)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
<hr>
|
|
151
|
+
|
|
152
|
+
## Installation
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
pip install snakeflex
|
|
156
|
+
```
|
|
157
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "snakeflex"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Flexbox and CSS Grid-inspired layout utilities for tkinter and customtkinter"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "SnakePlayer contributors" }]
|
|
12
|
+
keywords = ["tkinter", "customtkinter", "layout", "flexbox", "css-grid", "gui"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
"Topic :: Software Development :: User Interfaces",
|
|
25
|
+
]
|
|
26
|
+
requires-python = ">=3.10"
|
|
27
|
+
dependencies = []
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/toomeydev/snakeflex"
|
|
31
|
+
Repository = "https://github.com/toomeydev/snakeflex"
|
|
32
|
+
Issues = "https://github.com/toomeydev/snakeflex/issues"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.package-data]
|
|
38
|
+
snakeflex = ["py.typed"]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""snakeflex — Flexbox and CSS Grid-inspired layout primitives for tkinter.
|
|
2
|
+
|
|
3
|
+
Works with standard tkinter and customtkinter widgets. Zero external
|
|
4
|
+
dependencies — just Python and tkinter.
|
|
5
|
+
|
|
6
|
+
Quick start
|
|
7
|
+
-----------
|
|
8
|
+
::
|
|
9
|
+
|
|
10
|
+
from snakeflex import FlexRow, FlexCol, GridFrame, Spacer
|
|
11
|
+
|
|
12
|
+
# Horizontal bar: left label, growing spacer, right button
|
|
13
|
+
bar = FlexRow(parent, justify="space-between", align="center", gap=8)
|
|
14
|
+
bar.add(title_label)
|
|
15
|
+
bar.add(Spacer())
|
|
16
|
+
bar.add(settings_btn)
|
|
17
|
+
|
|
18
|
+
# Vertical panel: fixed header, growing content, fixed footer
|
|
19
|
+
panel = FlexCol(parent, align="stretch", gap=4)
|
|
20
|
+
panel.add(header, grow=0)
|
|
21
|
+
panel.add(content, grow=1)
|
|
22
|
+
panel.add(footer, grow=0)
|
|
23
|
+
|
|
24
|
+
# CSS Grid with fr units
|
|
25
|
+
layout = GridFrame(parent, columns="220px 1fr", rows="48px 1fr 28px")
|
|
26
|
+
layout.add(topbar, col=0, row=0, colspan=2)
|
|
27
|
+
layout.add(sidebar, col=0, row=1)
|
|
28
|
+
layout.add(main, col=1, row=1)
|
|
29
|
+
layout.add(statusbar, col=0, row=2, colspan=2)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from .flex import FlexFrame, FlexRow, FlexCol
|
|
33
|
+
from .grid_frame import GridFrame
|
|
34
|
+
from .spacer import Spacer
|
|
35
|
+
from .responsive import ResponsiveMixin
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"FlexFrame",
|
|
39
|
+
"FlexRow",
|
|
40
|
+
"FlexCol",
|
|
41
|
+
"GridFrame",
|
|
42
|
+
"Spacer",
|
|
43
|
+
"ResponsiveMixin",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
__version__ = "0.1.0"
|
|
47
|
+
__author__ = "SnakePlayer contributors"
|
|
48
|
+
__license__ = "MIT"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""snakeflex._base — shared base class for all layout containers.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
• fg_color kwarg → bg shim (customtkinter compatibility)
|
|
5
|
+
• transparent background resolution by walking the parent widget tree
|
|
6
|
+
• coalesced after(0) relayout scheduling
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import tkinter as tk
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _resolve_bg(parent: tk.Widget, fg_color: Optional[str]) -> Optional[str]:
|
|
15
|
+
"""Map fg_color to a real tk bg colour string.
|
|
16
|
+
|
|
17
|
+
Rules
|
|
18
|
+
-----
|
|
19
|
+
None → don't set bg at all (inherit Tk default)
|
|
20
|
+
"transparent" → walk up the parent tree until a concrete bg is found
|
|
21
|
+
anything else → use as-is
|
|
22
|
+
"""
|
|
23
|
+
if fg_color is None:
|
|
24
|
+
return None
|
|
25
|
+
if fg_color != "transparent":
|
|
26
|
+
return fg_color
|
|
27
|
+
# Walk up looking for a concrete colour.
|
|
28
|
+
node = parent
|
|
29
|
+
while node is not None:
|
|
30
|
+
try:
|
|
31
|
+
bg = node.cget("bg")
|
|
32
|
+
if bg and bg != "transparent":
|
|
33
|
+
return bg
|
|
34
|
+
except tk.TclError:
|
|
35
|
+
pass
|
|
36
|
+
node = getattr(node, "master", None)
|
|
37
|
+
return None # give up; Tk will use its default
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _FlexBase(tk.Frame):
|
|
41
|
+
"""Base frame for FlexFrame and GridFrame.
|
|
42
|
+
|
|
43
|
+
Accepts ``fg_color`` as an alias for ``bg`` so code written against
|
|
44
|
+
customtkinter's CTkFrame API compiles without modification::
|
|
45
|
+
|
|
46
|
+
FlexRow(parent, fg_color="#1e1e2e")
|
|
47
|
+
FlexRow(parent, fg_color="transparent")
|
|
48
|
+
FlexRow(parent, bg="#1e1e2e") # plain tk style also works
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, parent: tk.Widget, *, fg_color: Optional[str] = "transparent",
|
|
52
|
+
**kw) -> None:
|
|
53
|
+
# Resolve fg_color → bg before tk.Frame() runs so the frame is
|
|
54
|
+
# created with the right colour from the start.
|
|
55
|
+
if fg_color is not None and "bg" not in kw:
|
|
56
|
+
resolved = _resolve_bg(parent, fg_color)
|
|
57
|
+
if resolved is not None:
|
|
58
|
+
kw["bg"] = resolved
|
|
59
|
+
|
|
60
|
+
super().__init__(parent, **kw)
|
|
61
|
+
self._relayout_pending = False
|
|
62
|
+
|
|
63
|
+
def _schedule_relayout(self) -> None:
|
|
64
|
+
"""Queue a relayout on the next idle tick, coalescing multiple calls."""
|
|
65
|
+
if not self._relayout_pending:
|
|
66
|
+
self._relayout_pending = True
|
|
67
|
+
self.after(0, self._do_relayout)
|
|
68
|
+
|
|
69
|
+
def _do_relayout(self) -> None:
|
|
70
|
+
"""Subclasses override this to perform the actual grid reconfiguration."""
|
|
71
|
+
self._relayout_pending = False
|