flexgui 1.0.0__tar.gz → 1.0.2__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.
- {flexgui-1.0.0 → flexgui-1.0.2}/PKG-INFO +40 -6
- {flexgui-1.0.0 → flexgui-1.0.2}/README.md +39 -5
- {flexgui-1.0.0 → flexgui-1.0.2}/flexgui/__init__.py +1 -1
- {flexgui-1.0.0 → flexgui-1.0.2}/flexgui/app.py +11 -1
- {flexgui-1.0.0 → flexgui-1.0.2}/flexgui/builder.py +2 -2
- {flexgui-1.0.0 → flexgui-1.0.2}/flexgui/designer.py +139 -48
- {flexgui-1.0.0 → flexgui-1.0.2}/flexgui/registry.py +8 -0
- {flexgui-1.0.0 → flexgui-1.0.2}/flexgui/theme.py +38 -4
- {flexgui-1.0.0 → flexgui-1.0.2}/flexgui/widgets.py +76 -10
- {flexgui-1.0.0 → flexgui-1.0.2}/pyproject.toml +1 -1
- {flexgui-1.0.0 → flexgui-1.0.2}/LICENSE +0 -0
- {flexgui-1.0.0 → flexgui-1.0.2}/flexgui/editor.py +0 -0
- {flexgui-1.0.0 → flexgui-1.0.2}/flexgui/mode.py +0 -0
- {flexgui-1.0.0 → flexgui-1.0.2}/flexgui/runtime.py +0 -0
- {flexgui-1.0.0 → flexgui-1.0.2}/flexgui/triggers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: flexgui
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Thư viện GUI Python kéo-thả-thiết-kế trên tkinter — FlexGUI Studio tích hợp (design mode + mini VS Code cho trigger)
|
|
5
5
|
Keywords: gui,tkinter,designer,drag-and-drop,visual-designer,no-code,vietnamese,flexgui
|
|
6
6
|
Author: FlexGUI Team
|
|
@@ -78,9 +78,9 @@ thiết kế đã lưu). Tính năng:
|
|
|
78
78
|
- **Multi-select**: Ctrl/Shift + click, quét vùng chuột, kéo cả cụm
|
|
79
79
|
- Đổi z-order (menu chuột phải), nhân bản `Ctrl+D`, undo/redo `Ctrl+Z/Y`
|
|
80
80
|
- Bảng thuộc tính bên phải: chữ, tọa độ, kích thước **pixel WYSIWYG**,
|
|
81
|
-
màu chữ/nền, cỡ chữ, ẩn khi chạy, khoá...
|
|
81
|
+
màu chữ/nền, cỡ chữ, **độ trong suốt 0–100%**, ẩn khi chạy, khoá...
|
|
82
82
|
- **Xem trước F5** — chạy thử app thật + trigger thật ngay tại chỗ
|
|
83
|
-
- **Build .exe** bằng PyInstaller (nút
|
|
83
|
+
- **Build .exe** bằng PyInstaller (nút ❖ — tự cài nếu thiếu)
|
|
84
84
|
- **Xuất .py** — 1 file tự chạy nhúng toàn bộ thiết kế + trigger
|
|
85
85
|
|
|
86
86
|
`Ctrl+S` lưu 2 file cạnh `myapp.py`:
|
|
@@ -93,7 +93,7 @@ thiết kế đã lưu). Tính năng:
|
|
|
93
93
|
## 3. Trigger — sửa ở Studio HOẶC VS Code đều được
|
|
94
94
|
|
|
95
95
|
`myapp.triggers.py` là file Python thật: mở bằng **mini VS Code** trong
|
|
96
|
-
Studio (nút
|
|
96
|
+
Studio (nút `</> Triggers`) **hoặc** VS Code ngoài — cả hai sửa cùng một file.
|
|
97
97
|
|
|
98
98
|
```python
|
|
99
99
|
# myapp.triggers.py — label/button/... đã có sẵn, không cần import
|
|
@@ -159,8 +159,9 @@ label[1].set_text("...") # tương đương
|
|
|
159
159
|
label[1].get_text()
|
|
160
160
|
label[1].x = 200 # tọa độ pixel (đọc/ghi)
|
|
161
161
|
label[1].color = "red" # màu chữ
|
|
162
|
-
label[1].bg = "#FFF3B0" # màu nền
|
|
162
|
+
label[1].bg = "#FFF3B0" # màu nền GỐC
|
|
163
163
|
label[1].font_size = 14
|
|
164
|
+
label[1].transparency = 70 # ĐỘ TRONG SUỐT nền 0–100%
|
|
164
165
|
label[1].visible = False # tàng hình / hiện lại
|
|
165
166
|
label[1].enabled = False # khoá / mở
|
|
166
167
|
label[1].place(120, 80) # đặt lại vị trí tuyệt đối
|
|
@@ -179,6 +180,24 @@ alert("...") · confirm("...?") → True/False · prompt("Tên?") → str/None
|
|
|
179
180
|
close_app()
|
|
180
181
|
```
|
|
181
182
|
|
|
183
|
+
### Độ trong suốt (transparent background)
|
|
184
|
+
|
|
185
|
+
`transparency` chạy từ `0` (nền đậm nguyên gốc) đến `100` (hòa hoàn toàn
|
|
186
|
+
vào màu nền cửa sổ — trông như không có nền). tkinter không có alpha thật
|
|
187
|
+
cho từng widget nên FlexGUI **pha màu nền widget về đúng màu nền cửa sổ**:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
label[1].bg = "#0078d4" # nền xanh đậm
|
|
191
|
+
label[1].transparency = 50 # → xanh nhạt ngang nền cửa sổ
|
|
192
|
+
label[1].transparency = 100 # → nhìn như label không có nền
|
|
193
|
+
set_bg("#fff8e1") # đổi nền cửa sổ — mọi widget
|
|
194
|
+
# trong suốt tự PHA LẠI theo nền mới
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Trong **Studio**: chọn thành phần → bảng phải → hàng **“Trong suốt”** có
|
|
198
|
+
thanh trượt 0–100% (bước 5). Canvas hiển thị đúng hiệu ứng + huy hiệu `%`
|
|
199
|
+
góc trên để nhận biết, lưu vào `myapp.flexgui.json` và cả file xuất `.py`.
|
|
200
|
+
|
|
182
201
|
Widget bị xóa thì `input[3]` trả **widget rỗng an toàn** — code cũ không
|
|
183
202
|
bao giờ văng lỗi. ID **tái sử dụng**: xóa `input[3]` rồi thêm widget mới
|
|
184
203
|
thì widget mới nhận lại đúng ID `[3]`.
|
|
@@ -187,7 +206,7 @@ thì widget mới nhận lại đúng ID `[3]`.
|
|
|
187
206
|
|
|
188
207
|
## Đóng gói & chia sẻ
|
|
189
208
|
|
|
190
|
-
- **Build .exe** (nút
|
|
209
|
+
- **Build .exe** (nút ❖ trong Studio): PyInstaller tự động — file nằm
|
|
191
210
|
trong `flexgui_build/dist/`.
|
|
192
211
|
- **Xuất .py** (nút ▲): 1 file duy nhất nhúng thiết kế + trigger — chạy
|
|
193
212
|
bằng `python ten_file.py` (cần `pip install flexgui`).
|
|
@@ -213,6 +232,21 @@ flexgui/
|
|
|
213
232
|
- Python 3.8+ (chỉ dùng thư viện chuẩn — **không cần cài thêm gì**)
|
|
214
233
|
- Build `.exe` cần `pip install pyinstaller` (Studio tự cài nếu thiếu)
|
|
215
234
|
|
|
235
|
+
## Lịch sử phiên bản
|
|
236
|
+
|
|
237
|
+
- **1.0.2** — **Sửa lỗi "Xem trước" (F5) mở lại Studio**: khi file người
|
|
238
|
+
dùng còn `flexgui.setup()` (hoặc `design.mode = True`, hoặc chạy với cờ
|
|
239
|
+
`--design`), bấm "Xem trước" trước đây làm cửa sổ preview tự hủy rồi mở
|
|
240
|
+
thêm một Studio mới. Giờ cửa sổ xem trước **luôn chạy app thật + trigger
|
|
241
|
+
thật**, không cần sửa file trước khi xem trước.
|
|
242
|
+
- **1.0.1** — Sửa nút toolbar bị ẩn/ô trống (lỗi không truyền chữ vào nút;
|
|
243
|
+
thay ký hiệu bằng bộ glyph an toàn đa nền tảng — không còn ô vuông
|
|
244
|
+
trống trên Linux); **thêm độ trong suốt `transparency` 0–100%** cho cả
|
|
245
|
+
5 widget (thanh trượt trong bảng thuộc tính + pha màu tự động khi đổi
|
|
246
|
+
nền cửa sổ); tương thích Tk 8.6 & 9.x.
|
|
247
|
+
- **1.0.0** — Bản đầu: package PyPI, `flexgui.setup()` / `design.mode`,
|
|
248
|
+
Studio canvas Figma-style, trigger sửa 2 đầu Studio + VS Code.
|
|
249
|
+
|
|
216
250
|
## Đăng package lên PyPI (cho người phát hành)
|
|
217
251
|
|
|
218
252
|
```bash
|
|
@@ -49,9 +49,9 @@ thiết kế đã lưu). Tính năng:
|
|
|
49
49
|
- **Multi-select**: Ctrl/Shift + click, quét vùng chuột, kéo cả cụm
|
|
50
50
|
- Đổi z-order (menu chuột phải), nhân bản `Ctrl+D`, undo/redo `Ctrl+Z/Y`
|
|
51
51
|
- Bảng thuộc tính bên phải: chữ, tọa độ, kích thước **pixel WYSIWYG**,
|
|
52
|
-
màu chữ/nền, cỡ chữ, ẩn khi chạy, khoá...
|
|
52
|
+
màu chữ/nền, cỡ chữ, **độ trong suốt 0–100%**, ẩn khi chạy, khoá...
|
|
53
53
|
- **Xem trước F5** — chạy thử app thật + trigger thật ngay tại chỗ
|
|
54
|
-
- **Build .exe** bằng PyInstaller (nút
|
|
54
|
+
- **Build .exe** bằng PyInstaller (nút ❖ — tự cài nếu thiếu)
|
|
55
55
|
- **Xuất .py** — 1 file tự chạy nhúng toàn bộ thiết kế + trigger
|
|
56
56
|
|
|
57
57
|
`Ctrl+S` lưu 2 file cạnh `myapp.py`:
|
|
@@ -64,7 +64,7 @@ thiết kế đã lưu). Tính năng:
|
|
|
64
64
|
## 3. Trigger — sửa ở Studio HOẶC VS Code đều được
|
|
65
65
|
|
|
66
66
|
`myapp.triggers.py` là file Python thật: mở bằng **mini VS Code** trong
|
|
67
|
-
Studio (nút
|
|
67
|
+
Studio (nút `</> Triggers`) **hoặc** VS Code ngoài — cả hai sửa cùng một file.
|
|
68
68
|
|
|
69
69
|
```python
|
|
70
70
|
# myapp.triggers.py — label/button/... đã có sẵn, không cần import
|
|
@@ -130,8 +130,9 @@ label[1].set_text("...") # tương đương
|
|
|
130
130
|
label[1].get_text()
|
|
131
131
|
label[1].x = 200 # tọa độ pixel (đọc/ghi)
|
|
132
132
|
label[1].color = "red" # màu chữ
|
|
133
|
-
label[1].bg = "#FFF3B0" # màu nền
|
|
133
|
+
label[1].bg = "#FFF3B0" # màu nền GỐC
|
|
134
134
|
label[1].font_size = 14
|
|
135
|
+
label[1].transparency = 70 # ĐỘ TRONG SUỐT nền 0–100%
|
|
135
136
|
label[1].visible = False # tàng hình / hiện lại
|
|
136
137
|
label[1].enabled = False # khoá / mở
|
|
137
138
|
label[1].place(120, 80) # đặt lại vị trí tuyệt đối
|
|
@@ -150,6 +151,24 @@ alert("...") · confirm("...?") → True/False · prompt("Tên?") → str/None
|
|
|
150
151
|
close_app()
|
|
151
152
|
```
|
|
152
153
|
|
|
154
|
+
### Độ trong suốt (transparent background)
|
|
155
|
+
|
|
156
|
+
`transparency` chạy từ `0` (nền đậm nguyên gốc) đến `100` (hòa hoàn toàn
|
|
157
|
+
vào màu nền cửa sổ — trông như không có nền). tkinter không có alpha thật
|
|
158
|
+
cho từng widget nên FlexGUI **pha màu nền widget về đúng màu nền cửa sổ**:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
label[1].bg = "#0078d4" # nền xanh đậm
|
|
162
|
+
label[1].transparency = 50 # → xanh nhạt ngang nền cửa sổ
|
|
163
|
+
label[1].transparency = 100 # → nhìn như label không có nền
|
|
164
|
+
set_bg("#fff8e1") # đổi nền cửa sổ — mọi widget
|
|
165
|
+
# trong suốt tự PHA LẠI theo nền mới
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Trong **Studio**: chọn thành phần → bảng phải → hàng **“Trong suốt”** có
|
|
169
|
+
thanh trượt 0–100% (bước 5). Canvas hiển thị đúng hiệu ứng + huy hiệu `%`
|
|
170
|
+
góc trên để nhận biết, lưu vào `myapp.flexgui.json` và cả file xuất `.py`.
|
|
171
|
+
|
|
153
172
|
Widget bị xóa thì `input[3]` trả **widget rỗng an toàn** — code cũ không
|
|
154
173
|
bao giờ văng lỗi. ID **tái sử dụng**: xóa `input[3]` rồi thêm widget mới
|
|
155
174
|
thì widget mới nhận lại đúng ID `[3]`.
|
|
@@ -158,7 +177,7 @@ thì widget mới nhận lại đúng ID `[3]`.
|
|
|
158
177
|
|
|
159
178
|
## Đóng gói & chia sẻ
|
|
160
179
|
|
|
161
|
-
- **Build .exe** (nút
|
|
180
|
+
- **Build .exe** (nút ❖ trong Studio): PyInstaller tự động — file nằm
|
|
162
181
|
trong `flexgui_build/dist/`.
|
|
163
182
|
- **Xuất .py** (nút ▲): 1 file duy nhất nhúng thiết kế + trigger — chạy
|
|
164
183
|
bằng `python ten_file.py` (cần `pip install flexgui`).
|
|
@@ -184,6 +203,21 @@ flexgui/
|
|
|
184
203
|
- Python 3.8+ (chỉ dùng thư viện chuẩn — **không cần cài thêm gì**)
|
|
185
204
|
- Build `.exe` cần `pip install pyinstaller` (Studio tự cài nếu thiếu)
|
|
186
205
|
|
|
206
|
+
## Lịch sử phiên bản
|
|
207
|
+
|
|
208
|
+
- **1.0.2** — **Sửa lỗi "Xem trước" (F5) mở lại Studio**: khi file người
|
|
209
|
+
dùng còn `flexgui.setup()` (hoặc `design.mode = True`, hoặc chạy với cờ
|
|
210
|
+
`--design`), bấm "Xem trước" trước đây làm cửa sổ preview tự hủy rồi mở
|
|
211
|
+
thêm một Studio mới. Giờ cửa sổ xem trước **luôn chạy app thật + trigger
|
|
212
|
+
thật**, không cần sửa file trước khi xem trước.
|
|
213
|
+
- **1.0.1** — Sửa nút toolbar bị ẩn/ô trống (lỗi không truyền chữ vào nút;
|
|
214
|
+
thay ký hiệu bằng bộ glyph an toàn đa nền tảng — không còn ô vuông
|
|
215
|
+
trống trên Linux); **thêm độ trong suốt `transparency` 0–100%** cho cả
|
|
216
|
+
5 widget (thanh trượt trong bảng thuộc tính + pha màu tự động khi đổi
|
|
217
|
+
nền cửa sổ); tương thích Tk 8.6 & 9.x.
|
|
218
|
+
- **1.0.0** — Bản đầu: package PyPI, `flexgui.setup()` / `design.mode`,
|
|
219
|
+
Studio canvas Figma-style, trigger sửa 2 đầu Studio + VS Code.
|
|
220
|
+
|
|
187
221
|
## Đăng package lên PyPI (cho người phát hành)
|
|
188
222
|
|
|
189
223
|
```bash
|
|
@@ -120,7 +120,7 @@ class App:
|
|
|
120
120
|
|
|
121
121
|
# ------------------------------------------------------------ tiện ích
|
|
122
122
|
def bg(self, mau):
|
|
123
|
-
"""Đổi màu nền cửa
|
|
123
|
+
"""Đổi màu nền cửa sổ (đồng bộ mọi widget trong suốt theo)."""
|
|
124
124
|
self._nen = mau
|
|
125
125
|
self.root.config(bg=mau)
|
|
126
126
|
self.khung.config(bg=mau)
|
|
@@ -130,6 +130,8 @@ class App:
|
|
|
130
130
|
w.tkw.config(bg=mau)
|
|
131
131
|
if w.loai == "checkbox":
|
|
132
132
|
w.tkw.config(activebackground=mau)
|
|
133
|
+
if getattr(w, "_do_trong_suot", 0):
|
|
134
|
+
w._ap_trong_suot() # pha lại theo nền mới
|
|
133
135
|
except Exception:
|
|
134
136
|
pass
|
|
135
137
|
|
|
@@ -160,7 +162,15 @@ class App:
|
|
|
160
162
|
widget đang có — thiết kế tiếp bằng kéo thả)
|
|
161
163
|
chay_mainloop=False : chỉ kích hoạt engine, không vào mainloop
|
|
162
164
|
(dùng cho bản XEM TRƯỚC của Studio).
|
|
165
|
+
|
|
166
|
+
FIX 1.0.2: bản XEM TRƯỚC (Toplevel do Studio mở, goc=...) KHÔNG
|
|
167
|
+
BAO GIỜ chuyển sang Studio — kể cả khi file người dùng còn
|
|
168
|
+
flexgui.setup() / design.mode = True / có cờ --design. Trước đây
|
|
169
|
+
đúng case này bấm "Xem trước" làm cửa sổ preview tự hủy rồi mở
|
|
170
|
+
lại một Studio mới (lỗi user báo).
|
|
163
171
|
"""
|
|
172
|
+
if self.xem_truoc:
|
|
173
|
+
design_mode = False # cửa sổ xem trước = LUÔN chạy thật
|
|
164
174
|
if design_mode is None:
|
|
165
175
|
design_mode = design.mode or _doc_co_design()
|
|
166
176
|
if design_mode:
|
|
@@ -79,7 +79,7 @@ class TrinhDongGoi:
|
|
|
79
79
|
cs.transient(self.goc)
|
|
80
80
|
self.cua_so = cs
|
|
81
81
|
|
|
82
|
-
tk.Label(cs, text="
|
|
82
|
+
tk.Label(cs, text="PyInstaller đang làm việc — có thể mất 1–3 phút "
|
|
83
83
|
"lần đầu (nếu phải cài đặt).",
|
|
84
84
|
bg=MAU["nen_panel"], fg=MAU["chu"], anchor="w",
|
|
85
85
|
font=font_ui(10)).pack(fill="x", padx=14, pady=(12, 6))
|
|
@@ -95,7 +95,7 @@ class TrinhDongGoi:
|
|
|
95
95
|
|
|
96
96
|
duoi = tk.Frame(cs, bg=MAU["nen_panel"])
|
|
97
97
|
duoi.pack(fill="x", padx=14, pady=(0, 12))
|
|
98
|
-
tk.Button(duoi, text="
|
|
98
|
+
tk.Button(duoi, text="» Mở thư mục dist", state="disabled",
|
|
99
99
|
bg=MAU["nen_nut"], fg=MAU["chu_mo"], bd=0,
|
|
100
100
|
padx=12, pady=4, font=font_ui(9), cursor="hand2",
|
|
101
101
|
command=self._mo_thu_muc).pack(side="right")
|
|
@@ -5,13 +5,13 @@ FlexGUI Studio — trình thiết kế trực quan, đồ họa nền tối hi
|
|
|
5
5
|
Bố cục (kiểu Figma/VS Code):
|
|
6
6
|
|
|
7
7
|
┌──────────────────────────────────────────────────────────────┐
|
|
8
|
-
│
|
|
9
|
-
│
|
|
8
|
+
│ ◆ FlexGUI Studio · myapp [Mới][Mở…][Lưu][«][»][≡ Lưới 10px] │
|
|
9
|
+
│ [?][→ Xem trước][❖ Build][▲ Xuất][</>] │
|
|
10
10
|
├──────┬───────────────────────────────────────────┬────────────┤
|
|
11
11
|
│ │ ╔════════════════════════════════╗ │ CỬA SỔ / │
|
|
12
|
-
│ PAL │ ║ ⬜ myapp ─
|
|
12
|
+
│ PAL │ ║ ⬜ myapp ─ ❐ ✕ ║ │ THÀNH PHẦN │
|
|
13
13
|
│ ETTE │ ║────────────────────────────────║ │ (thuộc tính)│
|
|
14
|
-
│ TE │ ║ vùng thiết kế (kéo thả) ║ │
|
|
14
|
+
│ TE │ ║ vùng thiết kế (kéo thả) ║ │ + Trong suốt│
|
|
15
15
|
│ │ ╚════════════════════════════════╝ │ [Trigger] │
|
|
16
16
|
├──────┴───────────────────────────────────────────┴────────────┤
|
|
17
17
|
│ ● Sẵn sàng · X: 12 Y: 34 · Chọn: Button #2 · Lưới 10px │
|
|
@@ -35,7 +35,8 @@ import tkinter.font as tkfont
|
|
|
35
35
|
from tkinter import ttk, messagebox, filedialog, colorchooser, simpledialog
|
|
36
36
|
|
|
37
37
|
from . import registry as _reg
|
|
38
|
-
from .theme import MAU, MAU_APP, font_ui, font_mono, vien_bo, mau_luoi
|
|
38
|
+
from .theme import (MAU, MAU_APP, font_ui, font_mono, vien_bo, mau_luoi,
|
|
39
|
+
tron_mau)
|
|
39
40
|
from .editor import TrinhSoanThao
|
|
40
41
|
from .widgets import _tao_tu_du_lieu
|
|
41
42
|
from .app import App
|
|
@@ -109,6 +110,11 @@ MUC_CHEN = [ # snippet cho drawer trigger
|
|
|
109
110
|
" set_title(\"Tiêu đề mới\")\n"
|
|
110
111
|
" set_bg(\"#fff8e1\")\n"
|
|
111
112
|
" refresh()\n"),
|
|
113
|
+
("Độ trong suốt nền",
|
|
114
|
+
"def on_button_1_click():\n"
|
|
115
|
+
" # 0 = đậm · 50 = mờ nửa vời · 100 = hòa hẳn vào nền\n"
|
|
116
|
+
" label[1].transparency = 70\n"
|
|
117
|
+
" button[1].transparency = 40\n"),
|
|
112
118
|
]
|
|
113
119
|
|
|
114
120
|
|
|
@@ -116,7 +122,12 @@ MUC_CHEN = [ # snippet cho drawer trigger
|
|
|
116
122
|
# Nút phẳng (toolbar) — hover, kiểu accent, kiểu toggle
|
|
117
123
|
# ==============================================================================
|
|
118
124
|
class NutPhang(tk.Label):
|
|
119
|
-
"""Nút phẳng kiểu VS Code — Label + hiệu ứng hover + click.
|
|
125
|
+
"""Nút phẳng kiểu VS Code — Label + hiệu ứng hover + click.
|
|
126
|
+
|
|
127
|
+
LƯU Ý (sửa lỗi quan trọng): chữ PHẢI được truyền qua `text=` cho
|
|
128
|
+
tk.Label — bản trước quên dòng này nên MỌI nút toolbar hiện ô trống.
|
|
129
|
+
Chiều rộng tự co theo chữ (không đặt width cứng để tránh cắt chữ).
|
|
130
|
+
"""
|
|
120
131
|
|
|
121
132
|
def __init__(self, cha, chu, lenh=None, accent=False, toggle=False,
|
|
122
133
|
rong=None, co=9, dam=False):
|
|
@@ -129,10 +140,14 @@ class NutPhang(tk.Label):
|
|
|
129
140
|
except Exception:
|
|
130
141
|
nen_cha = MAU["nen"]
|
|
131
142
|
kw = dict(
|
|
143
|
+
text=chu, # ← LỖI CŨ: thiếu dòng này!
|
|
132
144
|
bg=MAU["gt"] if accent else nen_cha,
|
|
133
145
|
fg=MAU["chu_sang"] if accent else MAU["chu"],
|
|
134
|
-
font=font_ui(co, dam), padx=12, pady=6, cursor="hand2"
|
|
135
|
-
|
|
146
|
+
font=font_ui(co, dam), padx=12, pady=6, cursor="hand2",
|
|
147
|
+
anchor="center")
|
|
148
|
+
# rong chỉ là GỢI Ý chiều rộng tối thiểu (theo ký tự) — không bao giờ
|
|
149
|
+
# đặt hẹp hơn chuỗi hiện có để chữ không bị cắt cụt
|
|
150
|
+
if rong and len(chu) < rong:
|
|
136
151
|
kw["width"] = rong
|
|
137
152
|
super().__init__(cha, **kw)
|
|
138
153
|
self.bind("<Enter>", self._hover)
|
|
@@ -256,7 +271,9 @@ class Studio:
|
|
|
256
271
|
bar = tk.Frame(self.root, bg=MAU["nen"], pady=6, padx=8)
|
|
257
272
|
bar.pack(fill="x", side="top")
|
|
258
273
|
|
|
259
|
-
|
|
274
|
+
# Logo + tên file (chỉ dùng ký tự CÓ glyph trên mọi nền tảng —
|
|
275
|
+
# emoji/ký hiệu lạ từng làm nút hiện ô trống trên Linux)
|
|
276
|
+
tk.Label(bar, text="◆", fg=MAU["gt"], bg=MAU["nen"],
|
|
260
277
|
font=font_ui(15)).pack(side="left")
|
|
261
278
|
tk.Label(bar, text="FlexGUI Studio", fg=MAU["chu_sang"],
|
|
262
279
|
bg=MAU["nen"], font=font_ui(10, True)).pack(side="left",
|
|
@@ -268,32 +285,30 @@ class Studio:
|
|
|
268
285
|
|
|
269
286
|
tk.Frame(bar, bg=MAU["vien"], width=1).pack(side="left", fill="y",
|
|
270
287
|
padx=8, pady=2)
|
|
271
|
-
NutPhang(bar, "Mới", self._moi
|
|
272
|
-
NutPhang(bar, "Mở…", self._mo_file
|
|
273
|
-
NutPhang(bar, "Lưu", lambda: self._luu_toan_bo(True),
|
|
288
|
+
NutPhang(bar, "Mới", self._moi).pack(side="left", padx=1)
|
|
289
|
+
NutPhang(bar, "Mở…", self._mo_file).pack(side="left", padx=1)
|
|
290
|
+
NutPhang(bar, "Lưu", lambda: self._luu_toan_bo(True),
|
|
274
291
|
dam=True).pack(side="left", padx=1)
|
|
275
|
-
NutPhang(bar, "
|
|
276
|
-
NutPhang(bar, "
|
|
292
|
+
NutPhang(bar, "«", self._hoan_tac, rong=2).pack(side="left", padx=1)
|
|
293
|
+
NutPhang(bar, "»", self._lam_lai, rong=2).pack(side="left", padx=1)
|
|
277
294
|
|
|
278
295
|
tk.Frame(bar, bg=MAU["vien"], width=1).pack(side="left", fill="y",
|
|
279
296
|
padx=8, pady=2)
|
|
280
|
-
self.nut_luoi = NutPhang(bar, "
|
|
281
|
-
|
|
282
|
-
self.nut_luoi.dat_lenh(self._doi_luoi)
|
|
297
|
+
self.nut_luoi = NutPhang(bar, "≡ Lưới 10px", self._doi_luoi,
|
|
298
|
+
toggle=True)
|
|
283
299
|
self.nut_luoi.pack(side="left", padx=1)
|
|
284
300
|
self.nut_luoi.dat_bat(True)
|
|
285
301
|
|
|
286
302
|
# ---- bên phải ----
|
|
287
|
-
self.nut_drawer = NutPhang(bar, "
|
|
288
|
-
toggle=True
|
|
303
|
+
self.nut_drawer = NutPhang(bar, "</> Triggers", self._dao_drawer,
|
|
304
|
+
toggle=True)
|
|
289
305
|
self.nut_drawer.pack(side="right", padx=1)
|
|
290
|
-
NutPhang(bar, "▲ Xuất .py", self._xuat_py
|
|
306
|
+
NutPhang(bar, "▲ Xuất .py", self._xuat_py).pack(
|
|
291
307
|
side="right", padx=1)
|
|
292
|
-
self.nut_build = NutPhang(bar, "
|
|
293
|
-
rong=9)
|
|
308
|
+
self.nut_build = NutPhang(bar, "❖ Build .exe", self._build_exe)
|
|
294
309
|
self.nut_build.pack(side="right", padx=1)
|
|
295
|
-
NutPhang(bar, "
|
|
296
|
-
|
|
310
|
+
NutPhang(bar, "→ Xem trước", self._xem_truoc, accent=True).pack(
|
|
311
|
+
side="right", padx=(1, 8))
|
|
297
312
|
NutPhang(bar, "?", self._mo_tro_giup, rong=2).pack(side="right",
|
|
298
313
|
padx=1)
|
|
299
314
|
|
|
@@ -487,16 +502,16 @@ class Studio:
|
|
|
487
502
|
dau = tk.Frame(self.drawer, bg="#2d2d2d", padx=8, pady=3)
|
|
488
503
|
dau.pack(fill="x")
|
|
489
504
|
self.nhan_tab = tk.Label(
|
|
490
|
-
dau, text="
|
|
505
|
+
dau, text=" </> " + self._ten_triggers() + " • Python • UTF-8",
|
|
491
506
|
bg="#2d2d2d", fg=MAU["chu_sang"], font=font_ui(8),
|
|
492
507
|
anchor="w")
|
|
493
508
|
self.nhan_tab.pack(side="left")
|
|
494
509
|
NutPhang(dau, "✕", self._an_drawer, rong=2, co=8).pack(
|
|
495
510
|
side="right")
|
|
496
|
-
NutPhang(dau, "Chèn mẫu
|
|
511
|
+
NutPhang(dau, "Chèn mẫu ↓", self._chen_mau, co=8).pack(
|
|
497
512
|
side="right", padx=4)
|
|
498
|
-
NutPhang(dau, "
|
|
499
|
-
|
|
513
|
+
NutPhang(dau, "✓ Áp dụng & Lưu", lambda: self._ghi_triggers(True),
|
|
514
|
+
co=8).pack(side="right", padx=4)
|
|
500
515
|
|
|
501
516
|
self.editor = TrinhSoanThao(self.drawer)
|
|
502
517
|
self.editor.pack(fill="both", expand=True, padx=8, pady=(0, 8))
|
|
@@ -612,7 +627,7 @@ class Studio:
|
|
|
612
627
|
self.redo_ds.clear()
|
|
613
628
|
self.dirty = False
|
|
614
629
|
self.nhan_ten_file.config(text="· " + self.duong_json.name)
|
|
615
|
-
self.nhan_tab.config(text="
|
|
630
|
+
self.nhan_tab.config(text=" </> " + self._ten_triggers()
|
|
616
631
|
+ " • Python • UTF-8")
|
|
617
632
|
self._doc_triggers()
|
|
618
633
|
self._ve_lai()
|
|
@@ -683,13 +698,13 @@ class Studio:
|
|
|
683
698
|
fill="#e3e6ea", tags="cua_so")
|
|
684
699
|
vien_bo(cv, x0 + 12, y0 + 9, x0 + 24, y0 + 21, 4,
|
|
685
700
|
fill=MAU_APP["gt_mac_dinh"], outline="", tags="cua_so")
|
|
686
|
-
cv.create_text(x0 + 18, y0 + 15, text="
|
|
701
|
+
cv.create_text(x0 + 18, y0 + 15, text="◆", fill="#ffffff",
|
|
687
702
|
font=font_ui(8, True), tags="cua_so")
|
|
688
703
|
cv.create_text(x0 + 34, y0 + H_TIEU_DE / 2, text=tieu_de,
|
|
689
704
|
anchor="w", fill="#3b4048", font=font_ui(9, True),
|
|
690
705
|
tags="cua_so")
|
|
691
706
|
# nút cửa sổ (Windows style)
|
|
692
|
-
for i, chu in enumerate(("—", "
|
|
707
|
+
for i, chu in enumerate(("—", "❐", "✕")):
|
|
693
708
|
nx = x1 - 96 + i * 32
|
|
694
709
|
cv.create_rectangle(nx, y0, nx + 32, y0 + H_TIEU_DE,
|
|
695
710
|
fill=MAU["thanh_tieu_de"], outline="",
|
|
@@ -732,7 +747,7 @@ class Studio:
|
|
|
732
747
|
tags="goi_y")
|
|
733
748
|
|
|
734
749
|
def _doi_luoi(self):
|
|
735
|
-
"""Bật/tắt lưới + snap (nút
|
|
750
|
+
"""Bật/tắt lưới + snap (nút ≡ trên toolbar)."""
|
|
736
751
|
self.luoi_bat = not self.luoi_bat
|
|
737
752
|
self.nut_luoi.dat_bat(self.luoi_bat)
|
|
738
753
|
if self.luoi_bat:
|
|
@@ -743,7 +758,11 @@ class Studio:
|
|
|
743
758
|
"● Tắt lưới — kéo thả hoàn toàn tự do")
|
|
744
759
|
|
|
745
760
|
def _ve_comp(self, d):
|
|
746
|
-
"""Vẽ MỘT thành phần theo đúng loại (đồ họa mô phỏng hiện đại).
|
|
761
|
+
"""Vẽ MỘT thành phần theo đúng loại (đồ họa mô phỏng hiện đại).
|
|
762
|
+
|
|
763
|
+
Độ trong suốt được mô phỏng đúng như app thật: PHA màu nền của
|
|
764
|
+
widget về màu nền cửa sổ (transparency 0–100%).
|
|
765
|
+
"""
|
|
747
766
|
cv = self.ban_ve
|
|
748
767
|
X, Y = self._sang_canvas(d["x"], d["y"])
|
|
749
768
|
w, h = d.get("w") or 100, d.get("h") or 30
|
|
@@ -752,6 +771,14 @@ class Studio:
|
|
|
752
771
|
MAU_APP["nen_mac_dinh"]
|
|
753
772
|
co = d.get("font_size") or 10
|
|
754
773
|
mau_chu = d.get("color") or "#1f2430"
|
|
774
|
+
# độ trong suốt 0..100 → hàm pha màu mock y hệt runtime
|
|
775
|
+
do_ts = min(100, max(0, int(d.get("transparency") or 0)))
|
|
776
|
+
|
|
777
|
+
def pha(mau_nen_goc):
|
|
778
|
+
"""Pha màu nền widget về màu cửa sổ theo độ trong suốt."""
|
|
779
|
+
if do_ts <= 0 or not mau_nen_goc:
|
|
780
|
+
return mau_nen_goc
|
|
781
|
+
return tron_mau(mau_nen_goc, nen_cua_so, do_ts / 100.0)
|
|
755
782
|
|
|
756
783
|
# hit-rect trong suốt (đón chuột) — luôn nằm dưới cùng của comp
|
|
757
784
|
cv.create_rectangle(X, Y, X + w, Y + h, fill=nen_cua_so,
|
|
@@ -762,7 +789,7 @@ class Studio:
|
|
|
762
789
|
fill=mau_chu, font=font_ui(co), tags=tag)
|
|
763
790
|
|
|
764
791
|
elif d["loai"] == "button":
|
|
765
|
-
mau_nen = d.get("bg") or MAU_APP["gt_mac_dinh"]
|
|
792
|
+
mau_nen = pha(d.get("bg") or MAU_APP["gt_mac_dinh"])
|
|
766
793
|
vien_bo(cv, X, Y, X + w, Y + h, 8, fill=mau_nen, outline="",
|
|
767
794
|
tags=tag)
|
|
768
795
|
cv.create_text(X + w / 2, Y + h / 2, text=d.get("text") or "",
|
|
@@ -770,8 +797,11 @@ class Studio:
|
|
|
770
797
|
font=font_ui(co), tags=tag)
|
|
771
798
|
|
|
772
799
|
elif d["loai"] == "input":
|
|
773
|
-
|
|
774
|
-
|
|
800
|
+
nen_oi = pha(d.get("bg") or "#ffffff")
|
|
801
|
+
vien_bo(cv, X, Y, X + w, Y + h, 6, fill=nen_oi,
|
|
802
|
+
outline=nen_oi if do_ts else (d.get("bg")
|
|
803
|
+
or MAU_APP["khung"]),
|
|
804
|
+
tags=tag)
|
|
775
805
|
chu = (d.get("text") or "").strip()
|
|
776
806
|
cv.create_text(X + 8, Y + h / 2, anchor="w",
|
|
777
807
|
text=chu or "Ô nhập…",
|
|
@@ -781,12 +811,13 @@ class Studio:
|
|
|
781
811
|
elif d["loai"] == "checkbox":
|
|
782
812
|
c = 16
|
|
783
813
|
cy = Y + (h - c) / 2
|
|
784
|
-
cv.create_rectangle(X, cy, X + c, cy + c,
|
|
785
|
-
outline="#8a919c",
|
|
814
|
+
cv.create_rectangle(X, cy, X + c, cy + c,
|
|
815
|
+
fill=pha("#ffffff"), outline="#8a919c",
|
|
816
|
+
tags=tag)
|
|
786
817
|
if d.get("checked"):
|
|
787
818
|
cv.create_rectangle(X + 2, cy + 2, X + c - 2, cy + c - 2,
|
|
788
|
-
fill=MAU_APP["gt_mac_dinh"],
|
|
789
|
-
tags=tag)
|
|
819
|
+
fill=pha(MAU_APP["gt_mac_dinh"]),
|
|
820
|
+
outline="", tags=tag)
|
|
790
821
|
cv.create_line(X + 4, cy + 8, X + 7, cy + 11, X + 12,
|
|
791
822
|
cy + 4, fill="#ffffff", width=2,
|
|
792
823
|
capstyle="round", joinstyle="round",
|
|
@@ -797,11 +828,11 @@ class Studio:
|
|
|
797
828
|
|
|
798
829
|
elif d["loai"] == "text":
|
|
799
830
|
cv.create_rectangle(X, Y, X + w, Y + h,
|
|
800
|
-
fill=d.get("bg") or "#ffffff",
|
|
831
|
+
fill=pha(d.get("bg") or "#ffffff"),
|
|
801
832
|
outline=MAU_APP["khung"], tags=tag)
|
|
802
833
|
# thanh cuộn giả bên phải
|
|
803
834
|
cv.create_rectangle(X + w - 8, Y + 3, X + w - 3, Y + h - 3,
|
|
804
|
-
fill="#eef1f5", outline="", tags=tag)
|
|
835
|
+
fill=pha("#eef1f5"), outline="", tags=tag)
|
|
805
836
|
noi_dung = (d.get("text") or "").strip()
|
|
806
837
|
if noi_dung:
|
|
807
838
|
for i, dong in enumerate(noi_dung.split("\n")[:3]):
|
|
@@ -813,7 +844,15 @@ class Studio:
|
|
|
813
844
|
cv.create_rectangle(X + 8, Y + 10 + i * 16,
|
|
814
845
|
X + 8 + (w - 34 if i < 2 else 20),
|
|
815
846
|
Y + 14 + i * 16,
|
|
816
|
-
fill="#e4e8ee", outline="",
|
|
847
|
+
fill=pha("#e4e8ee"), outline="",
|
|
848
|
+
tags=tag)
|
|
849
|
+
|
|
850
|
+
# huy hiệu độ trong suốt (góc trái trên — không đụng huy hiệu ẨN)
|
|
851
|
+
if do_ts > 0:
|
|
852
|
+
vien_bo(cv, X + 4, Y + 4, X + 48, Y + 20, 5,
|
|
853
|
+
fill="#0e7490", outline="", tags=tag)
|
|
854
|
+
cv.create_text(X + 26, Y + 12, text=f"{do_ts}%",
|
|
855
|
+
fill="#ffffff", font=font_ui(7, True), tags=tag)
|
|
817
856
|
|
|
818
857
|
# huy hiệu trạng thái đặc biệt
|
|
819
858
|
if not d.get("visible", True):
|
|
@@ -1176,6 +1215,7 @@ class Studio:
|
|
|
1176
1215
|
"x": self._snap(x), "y": self._snap(y),
|
|
1177
1216
|
"w": w or 100, "h": h or 26, "text": chu,
|
|
1178
1217
|
"color": None, "bg": None, "font_size": None,
|
|
1218
|
+
"transparency": 0,
|
|
1179
1219
|
"visible": True, "enabled": True,
|
|
1180
1220
|
"checked": False if loai == "checkbox" else None}
|
|
1181
1221
|
self._danh_sach().append(d)
|
|
@@ -1483,6 +1523,8 @@ class Studio:
|
|
|
1483
1523
|
self._hang("Màu chữ", self._o_mau(chinh.get("color"),
|
|
1484
1524
|
_dat("color")))
|
|
1485
1525
|
self._hang("Màu nền", self._o_mau(chinh.get("bg"), _dat("bg")))
|
|
1526
|
+
self._o_trong_suot(chinh.get("transparency", 0) or 0,
|
|
1527
|
+
_dat("transparency", int))
|
|
1486
1528
|
self._hang("Cỡ chữ", self._o_nhap(chinh.get("font_size") or 10,
|
|
1487
1529
|
_dat("font_size", int),
|
|
1488
1530
|
chi_so=True))
|
|
@@ -1504,11 +1546,11 @@ class Studio:
|
|
|
1504
1546
|
# nút tiện dụng
|
|
1505
1547
|
duoi = tk.Frame(self.noi_bang, bg=MAU["nen_panel"])
|
|
1506
1548
|
duoi.pack(fill="x", pady=(10, 0))
|
|
1507
|
-
NutPhang(duoi, "
|
|
1549
|
+
NutPhang(duoi, "→ Sửa trigger", lambda: self._sua_trigger(),
|
|
1508
1550
|
co=8).pack(side="left")
|
|
1509
|
-
NutPhang(duoi, "
|
|
1551
|
+
NutPhang(duoi, "❐ Nhân bản", self._nhan_ban, co=8).pack(
|
|
1510
1552
|
side="left", padx=4)
|
|
1511
|
-
NutPhang(duoi, "
|
|
1553
|
+
NutPhang(duoi, "✕ Xóa", self._xoa_dang_chon, co=8).pack(
|
|
1512
1554
|
side="left", padx=4)
|
|
1513
1555
|
|
|
1514
1556
|
def _o_kiem(self, nhan, gia_tri, lenh):
|
|
@@ -1523,6 +1565,46 @@ class Studio:
|
|
|
1523
1565
|
anchor="w", command=lenh)
|
|
1524
1566
|
ck.pack(side="left")
|
|
1525
1567
|
|
|
1568
|
+
def _o_trong_suot(self, gia_tri, lenh):
|
|
1569
|
+
"""Hàng ĐỘ TRONG SUỐT: thanh trượt 0–100% + nhãn % + nút nhanh.
|
|
1570
|
+
|
|
1571
|
+
tkinter không có alpha thật cho từng widget nên FlexGUI PHA màu
|
|
1572
|
+
nền widget về màu nền cửa sổ (100% = hòa vào nền nhìn như
|
|
1573
|
+
transparent background thật).
|
|
1574
|
+
"""
|
|
1575
|
+
f = tk.Frame(self.noi_bang, bg=MAU["nen_panel"])
|
|
1576
|
+
f.pack(fill="x", pady=2)
|
|
1577
|
+
tk.Label(f, text="Trong suốt", fg=MAU["chu"], bg=MAU["nen_panel"],
|
|
1578
|
+
font=font_ui(8), width=9, anchor="w").pack(side="left")
|
|
1579
|
+
|
|
1580
|
+
nhan_phan_tram = tk.Label(f, text=f"{int(gia_tri)}%",
|
|
1581
|
+
fg=MAU["gt"], bg=MAU["nen_panel"],
|
|
1582
|
+
font=font_ui(8, True), width=4,
|
|
1583
|
+
anchor="e")
|
|
1584
|
+
|
|
1585
|
+
def _cap_nhat(v):
|
|
1586
|
+
try:
|
|
1587
|
+
v = int(float(v))
|
|
1588
|
+
except (TypeError, ValueError):
|
|
1589
|
+
return
|
|
1590
|
+
nhan_phan_tram.config(text=f"{v}%")
|
|
1591
|
+
lenh(v)
|
|
1592
|
+
|
|
1593
|
+
sc = tk.Scale(
|
|
1594
|
+
f, from_=0, to=100, resolution=5, orient="horizontal",
|
|
1595
|
+
showvalue=False, bg=MAU["nen_panel"], fg=MAU["chu"],
|
|
1596
|
+
troughcolor="#3c3c3c", highlightthickness=0, bd=0,
|
|
1597
|
+
activebackground=MAU["gt"], sliderrelief="flat",
|
|
1598
|
+
length=118, width=11, command=_cap_nhat)
|
|
1599
|
+
# Tk 9 không kích hoạt -command khi set() lập trình — thêm binding
|
|
1600
|
+
# nhả chuột để giá trị luôn được chốt trên MỌI bản Tk (8.6 & 9.x)
|
|
1601
|
+
sc.bind("<ButtonRelease-1>",
|
|
1602
|
+
lambda _e: _cap_nhat(sc.get()))
|
|
1603
|
+
sc.set(int(gia_tri))
|
|
1604
|
+
sc.pack(side="left", fill="x", expand=True)
|
|
1605
|
+
nhan_phan_tram.pack(side="left", padx=(4, 0))
|
|
1606
|
+
return f
|
|
1607
|
+
|
|
1526
1608
|
def _chup_ve_cuoi_hang(self):
|
|
1527
1609
|
"""Hẹn đẩy undo 700ms sau lần sửa cuối (gộp thao tác liên tục)."""
|
|
1528
1610
|
if getattr(self, "_hen_undo_cuoi", None):
|
|
@@ -1682,7 +1764,7 @@ class Studio:
|
|
|
1682
1764
|
self.duong_triggers = self.duong_json.parent / (
|
|
1683
1765
|
self.duong_json.name[:-len(".flexgui.json")] + ".triggers.py")
|
|
1684
1766
|
self.nhan_ten_file.config(text="· " + self.duong_json.name)
|
|
1685
|
-
self.nhan_tab.config(text="
|
|
1767
|
+
self.nhan_tab.config(text=" </> " + self._ten_triggers()
|
|
1686
1768
|
+ " • Python • UTF-8")
|
|
1687
1769
|
return True
|
|
1688
1770
|
|
|
@@ -1730,7 +1812,10 @@ class Studio:
|
|
|
1730
1812
|
"Trigger có lỗi (vẫn xem trước được)",
|
|
1731
1813
|
"Một số đoạn trigger không chạy được:\n\n" + chi_tiet,
|
|
1732
1814
|
parent=self.root)
|
|
1733
|
-
|
|
1815
|
+
# design_mode=False: file người dùng có thể còn flexgui.setup()
|
|
1816
|
+
# (design.mode=True) — xem trước PHẢI chạy app thật, không được
|
|
1817
|
+
# mở lại Studio (fix 1.0.2,Guard nữa nằm trong App.run).
|
|
1818
|
+
app.run(chay_mainloop=False, design_mode=False)
|
|
1734
1819
|
self._cap_nhat_trang_thai("● Đang xem trước…")
|
|
1735
1820
|
except Exception:
|
|
1736
1821
|
_reg._dat_app(None)
|
|
@@ -1868,6 +1953,12 @@ class Studio:
|
|
|
1868
1953
|
("Mũi tên (+Shift)", "Dịch 1px (Shift = 10px, tự snap lưới)"),
|
|
1869
1954
|
("Escape", "Bỏ chọn hết"),
|
|
1870
1955
|
("", ""),
|
|
1956
|
+
("THUỘC TÍNH ĐẸP", ""),
|
|
1957
|
+
("Trong suốt", "Bảng phải → thanh trượt 0–100% — nền widget "
|
|
1958
|
+
"pha dần về màu nền cửa sổ (giả transparent background)"),
|
|
1959
|
+
("transparency", "Dùng trong code: label[1].transparency = 70 "
|
|
1960
|
+
"· button[2].transparency = 100"),
|
|
1961
|
+
("", ""),
|
|
1871
1962
|
("TƯƠNG TÁC", ""),
|
|
1872
1963
|
("Kéo", "Di chuyển — giữ Ctrl/Shift + click để chọn nhiều, "
|
|
1873
1964
|
"quét vùng chuột trên nền trống"),
|
|
@@ -112,6 +112,14 @@ class _WidgetDaXoa:
|
|
|
112
112
|
def enabled(self, gia_tri):
|
|
113
113
|
pass
|
|
114
114
|
|
|
115
|
+
@property
|
|
116
|
+
def transparency(self):
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
@transparency.setter
|
|
120
|
+
def transparency(self, ty_le):
|
|
121
|
+
pass
|
|
122
|
+
|
|
115
123
|
def __bool__(self):
|
|
116
124
|
return False
|
|
117
125
|
|
|
@@ -95,8 +95,9 @@ def font_ui(co=9, dam=False):
|
|
|
95
95
|
"""Font cho giao diện (toolbar, panel, widget app). `dam` = in đậm."""
|
|
96
96
|
global _ho_ui_da_chon
|
|
97
97
|
if _ho_ui_da_chon is None:
|
|
98
|
-
_ho_ui_da_chon = _chon_ho(["Segoe UI", "SF Pro Text",
|
|
99
|
-
"DejaVu Sans", "
|
|
98
|
+
_ho_ui_da_chon = _chon_ho(["Segoe UI", "SF Pro Text", "Avenir Next",
|
|
99
|
+
"DejaVu Sans", "Noto Sans", "FreeSans",
|
|
100
|
+
"Helvetica"])
|
|
100
101
|
return (_ho_ui_da_chon, co, "bold" if dam else "normal")
|
|
101
102
|
|
|
102
103
|
|
|
@@ -104,11 +105,44 @@ def font_mono(co=11):
|
|
|
104
105
|
"""Font monospace cho editor code (hỗ trợ tiếng Việt)."""
|
|
105
106
|
global _ho_mono_da_chon
|
|
106
107
|
if _ho_mono_da_chon is None:
|
|
107
|
-
_ho_mono_da_chon = _chon_ho(["Consolas", "Cascadia Mono",
|
|
108
|
-
"DejaVu Sans Mono", "
|
|
108
|
+
_ho_mono_da_chon = _chon_ho(["Consolas", "Cascadia Mono", "Menlo",
|
|
109
|
+
"DejaVu Sans Mono", "Noto Sans Mono",
|
|
110
|
+
"FreeMono", "Courier New"])
|
|
109
111
|
return (_ho_mono_da_chon, co)
|
|
110
112
|
|
|
111
113
|
|
|
114
|
+
def _tach_mau(mau):
|
|
115
|
+
"""'#rrggbb' -> (r, g, b). Trả None nếu không phân tích được."""
|
|
116
|
+
try:
|
|
117
|
+
c = (mau or "").strip().lstrip("#")
|
|
118
|
+
if len(c) == 3:
|
|
119
|
+
c = "".join(ch * 2 for ch in c)
|
|
120
|
+
if len(c) != 6:
|
|
121
|
+
return None
|
|
122
|
+
return int(c[0:2], 16), int(c[2:4], 16), int(c[4:6], 16)
|
|
123
|
+
except Exception:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def tron_mau(mau_a, mau_b, ty_le):
|
|
128
|
+
"""PHA 2 MÀU — nhân tạo độ trong suốt cho nền widget.
|
|
129
|
+
|
|
130
|
+
ty_le = 0.0 -> đúng mau_a (đậm nguyên gốc)
|
|
131
|
+
ty_le = 1.0 -> đúng mau_b (hòa hoàn toàn vào nền cửa sổ)
|
|
132
|
+
|
|
133
|
+
tkinter không hỗ trợ alpha thật cho từng widget nên FlexGUI giả
|
|
134
|
+
độ trong suốt bằng cách PHA màu nền widget về màu nền cửa sổ.
|
|
135
|
+
"""
|
|
136
|
+
a, b = _tach_mau(mau_a), _tach_mau(mau_b)
|
|
137
|
+
if a is None:
|
|
138
|
+
return mau_b if ty_le >= 0.999 else mau_a
|
|
139
|
+
if b is None:
|
|
140
|
+
return mau_a
|
|
141
|
+
t = max(0.0, min(1.0, float(ty_le)))
|
|
142
|
+
return "#%02x%02x%02x" % tuple(
|
|
143
|
+
round(a[i] + (b[i] - a[i]) * t) for i in range(3))
|
|
144
|
+
|
|
145
|
+
|
|
112
146
|
def vien_bo(cv, x1, y1, x2, y2, r=8, **kw):
|
|
113
147
|
"""Vẽ HÌNH CHỮ NHẬT BO GÓC trên canvas (dùng polygon smooth).
|
|
114
148
|
|
|
@@ -20,7 +20,7 @@ import tkinter as tk
|
|
|
20
20
|
import tkinter.font as tkfont
|
|
21
21
|
|
|
22
22
|
from . import registry as _reg
|
|
23
|
-
from .theme import MAU_APP, font_ui
|
|
23
|
+
from .theme import MAU_APP, font_ui, tron_mau
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class GuiWidget:
|
|
@@ -50,9 +50,10 @@ class GuiWidget:
|
|
|
50
50
|
# ---- thuộc tính tùy biến (None = dùng giá trị gốc của tkinter) ----
|
|
51
51
|
self._hien = True # .visible
|
|
52
52
|
self._mau_chu = None # .color (fg)
|
|
53
|
-
self._mau_nen = None # .bg (nền)
|
|
53
|
+
self._mau_nen = None # .bg (nền GỐC trước khi pha)
|
|
54
54
|
self._co_font = None # .font_size
|
|
55
55
|
self._kich_hoat = True # .enabled
|
|
56
|
+
self._do_trong_suot = 0 # .transparency (0..100%)
|
|
56
57
|
self._fg_goc = None
|
|
57
58
|
self._bg_goc = None
|
|
58
59
|
self._font_goc = None
|
|
@@ -257,7 +258,11 @@ class GuiWidget:
|
|
|
257
258
|
|
|
258
259
|
@property
|
|
259
260
|
def bg(self):
|
|
260
|
-
"""Màu NỀN (đọc/ghi). Ví dụ: button[2].bg = "#FFFFCC".
|
|
261
|
+
"""Màu NỀN GỐC (đọc/ghi). Ví dụ: button[2].bg = "#FFFFCC".
|
|
262
|
+
|
|
263
|
+
Khi có .transparency > 0, màu hiển thị thật là màu này pha về
|
|
264
|
+
màu nền cửa sổ (đọc .bg vẫn trả màu gốc).
|
|
265
|
+
"""
|
|
261
266
|
return self._mau_nen
|
|
262
267
|
|
|
263
268
|
@bg.setter
|
|
@@ -265,7 +270,59 @@ class GuiWidget:
|
|
|
265
270
|
self._mau_nen = mau
|
|
266
271
|
if self.tkw is not None:
|
|
267
272
|
self.tkw.config(bg=mau if mau is not None else self._bg_goc)
|
|
268
|
-
self._ap_dung_phu(mau)
|
|
273
|
+
self._ap_dung_phu(mau if mau is not None else self._nen_mac_dinh())
|
|
274
|
+
self._ap_trong_suot()
|
|
275
|
+
|
|
276
|
+
def _nen_mac_dinh(self):
|
|
277
|
+
"""Màu nền mặc định THEO LOẠI widget (khi user chưa đặt .bg)."""
|
|
278
|
+
if self.loai == "button":
|
|
279
|
+
return MAU_APP["gt_mac_dinh"]
|
|
280
|
+
if self.loai in ("input", "text"):
|
|
281
|
+
return MAU_APP["nen_oi"]
|
|
282
|
+
return getattr(self.app, "_nen", None) or MAU_APP["nen_mac_dinh"]
|
|
283
|
+
|
|
284
|
+
def _nen_goc(self):
|
|
285
|
+
"""Nền gốc hiện tại (bg user đặt HOẶC mặc định theo loại)."""
|
|
286
|
+
return self._mau_nen if self._mau_nen is not None else self._nen_mac_dinh()
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def transparency(self):
|
|
290
|
+
"""Độ TRONG SUỐT nền 0..100% (đọc/ghi).
|
|
291
|
+
|
|
292
|
+
tkinter không có alpha thật cho từng widget nên FlexGUI giả bằng
|
|
293
|
+
cách PHA màu nền widget về đúng màu nền cửa sổ:
|
|
294
|
+
0 = nền đậm nguyên gốc
|
|
295
|
+
50 = mờ một nửa
|
|
296
|
+
100 = hòa hoàn toàn vào nền (trông như không có nền)
|
|
297
|
+
|
|
298
|
+
Ví dụ: label[1].transparency = 60 button[2].transparency = 100
|
|
299
|
+
"""
|
|
300
|
+
return self._do_trong_suot
|
|
301
|
+
|
|
302
|
+
@transparency.setter
|
|
303
|
+
def transparency(self, ty_le):
|
|
304
|
+
try:
|
|
305
|
+
self._do_trong_suot = max(0, min(100, int(ty_le or 0)))
|
|
306
|
+
except (TypeError, ValueError):
|
|
307
|
+
self._do_trong_suot = 0
|
|
308
|
+
self._ap_trong_suot()
|
|
309
|
+
|
|
310
|
+
def _ap_trong_suot(self):
|
|
311
|
+
"""Áp nền đã pha (hoặc nền gốc khi transparency = 0)."""
|
|
312
|
+
if self.tkw is None:
|
|
313
|
+
return
|
|
314
|
+
nen_cua_so = getattr(self.app, "_nen", None) \
|
|
315
|
+
or MAU_APP["nen_mac_dinh"]
|
|
316
|
+
if self._do_trong_suot <= 0:
|
|
317
|
+
hien = self._nen_goc()
|
|
318
|
+
else:
|
|
319
|
+
hien = tron_mau(self._nen_goc(), nen_cua_so,
|
|
320
|
+
self._do_trong_suot / 100.0)
|
|
321
|
+
try:
|
|
322
|
+
self.tkw.config(bg=hien)
|
|
323
|
+
self._ap_dung_phu(hien)
|
|
324
|
+
except tk.TclError:
|
|
325
|
+
pass
|
|
269
326
|
|
|
270
327
|
def _ap_dung_phu(self, mau):
|
|
271
328
|
"""Hiệu ứng phụ khi đổi nền (một số loại cần đổi cả activebackground)."""
|
|
@@ -323,7 +380,8 @@ class GuiWidget:
|
|
|
323
380
|
|
|
324
381
|
# ------------------------------------------------------ lệnh điều khiển
|
|
325
382
|
def refresh(self):
|
|
326
|
-
"""Áp LẠI toàn bộ thuộc tính đã lưu (vị trí, cỡ, màu, ẩn-hiện
|
|
383
|
+
"""Áp LẠI toàn bộ thuộc tính đã lưu (vị trí, cỡ, màu, ẩn-hiện,
|
|
384
|
+
độ trong suốt...).
|
|
327
385
|
|
|
328
386
|
Dùng khi nghi giao diện chưa kịp vẽ đúng: label[1].refresh()
|
|
329
387
|
"""
|
|
@@ -331,6 +389,7 @@ class GuiWidget:
|
|
|
331
389
|
self.color = self._mau_chu
|
|
332
390
|
self.bg = self._mau_nen
|
|
333
391
|
self.font_size = self._co_font
|
|
392
|
+
self._ap_trong_suot()
|
|
334
393
|
self._ap_dung_hien_thi()
|
|
335
394
|
return True
|
|
336
395
|
|
|
@@ -432,6 +491,7 @@ class GuiWidget:
|
|
|
432
491
|
"visible": self._hien,
|
|
433
492
|
"color": self._mau_chu,
|
|
434
493
|
"bg": self._mau_nen,
|
|
494
|
+
"transparency": self._do_trong_suot,
|
|
435
495
|
"font_size": self._co_font,
|
|
436
496
|
"enabled": self._kich_hoat,
|
|
437
497
|
}
|
|
@@ -448,7 +508,7 @@ class Label(GuiWidget):
|
|
|
448
508
|
|
|
449
509
|
def __init__(self, text, x, y, id=None, width=None,
|
|
450
510
|
color=None, bg=None, font_size=None, visible=True,
|
|
451
|
-
enabled=True):
|
|
511
|
+
enabled=True, transparency=0):
|
|
452
512
|
app = _reg._app_hien_tai(tao_mac_dinh=True)
|
|
453
513
|
super().__init__(app, x, y, id=id)
|
|
454
514
|
self.tkw = tk.Label(app.khung, text=text, bg=bg or app._nen,
|
|
@@ -460,6 +520,7 @@ class Label(GuiWidget):
|
|
|
460
520
|
self.font_size = font_size
|
|
461
521
|
if bg is not None:
|
|
462
522
|
self.bg = bg
|
|
523
|
+
self.transparency = transparency
|
|
463
524
|
self.visible = visible
|
|
464
525
|
self.enabled = enabled
|
|
465
526
|
|
|
@@ -480,7 +541,7 @@ class Button(GuiWidget):
|
|
|
480
541
|
|
|
481
542
|
def __init__(self, text, x, y, command=None, id=None, width=None,
|
|
482
543
|
color=None, bg=None, font_size=None, visible=True,
|
|
483
|
-
enabled=True):
|
|
544
|
+
enabled=True, transparency=0):
|
|
484
545
|
app = _reg._app_hien_tai(tao_mac_dinh=True)
|
|
485
546
|
super().__init__(app, x, y, id=id)
|
|
486
547
|
self._lenh = command
|
|
@@ -498,6 +559,7 @@ class Button(GuiWidget):
|
|
|
498
559
|
if bg:
|
|
499
560
|
self.bg = bg
|
|
500
561
|
self.font_size = font_size
|
|
562
|
+
self.transparency = transparency
|
|
501
563
|
self.visible = visible
|
|
502
564
|
self.enabled = enabled
|
|
503
565
|
|
|
@@ -552,7 +614,7 @@ class Input(GuiWidget):
|
|
|
552
614
|
|
|
553
615
|
def __init__(self, x, y, width=20, id=None,
|
|
554
616
|
color=None, bg=None, font_size=None, visible=True,
|
|
555
|
-
enabled=True):
|
|
617
|
+
enabled=True, transparency=0):
|
|
556
618
|
app = _reg._app_hien_tai(tao_mac_dinh=True)
|
|
557
619
|
super().__init__(app, x, y, id=id)
|
|
558
620
|
self._rong = int(width) if width else 20
|
|
@@ -570,6 +632,7 @@ class Input(GuiWidget):
|
|
|
570
632
|
self.font_size = font_size
|
|
571
633
|
if bg is not None:
|
|
572
634
|
self.bg = bg
|
|
635
|
+
self.transparency = transparency
|
|
573
636
|
self.visible = visible
|
|
574
637
|
self.enabled = enabled
|
|
575
638
|
|
|
@@ -599,7 +662,7 @@ class Checkbox(GuiWidget):
|
|
|
599
662
|
|
|
600
663
|
def __init__(self, text, x, y, checked=False, id=None, width=None,
|
|
601
664
|
color=None, bg=None, font_size=None, visible=True,
|
|
602
|
-
enabled=True):
|
|
665
|
+
enabled=True, transparency=0):
|
|
603
666
|
app = _reg._app_hien_tai(tao_mac_dinh=True)
|
|
604
667
|
super().__init__(app, x, y, id=id)
|
|
605
668
|
self._duoc_chon = bool(checked)
|
|
@@ -617,6 +680,7 @@ class Checkbox(GuiWidget):
|
|
|
617
680
|
self.font_size = font_size
|
|
618
681
|
if bg is not None:
|
|
619
682
|
self.bg = bg
|
|
683
|
+
self.transparency = transparency
|
|
620
684
|
self.visible = visible
|
|
621
685
|
self.enabled = enabled
|
|
622
686
|
|
|
@@ -655,7 +719,7 @@ class Text(GuiWidget):
|
|
|
655
719
|
|
|
656
720
|
def __init__(self, x, y, width=40, height=6, id=None,
|
|
657
721
|
color=None, bg=None, font_size=None, visible=True,
|
|
658
|
-
enabled=True):
|
|
722
|
+
enabled=True, transparency=0):
|
|
659
723
|
app = _reg._app_hien_tai(tao_mac_dinh=True)
|
|
660
724
|
super().__init__(app, x, y, id=id)
|
|
661
725
|
self._rong = int(width) if width else 40
|
|
@@ -673,6 +737,7 @@ class Text(GuiWidget):
|
|
|
673
737
|
self.font_size = font_size
|
|
674
738
|
if bg is not None:
|
|
675
739
|
self.bg = bg
|
|
740
|
+
self.transparency = transparency
|
|
676
741
|
self.visible = visible
|
|
677
742
|
self.enabled = enabled
|
|
678
743
|
|
|
@@ -707,6 +772,7 @@ def _tao_tu_du_lieu(app, d):
|
|
|
707
772
|
loai = d.get("loai")
|
|
708
773
|
st = dict(color=d.get("color"), bg=d.get("bg"),
|
|
709
774
|
font_size=d.get("font_size"),
|
|
775
|
+
transparency=d.get("transparency", 0) or 0,
|
|
710
776
|
visible=d.get("visible", True), enabled=d.get("enabled", True))
|
|
711
777
|
if loai == "label":
|
|
712
778
|
w = Label(d.get("text") or "", d["x"], d["y"], id=d.get("id"), **st)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flexgui"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.2"
|
|
8
8
|
description = "Thư viện GUI Python kéo-thả-thiết-kế trên tkinter — FlexGUI Studio tích hợp (design mode + mini VS Code cho trigger)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|