ctkforge 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.
- ctkforge-0.1.0/PKG-INFO +149 -0
- ctkforge-0.1.0/README.md +139 -0
- ctkforge-0.1.0/pyproject.toml +23 -0
- ctkforge-0.1.0/setup.cfg +4 -0
- ctkforge-0.1.0/src/ctkforge/__init__.py +26 -0
- ctkforge-0.1.0/src/ctkforge/debug.py +95 -0
- ctkforge-0.1.0/src/ctkforge/geometry.py +96 -0
- ctkforge-0.1.0/src/ctkforge/popup/__init__.py +20 -0
- ctkforge-0.1.0/src/ctkforge/popup/buttons.py +145 -0
- ctkforge-0.1.0/src/ctkforge/popup/constants.py +107 -0
- ctkforge-0.1.0/src/ctkforge/popup/icons/error.png +0 -0
- ctkforge-0.1.0/src/ctkforge/popup/icons/info.png +0 -0
- ctkforge-0.1.0/src/ctkforge/popup/icons/question.png +0 -0
- ctkforge-0.1.0/src/ctkforge/popup/icons/success.png +0 -0
- ctkforge-0.1.0/src/ctkforge/popup/icons/warning.png +0 -0
- ctkforge-0.1.0/src/ctkforge/popup/icons.py +135 -0
- ctkforge-0.1.0/src/ctkforge/popup/popup.py +389 -0
- ctkforge-0.1.0/src/ctkforge/resources.py +84 -0
- ctkforge-0.1.0/src/ctkforge/spinbox/__init__.py +5 -0
- ctkforge-0.1.0/src/ctkforge/spinbox/spinbox.py +322 -0
- ctkforge-0.1.0/src/ctkforge/table/__init__.py +5 -0
- ctkforge-0.1.0/src/ctkforge/table/table.py +2117 -0
- ctkforge-0.1.0/src/ctkforge.egg-info/PKG-INFO +149 -0
- ctkforge-0.1.0/src/ctkforge.egg-info/SOURCES.txt +25 -0
- ctkforge-0.1.0/src/ctkforge.egg-info/dependency_links.txt +1 -0
- ctkforge-0.1.0/src/ctkforge.egg-info/requires.txt +2 -0
- ctkforge-0.1.0/src/ctkforge.egg-info/top_level.txt +1 -0
ctkforge-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ctkforge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CustomTkinter extension components and widgets
|
|
5
|
+
Author: Rafalus
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: customtkinter>=6.0.0
|
|
9
|
+
Requires-Dist: pillow>=12.0.0
|
|
10
|
+
|
|
11
|
+
# CTkForge
|
|
12
|
+
|
|
13
|
+
Custom extension components and widgets for [CustomTkinter](https://github.com/TomSchimansky/CustomTkinter).
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install ctkforge
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **[CTkPopup](docs/CTkPopup.md)**: A modern popup dialog widget for alerts, confirmations, and error messages. Includes ready-to-use types (info, warning, error, question, success), keyboard shortcuts (Enter to confirm, Escape to cancel), clean Windows 11 button styling, and automatically turns into a scrollable box for long error text. See [detailed documentation](docs/CTkPopup.md) or run [example_ctkpopup.py](examples/example_ctkpopup.py).
|
|
24
|
+
|
|
25
|
+
- **[CTkSpinbox](docs/CTkSpinbox.md)**: A number input widget with classic up and down arrow buttons (`▲`/`▼`). Supports mouse wheel scrolling, arrow key adjustments, minimum/maximum limits, custom steps, and matches CustomTkinter's colors and rounded corners. See [detailed documentation](docs/CTkSpinbox.md) or run [example_ctkspinbox.py](examples/example_ctkspinbox.py).
|
|
26
|
+
|
|
27
|
+
- **[CTkTable](docs/table.md)**: A fast, smooth data table widget built to handle massive datasets without slowing down. Features smart column sorting, row and cell selection, double-click cell editing, Excel-compatible copy-pasting (`Ctrl+C`), auto-fitting column widths, and optional alternating row colors. See [API reference](docs/table.md), [user guide](docs/table_user_guide.md), or run [demo_ctktable.py](examples/demo_ctktable.py).
|
|
28
|
+
- **Universal DPI `unscale` Helper**: Utility function to convert raw physical pixel dimensions from Tkinter (`winfo_*`) back to unscaled 100% baseline (96 DPI) values for CustomTkinter windows.
|
|
29
|
+
- **Resource Locator (`resource_path`)**: Robust asset path resolver that finds images, icons, audio, and data files across direct filesystem paths, the script entry directory (`sys.argv[0]`), and PyInstaller single-file bundle extractions (`sys._MEIPASS`).
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### 1. CTkPopup Dialogs
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import customtkinter as ctk
|
|
37
|
+
from ctkforge import CTkPopup
|
|
38
|
+
|
|
39
|
+
app = ctk.CTk()
|
|
40
|
+
|
|
41
|
+
# Quick confirmation dialog (Enter for Yes, Escape for No)
|
|
42
|
+
popup = CTkPopup(
|
|
43
|
+
master=app,
|
|
44
|
+
title="Confirm Action",
|
|
45
|
+
message="Do you want to proceed with the update?",
|
|
46
|
+
type="question",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
choice = popup.get()
|
|
50
|
+
if choice is True:
|
|
51
|
+
print("User clicked Yes")
|
|
52
|
+
else:
|
|
53
|
+
print("User clicked No or closed the dialog")
|
|
54
|
+
|
|
55
|
+
app.mainloop()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
*(See [example_ctkpopup.py](examples/example_ctkpopup.py) and [demo_ctkpopup.py](examples/demo_ctkpopup.py) for interactive demos).*
|
|
59
|
+
|
|
60
|
+
### 2. CTkSpinbox
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import customtkinter as ctk
|
|
64
|
+
from ctkforge import CTkSpinbox
|
|
65
|
+
|
|
66
|
+
app = ctk.CTk()
|
|
67
|
+
|
|
68
|
+
spinbox = CTkSpinbox(
|
|
69
|
+
app,
|
|
70
|
+
start_value=25,
|
|
71
|
+
min_value=1,
|
|
72
|
+
max_value=100,
|
|
73
|
+
step=1,
|
|
74
|
+
width=120,
|
|
75
|
+
height=38,
|
|
76
|
+
)
|
|
77
|
+
spinbox.pack(padx=20, pady=20)
|
|
78
|
+
|
|
79
|
+
app.mainloop()
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
*(See [example_ctkspinbox.py](examples/example_ctkspinbox.py) for the standalone snippet, and [demo_ctkspinbox.py](examples/demo_ctkspinbox.py) for a complete app).*
|
|
83
|
+
|
|
84
|
+
### 3. CTkTable Data Grid
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import customtkinter as ctk
|
|
88
|
+
from ctkforge import CTkTable
|
|
89
|
+
|
|
90
|
+
app = ctk.CTk()
|
|
91
|
+
app.geometry("800x400")
|
|
92
|
+
|
|
93
|
+
headers = ["ID", "Employee Name", "Department", "Salary"]
|
|
94
|
+
data = [
|
|
95
|
+
["101", "Ada Lovelace", "Research", "$125,000"],
|
|
96
|
+
["102", "Alan Turing", "Data Systems", "$135,000"],
|
|
97
|
+
["103", "Grace Hopper", "Engineering", "$130,000"],
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
table = CTkTable(
|
|
101
|
+
app,
|
|
102
|
+
headers=headers,
|
|
103
|
+
values=data,
|
|
104
|
+
column_widths=[90, 220, 180, 140],
|
|
105
|
+
column_types={0: "int"},
|
|
106
|
+
selection_target="row",
|
|
107
|
+
selection_mode="extended",
|
|
108
|
+
editable=True,
|
|
109
|
+
alternate_row_color=("gray92", "gray14"),
|
|
110
|
+
)
|
|
111
|
+
table.pack(fill="both", expand=True, padx=20, pady=20)
|
|
112
|
+
|
|
113
|
+
app.mainloop()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
*(See [example_ctktable.py](examples/example_ctktable.py) and [demo_ctktable.py](examples/demo_ctktable.py) for interactive demos).*
|
|
117
|
+
|
|
118
|
+
### 4. Universal DPI Unscaling (`unscale`)
|
|
119
|
+
|
|
120
|
+
Convert raw physical pixel values from Tkinter (`winfo_*`) back to unscaled 100% baseline (96 DPI) values to prevent double-scaling bugs when setting CustomTkinter geometry on High-DPI displays:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import customtkinter as ctk
|
|
124
|
+
from ctkforge import unscale
|
|
125
|
+
|
|
126
|
+
app = ctk.CTk()
|
|
127
|
+
app.update_idletasks()
|
|
128
|
+
|
|
129
|
+
# Unscale multiple physical dimensions simultaneously:
|
|
130
|
+
width, height = unscale(app, app.winfo_width(), app.winfo_height())
|
|
131
|
+
|
|
132
|
+
# Or unscale a single coordinate:
|
|
133
|
+
root_x = unscale(app, app.winfo_rootx())
|
|
134
|
+
|
|
135
|
+
# Set window geometry safely without High-DPI drift:
|
|
136
|
+
app.geometry(f"{width}x{height}")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 5. Resource Path Resolution (`resource_path`)
|
|
140
|
+
|
|
141
|
+
Find files reliably in both development and PyInstaller single-file executables (`.exe`):
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from ctkforge import resource_path
|
|
145
|
+
|
|
146
|
+
# Finds asset whether running from terminal, script folder, or PyInstaller bundle
|
|
147
|
+
icon_path = resource_path("assets/custom_icon.png")
|
|
148
|
+
```
|
|
149
|
+
|
ctkforge-0.1.0/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# CTkForge
|
|
2
|
+
|
|
3
|
+
Custom extension components and widgets for [CustomTkinter](https://github.com/TomSchimansky/CustomTkinter).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install ctkforge
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **[CTkPopup](docs/CTkPopup.md)**: A modern popup dialog widget for alerts, confirmations, and error messages. Includes ready-to-use types (info, warning, error, question, success), keyboard shortcuts (Enter to confirm, Escape to cancel), clean Windows 11 button styling, and automatically turns into a scrollable box for long error text. See [detailed documentation](docs/CTkPopup.md) or run [example_ctkpopup.py](examples/example_ctkpopup.py).
|
|
14
|
+
|
|
15
|
+
- **[CTkSpinbox](docs/CTkSpinbox.md)**: A number input widget with classic up and down arrow buttons (`▲`/`▼`). Supports mouse wheel scrolling, arrow key adjustments, minimum/maximum limits, custom steps, and matches CustomTkinter's colors and rounded corners. See [detailed documentation](docs/CTkSpinbox.md) or run [example_ctkspinbox.py](examples/example_ctkspinbox.py).
|
|
16
|
+
|
|
17
|
+
- **[CTkTable](docs/table.md)**: A fast, smooth data table widget built to handle massive datasets without slowing down. Features smart column sorting, row and cell selection, double-click cell editing, Excel-compatible copy-pasting (`Ctrl+C`), auto-fitting column widths, and optional alternating row colors. See [API reference](docs/table.md), [user guide](docs/table_user_guide.md), or run [demo_ctktable.py](examples/demo_ctktable.py).
|
|
18
|
+
- **Universal DPI `unscale` Helper**: Utility function to convert raw physical pixel dimensions from Tkinter (`winfo_*`) back to unscaled 100% baseline (96 DPI) values for CustomTkinter windows.
|
|
19
|
+
- **Resource Locator (`resource_path`)**: Robust asset path resolver that finds images, icons, audio, and data files across direct filesystem paths, the script entry directory (`sys.argv[0]`), and PyInstaller single-file bundle extractions (`sys._MEIPASS`).
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### 1. CTkPopup Dialogs
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import customtkinter as ctk
|
|
27
|
+
from ctkforge import CTkPopup
|
|
28
|
+
|
|
29
|
+
app = ctk.CTk()
|
|
30
|
+
|
|
31
|
+
# Quick confirmation dialog (Enter for Yes, Escape for No)
|
|
32
|
+
popup = CTkPopup(
|
|
33
|
+
master=app,
|
|
34
|
+
title="Confirm Action",
|
|
35
|
+
message="Do you want to proceed with the update?",
|
|
36
|
+
type="question",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
choice = popup.get()
|
|
40
|
+
if choice is True:
|
|
41
|
+
print("User clicked Yes")
|
|
42
|
+
else:
|
|
43
|
+
print("User clicked No or closed the dialog")
|
|
44
|
+
|
|
45
|
+
app.mainloop()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
*(See [example_ctkpopup.py](examples/example_ctkpopup.py) and [demo_ctkpopup.py](examples/demo_ctkpopup.py) for interactive demos).*
|
|
49
|
+
|
|
50
|
+
### 2. CTkSpinbox
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import customtkinter as ctk
|
|
54
|
+
from ctkforge import CTkSpinbox
|
|
55
|
+
|
|
56
|
+
app = ctk.CTk()
|
|
57
|
+
|
|
58
|
+
spinbox = CTkSpinbox(
|
|
59
|
+
app,
|
|
60
|
+
start_value=25,
|
|
61
|
+
min_value=1,
|
|
62
|
+
max_value=100,
|
|
63
|
+
step=1,
|
|
64
|
+
width=120,
|
|
65
|
+
height=38,
|
|
66
|
+
)
|
|
67
|
+
spinbox.pack(padx=20, pady=20)
|
|
68
|
+
|
|
69
|
+
app.mainloop()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
*(See [example_ctkspinbox.py](examples/example_ctkspinbox.py) for the standalone snippet, and [demo_ctkspinbox.py](examples/demo_ctkspinbox.py) for a complete app).*
|
|
73
|
+
|
|
74
|
+
### 3. CTkTable Data Grid
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import customtkinter as ctk
|
|
78
|
+
from ctkforge import CTkTable
|
|
79
|
+
|
|
80
|
+
app = ctk.CTk()
|
|
81
|
+
app.geometry("800x400")
|
|
82
|
+
|
|
83
|
+
headers = ["ID", "Employee Name", "Department", "Salary"]
|
|
84
|
+
data = [
|
|
85
|
+
["101", "Ada Lovelace", "Research", "$125,000"],
|
|
86
|
+
["102", "Alan Turing", "Data Systems", "$135,000"],
|
|
87
|
+
["103", "Grace Hopper", "Engineering", "$130,000"],
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
table = CTkTable(
|
|
91
|
+
app,
|
|
92
|
+
headers=headers,
|
|
93
|
+
values=data,
|
|
94
|
+
column_widths=[90, 220, 180, 140],
|
|
95
|
+
column_types={0: "int"},
|
|
96
|
+
selection_target="row",
|
|
97
|
+
selection_mode="extended",
|
|
98
|
+
editable=True,
|
|
99
|
+
alternate_row_color=("gray92", "gray14"),
|
|
100
|
+
)
|
|
101
|
+
table.pack(fill="both", expand=True, padx=20, pady=20)
|
|
102
|
+
|
|
103
|
+
app.mainloop()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
*(See [example_ctktable.py](examples/example_ctktable.py) and [demo_ctktable.py](examples/demo_ctktable.py) for interactive demos).*
|
|
107
|
+
|
|
108
|
+
### 4. Universal DPI Unscaling (`unscale`)
|
|
109
|
+
|
|
110
|
+
Convert raw physical pixel values from Tkinter (`winfo_*`) back to unscaled 100% baseline (96 DPI) values to prevent double-scaling bugs when setting CustomTkinter geometry on High-DPI displays:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import customtkinter as ctk
|
|
114
|
+
from ctkforge import unscale
|
|
115
|
+
|
|
116
|
+
app = ctk.CTk()
|
|
117
|
+
app.update_idletasks()
|
|
118
|
+
|
|
119
|
+
# Unscale multiple physical dimensions simultaneously:
|
|
120
|
+
width, height = unscale(app, app.winfo_width(), app.winfo_height())
|
|
121
|
+
|
|
122
|
+
# Or unscale a single coordinate:
|
|
123
|
+
root_x = unscale(app, app.winfo_rootx())
|
|
124
|
+
|
|
125
|
+
# Set window geometry safely without High-DPI drift:
|
|
126
|
+
app.geometry(f"{width}x{height}")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 5. Resource Path Resolution (`resource_path`)
|
|
130
|
+
|
|
131
|
+
Find files reliably in both development and PyInstaller single-file executables (`.exe`):
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from ctkforge import resource_path
|
|
135
|
+
|
|
136
|
+
# Finds asset whether running from terminal, script folder, or PyInstaller bundle
|
|
137
|
+
icon_path = resource_path("assets/custom_icon.png")
|
|
138
|
+
```
|
|
139
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ctkforge"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CustomTkinter extension components and widgets"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Rafalus" },
|
|
12
|
+
]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"customtkinter>=6.0.0",
|
|
16
|
+
"pillow>=12.0.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["src"]
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.package-data]
|
|
23
|
+
"*" = ["*.png"]
|
ctkforge-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""CustomTkinter extension components and widgets."""
|
|
2
|
+
|
|
3
|
+
from ctkforge.debug import (
|
|
4
|
+
DEBUG_BANNER,
|
|
5
|
+
enable_debug_mode,
|
|
6
|
+
is_debug_mode,
|
|
7
|
+
print_window_telemetry,
|
|
8
|
+
)
|
|
9
|
+
from ctkforge.geometry import scale, unscale
|
|
10
|
+
from ctkforge.popup import CTkPopup
|
|
11
|
+
from ctkforge.resources import resource_path
|
|
12
|
+
from ctkforge.spinbox import CTkSpinbox
|
|
13
|
+
from ctkforge.table import CTkTable
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"DEBUG_BANNER",
|
|
17
|
+
"CTkPopup",
|
|
18
|
+
"CTkSpinbox",
|
|
19
|
+
"CTkTable",
|
|
20
|
+
"enable_debug_mode",
|
|
21
|
+
"is_debug_mode",
|
|
22
|
+
"print_window_telemetry",
|
|
23
|
+
"resource_path",
|
|
24
|
+
"scale",
|
|
25
|
+
"unscale",
|
|
26
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Runtime telemetry and debug tools for CustomTkinter windows."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ctkforge.geometry import unscale
|
|
6
|
+
|
|
7
|
+
DEBUG_BANNER: str = " === DEBUG MODE IS ACTIVE ==="
|
|
8
|
+
_DEBUG_ACTIVE: bool = False
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"DEBUG_BANNER",
|
|
12
|
+
"enable_debug_mode",
|
|
13
|
+
"is_debug_mode",
|
|
14
|
+
"print_window_telemetry",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def is_debug_mode() -> bool:
|
|
19
|
+
"""Return whether debug mode is currently active across the application."""
|
|
20
|
+
return _DEBUG_ACTIVE
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def print_window_telemetry(window: Any) -> None:
|
|
24
|
+
"""Print size, DPI scaling, and baseline dimensions for the active window.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
window: The active Tk or CustomTkinter window to inspect.
|
|
28
|
+
"""
|
|
29
|
+
if not hasattr(window, "winfo_exists") or not window.winfo_exists():
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
window.update_idletasks()
|
|
33
|
+
width = window.winfo_width()
|
|
34
|
+
height = window.winfo_height()
|
|
35
|
+
base_w, base_h = unscale(window, width, height)
|
|
36
|
+
base_req_w, base_req_h = unscale(
|
|
37
|
+
window, window.winfo_reqwidth(), window.winfo_reqheight()
|
|
38
|
+
)
|
|
39
|
+
scaling = (
|
|
40
|
+
window._get_window_scaling()
|
|
41
|
+
if hasattr(window, "_get_window_scaling")
|
|
42
|
+
else getattr(window, "_get_widget_scaling", lambda: 1.0)()
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Resolve window title
|
|
46
|
+
title_text = (
|
|
47
|
+
window.title()
|
|
48
|
+
if callable(getattr(window, "title", None))
|
|
49
|
+
else getattr(window, "_title_text", "Untitled")
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
class_name = window.__class__.__name__
|
|
53
|
+
|
|
54
|
+
print(f"\n--- {class_name} Telemetry ---")
|
|
55
|
+
print(f"Title: {title_text}")
|
|
56
|
+
print(
|
|
57
|
+
f"Physical Size: {width}x{height} px @ {scaling:g} ({int(scaling * 100)}%) Scaling"
|
|
58
|
+
)
|
|
59
|
+
print(f"Baseline Size: {base_w}x{base_h} px (96 DPI)")
|
|
60
|
+
if hasattr(window, "_target_width"):
|
|
61
|
+
print(f"Target Width: {window._target_width} px")
|
|
62
|
+
print(
|
|
63
|
+
f"Req Size: {base_req_w}x{base_req_h} px (base) / {window.winfo_reqwidth()}x{window.winfo_reqheight()} px (phys)"
|
|
64
|
+
)
|
|
65
|
+
print("-" * (len(class_name) + 18) + "\n")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def enable_debug_mode(app: Any, banner: str = DEBUG_BANNER) -> None:
|
|
69
|
+
"""Activate debug mode across the application, popups, and future windows.
|
|
70
|
+
|
|
71
|
+
This function:
|
|
72
|
+
1. Sets the global debug state to active.
|
|
73
|
+
2. Appends the debug banner to the root application window title.
|
|
74
|
+
3. Binds <F12> globally so pressing F12 in any focused window prints its telemetry.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
app: The root CTk or CTkToplevel application instance.
|
|
78
|
+
banner: Text appended to the window title to show debug is active.
|
|
79
|
+
"""
|
|
80
|
+
global _DEBUG_ACTIVE
|
|
81
|
+
_DEBUG_ACTIVE = True
|
|
82
|
+
|
|
83
|
+
# Append banner to app title if not already present
|
|
84
|
+
if hasattr(app, "title") and callable(app.title):
|
|
85
|
+
current_title = app.title()
|
|
86
|
+
if banner not in current_title:
|
|
87
|
+
app.title(f"{current_title}{banner}")
|
|
88
|
+
|
|
89
|
+
def _handle_f12(event: Any) -> None:
|
|
90
|
+
target_window = event.widget.winfo_toplevel()
|
|
91
|
+
print_window_telemetry(target_window)
|
|
92
|
+
|
|
93
|
+
# Bind globally so whichever window has focus receives the F12 telemetry dump
|
|
94
|
+
if hasattr(app, "bind_all") and callable(app.bind_all):
|
|
95
|
+
app.bind_all("<F12>", _handle_f12)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""DPI-aware geometry, scaling, and coordinate conversion helpers for CustomTkinter."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, overload
|
|
4
|
+
|
|
5
|
+
__all__ = ["scale", "unscale"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _get_scaling(target: Any) -> float:
|
|
9
|
+
"""Safely extract active DPI scaling factor from a CTk widget or window.
|
|
10
|
+
|
|
11
|
+
CustomTkinter defines `_get_window_scaling()` on CTkBaseClass, so `hasattr`
|
|
12
|
+
returns True for all widgets. However, the underlying private `__window_scaling`
|
|
13
|
+
attribute only exists on root `CTk` windows. Calling it on child widgets raises
|
|
14
|
+
AttributeError. We inspect `_get_widget_scaling` first and safely catch
|
|
15
|
+
AttributeError if `_get_window_scaling` is called on a child widget.
|
|
16
|
+
"""
|
|
17
|
+
for method_name in ("_get_widget_scaling", "_get_window_scaling"):
|
|
18
|
+
method = getattr(target, method_name, None)
|
|
19
|
+
if callable(method):
|
|
20
|
+
try:
|
|
21
|
+
val = method()
|
|
22
|
+
if isinstance(val, (int, float)) and val > 0:
|
|
23
|
+
return float(val)
|
|
24
|
+
except AttributeError:
|
|
25
|
+
continue
|
|
26
|
+
return 1.0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@overload
|
|
30
|
+
def unscale(target: Any, value: float, /) -> int: ...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@overload
|
|
34
|
+
def unscale(
|
|
35
|
+
target: Any,
|
|
36
|
+
value1: float,
|
|
37
|
+
value2: float,
|
|
38
|
+
/,
|
|
39
|
+
*values: float,
|
|
40
|
+
) -> tuple[int, ...]: ...
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def unscale(target: Any, *values: float) -> int | tuple[int, ...]:
|
|
44
|
+
"""Convert physical (device hardware) pixel values to logical (96 DPI baseline) integers.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
target: A CTk widget or window providing the active DPI scaling factor.
|
|
48
|
+
*values: One or more physical pixel values to convert to logical units.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
A single logical integer if one value was provided, or a tuple of logical integers if multiple.
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If no values are passed.
|
|
55
|
+
"""
|
|
56
|
+
if not values:
|
|
57
|
+
raise ValueError("unscale() requires at least one value to convert.")
|
|
58
|
+
|
|
59
|
+
scaling = _get_scaling(target)
|
|
60
|
+
result = tuple(round(v / scaling) if scaling else round(v) for v in values)
|
|
61
|
+
return result[0] if len(result) == 1 else result
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@overload
|
|
65
|
+
def scale(target: Any, value: float, /) -> int: ...
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@overload
|
|
69
|
+
def scale(
|
|
70
|
+
target: Any,
|
|
71
|
+
value1: float,
|
|
72
|
+
value2: float,
|
|
73
|
+
/,
|
|
74
|
+
*values: float,
|
|
75
|
+
) -> tuple[int, ...]: ...
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def scale(target: Any, *values: float) -> int | tuple[int, ...]:
|
|
79
|
+
"""Convert logical (96 DPI baseline) pixel values to physical (device hardware) integers.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
target: A CTk widget or window providing the active DPI scaling factor.
|
|
83
|
+
*values: One or more logical pixel values to convert to physical device units.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
A single physical integer if one value was provided, or a tuple of physical integers if multiple.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: If no values are passed.
|
|
90
|
+
"""
|
|
91
|
+
if not values:
|
|
92
|
+
raise ValueError("scale() requires at least one value to convert.")
|
|
93
|
+
|
|
94
|
+
scaling = _get_scaling(target)
|
|
95
|
+
result = tuple(round(v * scaling) if scaling else round(v) for v in values)
|
|
96
|
+
return result[0] if len(result) == 1 else result
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""CTkPopup - A robust, modern, and DPI-aware dialog replacement for CTkMessagebox.
|
|
2
|
+
|
|
3
|
+
Features:
|
|
4
|
+
- 4 semantic button roles (primary, cancel, destructive, normal) with dedicated resting and hover colors
|
|
5
|
+
- Decoupled semantic return values (booleans, tokens)
|
|
6
|
+
- Hidden-staging window lifecycle preventing geometry jumps
|
|
7
|
+
- The Courier Rule: safe return values without widget destruction errors
|
|
8
|
+
- Automatic switching to copyable CTkTextbox for long logs and exceptions
|
|
9
|
+
- Option B Smart Fail-Safe icon resolution
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .constants import PRESETS, ROLE_COLORS
|
|
13
|
+
from .popup import CTkPopup
|
|
14
|
+
|
|
15
|
+
__version__ = "1.0.0"
|
|
16
|
+
__all__ = [
|
|
17
|
+
"PRESETS",
|
|
18
|
+
"ROLE_COLORS",
|
|
19
|
+
"CTkPopup",
|
|
20
|
+
]
|