Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions install.bat
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ echo.

call venv\Scripts\activate.bat

pip install --upgrade pip --quiet --disable-pip-version-check
pip install -r requirements.txt --disable-pip-version-check
venv\Scripts\python.exe -m pip install --upgrade pip --quiet --disable-pip-version-check
venv\Scripts\python.exe -m pip install -r requirements.txt --disable-pip-version-check

if %errorlevel% neq 0 (
echo.
Expand Down
72 changes: 54 additions & 18 deletions voxflow/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,15 +450,14 @@ def _build_transcript(self):
).pack(side="right")

self.textbox = ctk.CTkTextbox(
card,
font=ctk.CTkFont(family="Segoe UI", size=14),
fg_color="transparent",
text_color=C["txt"],
wrap="word", height=90, border_width=0,
)
self.textbox.pack(fill="both", expand=True, padx=14, pady=(6, 10))
self.textbox.insert("1.0", "Twój tekst pojawi się tutaj...")
self.textbox.configure(state="disabled")
card,
font=ctk.CTkFont(family="Segoe UI", size=14),
fg_color="transparent",
text_color=C["txt"],
wrap="word", height=90, border_width=0,
)
self.textbox.pack(fill="both", expand=True, padx=14, pady=(6, 10))
self.textbox.insert("1.0", "Twój tekst pojawi się tutaj — możesz go edytować przed skopiowaniem...")

# ── Quick Controls (language / model / hotkey) ─────────────────

Expand Down Expand Up @@ -638,7 +637,42 @@ def opt_row(parent, label, values, var, cmd, width=130):
self.autostart_var = ctk.BooleanVar(value=is_autostart_enabled())
sw_row(inner, "🚀 Uruchamiaj z Windows", self.autostart_var, self._on_autostart_toggle)

ctk.CTkFrame(inner, fg_color=C["border"], height=1).pack(fill="x", pady=(8, 6))
# ── Color theme selector ───────────────────────
ctk.CTkFrame(inner, fg_color=C["border"], height=1).pack(fill="x", pady=(10, 8))

ctk.CTkLabel(
inner, text="🎨 Motyw kolorów",
font=ctk.CTkFont(size=12, weight="bold"),
text_color=C["txt2"],
).pack(anchor="w", pady=(0, 6))

theme_row = ctk.CTkFrame(inner, fg_color="transparent")
theme_row.pack(fill="x")

THEMES = [
("Fioletowy", "#7c3aed", "#09091a"),
("Niebieski", "#1d4ed8", "#080e1a"),
("Zielony", "#059669", "#08130f"),
]

def _apply_theme(accent, bg):
C["accent"] = accent
C["accent2"] = accent
C["accent_dim"] = accent
C["bg"] = bg
self.configure(fg_color=bg)
self.config.save()

for label, accent, bg in THEMES:
ctk.CTkButton(
theme_row, text=label, width=100, height=30,
font=ctk.CTkFont(size=11),
fg_color=accent, hover_color=accent,
corner_radius=8,
command=lambda a=accent, b=bg: _apply_theme(a, b),
).pack(side="left", padx=(0, 6))

ctk.CTkFrame(inner, fg_color=C["border"], height=1).pack(fill="x", pady=(10, 6))

ctk.CTkLabel(
inner,
Expand All @@ -649,6 +683,7 @@ def opt_row(parent, label, values, var, cmd, width=130):
justify="left",
).pack(anchor="w")


# ── Footer ────────────────────────────────────────────────────

def _build_footer(self):
Expand Down Expand Up @@ -736,7 +771,11 @@ def _finish_hotkey_capture(self, key: Optional[str]):
if key:
self.config.hotkey = key
self.config.save()
self.hotkey_manager.update_hotkey(key)
# BUGFIX: update_hotkey checks was_active which is False after stop(),
# so we update the key manually and always call start().
self.hotkey_manager.hotkey = key.lower()
self.hotkey_manager._is_combo = "+" in key
self.hotkey_manager.start()
display = key.upper().replace("+", " + ")
self.hotkey_btn.configure(
text=display,
Expand All @@ -753,7 +792,7 @@ def _finish_hotkey_capture(self, key: Optional[str]):
# Reset border after 2s
self.after(2000, lambda: self.hotkey_btn.configure(border_color=C["border2"]))
else:
# Cancelled / timeout
# Cancelled / timeout — restart with old hotkey
self.hotkey_btn.configure(
text=self.config.hotkey.upper().replace("+", " + "),
fg_color=C["bg_input"],
Expand Down Expand Up @@ -900,11 +939,9 @@ def _on_done(self, result: dict):
self.status.configure(text="🤔 Nie rozpoznano mowy", text_color=C["warn"])
return

# Update transcript box
self.textbox.configure(state="normal")
# Update transcript box (always editable — user can fix before copying)
self.textbox.delete("1.0", "end")
self.textbox.insert("1.0", text)
self.textbox.configure(state="disabled")

# Auto-copy
if self.config.auto_copy_to_clipboard:
Expand Down Expand Up @@ -1077,10 +1114,9 @@ def _reload_model(self, sz):
)

def _copy_text(self):
self.textbox.configure(state="normal")
t = self.textbox.get("1.0", "end").strip()
self.textbox.configure(state="disabled")
if t and t != "Twój tekst pojawi się tutaj...":
placeholder = "Twój tekst pojawi się tutaj"
if t and not t.startswith(placeholder):
pyperclip.copy(t)
self.status.configure(text="📋 Skopiowano!", text_color=C["ok"])

Expand Down
109 changes: 61 additions & 48 deletions voxflow/overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,20 @@ def _create_window(self):
win = self._root
win.overrideredirect(True)
win.attributes("-topmost", True)
win.attributes("-alpha", 0.0) # start invisible, fade in
win.attributes("-transparentcolor", "#010101") # key-colour transparency
win.attributes("-alpha", 0.0) # start invisible, fade in

sw = win.winfo_screenwidth()
sh = win.winfo_screenheight()
x = (sw - self.W) // 2
y = sh - self.H - self.MARGIN_BOTTOM
win.geometry(f"{self.W}x{self.H}+{x}+{y}")
win.configure(bg="#010101")

# Match canvas background to panel color — no transparent key needed
win.configure(bg="#0d0b1e")

self._canvas = tk.Canvas(
win, width=self.W, height=self.H,
bg="#010101", highlightthickness=0,
bg="#0d0b1e", highlightthickness=0,
)
self._canvas.pack()
self._animate()
Expand All @@ -93,7 +94,7 @@ def _animate(self):

# --- Fade in / fade out ---
if self._fading_out:
self._alpha = max(0.0, self._alpha - 0.07)
self._alpha = max(0.0, self._alpha - 0.08)
try:
self._root.attributes("-alpha", self._alpha)
except Exception:
Expand All @@ -102,8 +103,8 @@ def _animate(self):
if self._parent:
self._parent.after(0, self._destroy_window)
return
elif self._alpha < 0.95:
self._alpha = min(0.95, self._alpha + 0.06)
elif self._alpha < 0.93:
self._alpha = min(0.93, self._alpha + 0.07)
try:
self._root.attributes("-alpha", self._alpha)
except Exception:
Expand All @@ -122,29 +123,32 @@ def _draw(self):
W, H = self.W, self.H
c.delete("all")

# ── Outer glow (shadow layer) ─────────────────────────────
# Subtle purple glow border drawn as stacked rounded rects
for i in range(4, 0, -1):
alpha_colors = ["#1a0a2e", "#1e0d38", "#220f42", "#26114a"]
self._rrect(c, i, i, W - i, H - i, r=18, fill=alpha_colors[i-1], outline="")
# ── Border glow (layered rectangles, outer→inner) ────────
glow_colors = ["#3b1f6a", "#4c2181", "#5d28a0", "#6d28d9"]
radii = [16, 14, 13, 12]
for col, r in zip(glow_colors, radii):
pad = radii[0] - r
self._rrect(c, pad, pad, W - pad, H - pad, r=r, fill="", outline=col, width=2)

# ── Glass panel fill ──────────────────────────────────────
self._rrect(c, 3, 3, W - 3, H - 3, r=12,
fill="#100e28", outline="")

# ── Glass panel ───────────────────────────────────────────
self._rrect(c, 3, 3, W - 3, H - 3, r=16,
fill="#0d0b1e", outline="#6d28d9", width=2)
# Inner top-shine strip
self._rrect(c, 4, 4, W - 4, 4 + H // 3, r=11,
fill="#ffffff0a", outline="")

# Inner highlight line (top edge shine)
self._rrect(c, 4, 4, W - 4, 18, r=14,
fill="#ffffff08", outline="")
# Thin bright border top-cap
self._rrect(c, 3, 3, W - 3, H - 3, r=12,
fill="", outline="#7c3aed", width=1)

# ── Pulsing record dot (left) ─────────────────────────────
pulse = (math.sin(self._phase * 3.0) + 1) / 2
cx, cy = 38, H // 2

# Outer glow rings
for ri, col, fade in [(22, "#ef444420", 1.0),
(17, "#ef444440", 1.0),
(13, "#ef444480", 1.0)]:
r = ri + pulse * 3
for ri, col in [(22, "#ef444418"), (16, "#ef444438"), (12, "#ef444470")]:
r = ri + pulse * 4
c.create_oval(cx - r, cy - r, cx + r, cy + r,
fill=col, outline="")

Expand All @@ -153,38 +157,41 @@ def _draw(self):
c.create_oval(cx - dr, cy - dr, cx + dr, cy + dr,
fill="#ef4444", outline="#fca5a5", width=1)

# Tiny white mic icon inside dot
# Mic icon inside dot
c.create_oval(cx - 3, cy - 6, cx + 3, cy, fill="white", outline="")
c.create_line(cx, cy, cx, cy + 5, fill="white", width=2)
c.create_line(cx - 4, cy + 5, cx + 4, cy + 5, fill="white", width=2)

# ── Text labels ───────────────────────────────────────────
# ── Text ──────────────────────────────────────────────────
c.create_text(62, cy - 9,
text="Nagrywam...",
fill="#fca5a5", font=("Segoe UI", 11, "bold"),
anchor="w")
c.create_text(62, cy + 8,
c.create_text(62, cy + 9,
text="VoxFlow \u2022 zwolnij klawisz aby zakonczyc",
fill="#7c6fa0", font=("Segoe UI", 8),
fill="#6d5fa0", font=("Segoe UI", 8),
anchor="w")

# ── Waveform bars (right section) ─────────────────────────
n_bars = 30
# Thin separator line
c.create_line(230, 12, 230, H - 12, fill="#3d2d70", width=1)

# ── Waveform bars ─────────────────────────────────────────
n_bars = 32
bw = 4
gap = 2
x0 = 230
x0 = 238
max_h = 20

for i in range(n_bars):
if x0 + i * (bw + gap) > W - 8:
break
t = self._phase * 2.6 + i * 0.38
wave = abs(math.sin(t)) * 0.6 + abs(math.sin(t * 0.7 + 1.1)) * 0.4
amp = (wave * 0.5 + self._level * 0.65) * max_h + 3
amp = (wave * 0.45 + self._level * 0.7) * max_h + 3
amp = min(amp, max_h)

x = x0 + i * (bw + gap)
bar_cy = cy

# Gradient colour: low=purple, mid=violet, high=rose
ratio = amp / max_h
if ratio > 0.78:
col = "#f43f5e"
Expand All @@ -196,26 +203,32 @@ def _draw(self):
col = "#4c1d95"

# Bar body
c.create_rectangle(x, bar_cy - amp, x + bw, bar_cy + amp,
c.create_rectangle(x, cy - amp, x + bw, cy + amp,
fill=col, outline="")
# Rounded cap
c.create_oval(x - 1, bar_cy - amp - 2, x + bw + 1, bar_cy - amp + 2,
# Rounded top cap
c.create_oval(x - 1, cy - amp - 2, x + bw + 1, cy - amp + 2,
fill=col, outline="")

# ── Right accent line ─────────────────────────────────────
rx = x0 + n_bars * (bw + gap) + 6
c.create_line(rx, 12, rx, H - 12,
fill="#6d28d950", width=1)

# ── Helper ────────────────────────────────────────────────────

@staticmethod
def _rrect(canvas, x1, y1, x2, y2, r=12, **kw):
"""Draw a rounded rectangle."""
canvas.create_arc(x1, y1, x1+2*r, y1+2*r, start=90, extent=90, style="pieslice", **kw)
canvas.create_arc(x2 - 2*r, y1, x2, y1+2*r, start=0, extent=90, style="pieslice", **kw)
canvas.create_arc(x1, y2 - 2*r, x1+2*r, y2, start=180, extent=90, style="pieslice", **kw)
canvas.create_arc(x2 - 2*r, y2 - 2*r, x2, y2, start=270, extent=90, style="pieslice", **kw)
canvas.create_rectangle(x1 + r, y1, x2 - r, y2, **kw)
canvas.create_rectangle(x1, y1 + r, x1 + r, y2 - r, **kw)
canvas.create_rectangle(x2 - r, y1 + r, x2, y2 - r, **kw)
"""Draw a rounded rectangle on canvas."""
# Clamp radius
r = min(r, (x2 - x1) // 2, (y2 - y1) // 2)
points = [
x1 + r, y1,
x2 - r, y1,
x2, y1,
x2, y1 + r,
x2, y2 - r,
x2, y2,
x2 - r, y2,
x1 + r, y2,
x1, y2,
x1, y2 - r,
x1, y1 + r,
x1, y1,
x1 + r, y1,
]
canvas.create_polygon(points, smooth=True, **kw)
Loading