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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flexgui
3
- Version: 1.0.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 📦 — tự cài nếu thiếu)
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 ⌨ Triggers) **hoặc** VS Code ngoài — cả hai sửa cùng một file.
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 📦 trong Studio): PyInstaller tự động — file nằm
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 📦 — tự cài nếu thiếu)
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 ⌨ Triggers) **hoặc** VS Code ngoài — cả hai sửa cùng một file.
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 📦 trong Studio): PyInstaller tự động — file nằm
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
@@ -53,7 +53,7 @@ from .widgets import Label, Button, Input, Checkbox, Text
53
53
  from .app import App
54
54
  from .runtime import run, setup, run_embedded
55
55
 
56
- __version__ = "1.0.0"
56
+ __version__ = "1.0.2"
57
57
  __author__ = "FlexGUI Team"
58
58
 
59
59
  __all__ = [
@@ -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 sổ."""
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="⏳ PyInstaller đang làm việc — có thể mất 1–3 phút "
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="📂 Mở thư mục dist", state="disabled",
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
- │ ◈ FlexGUI Studio · myapp [Mới][Mở][Lưu] [↶ ↷] [▦ Lưới] │
9
- │ [Triggers] [Xuất .py] [▲ Build] [▶│
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 ─ □ ✕ ║ │ THÀNH PHẦN │
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
- if rong:
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
- tk.Label(bar, text="◈", fg=MAU["gt"], bg=MAU["nen"],
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, rong=4).pack(side="left", padx=1)
272
- NutPhang(bar, "Mở…", self._mo_file, rong=4).pack(side="left", padx=1)
273
- NutPhang(bar, "Lưu", lambda: self._luu_toan_bo(True), rong=4,
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, "↶", self._hoan_tac, rong=2).pack(side="left", padx=1)
276
- NutPhang(bar, "↷", self._lam_lai, rong=2).pack(side="left", padx=1)
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, "▦ Lưới 10px", None, toggle=True,
281
- rong=9)
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, "⌨ Triggers", self._dao_drawer,
288
- toggle=True, rong=10)
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, rong=9).pack(
306
+ NutPhang(bar, "▲ Xuất .py", self._xuat_py).pack(
291
307
  side="right", padx=1)
292
- self.nut_build = NutPhang(bar, "📦 Build .exe", self._build_exe,
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, "▶ Xem trước", self._xem_truoc, accent=True,
296
- rong=10).pack(side="right", padx=(1, 8))
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=" ⌨ " + self._ten_triggers() + " • Python • UTF-8",
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 ▾", self._chen_mau, rong=9, co=8).pack(
511
+ NutPhang(dau, "Chèn mẫu ↓", self._chen_mau, co=8).pack(
497
512
  side="right", padx=4)
498
- NutPhang(dau, "💾 Áp dụng & Lưu", lambda: self._ghi_triggers(True),
499
- rong=14, co=8).pack(side="right", padx=4)
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=" ⌨ " + self._ten_triggers()
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="◈", fill="#ffffff",
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 ▦ trên toolbar)."""
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
- vien_bo(cv, X, Y, X + w, Y + h, 6, fill=d.get("bg") or "#ffffff",
774
- outline=d.get("bg") or MAU_APP["khung"], tags=tag)
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, fill="#ffffff",
785
- outline="#8a919c", tags=tag)
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"], outline="",
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="", tags=tag)
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, "⌨ Sửa trigger", lambda: self._sua_trigger(),
1549
+ NutPhang(duoi, "→ Sửa trigger", lambda: self._sua_trigger(),
1508
1550
  co=8).pack(side="left")
1509
- NutPhang(duoi, "⧉ Nhân bản", self._nhan_ban, co=8).pack(
1551
+ NutPhang(duoi, "❐ Nhân bản", self._nhan_ban, co=8).pack(
1510
1552
  side="left", padx=4)
1511
- NutPhang(duoi, "🗑 Xóa", self._xoa_dang_chon, co=8).pack(
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=" ⌨ " + self._ten_triggers()
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
- app.run(chay_mainloop=False)
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", "Helvetica"])
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", "Courier New"])
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.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