primocode 9.10.0 → 10.0.9
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.
- package/README.md +30 -6
- package/bin/primocode.js +169 -342
- package/lib/act.js +360 -39
- package/lib/browser.js +105 -4
- package/lib/casar.js +108 -0
- package/lib/catalogo.js +6 -2
- package/lib/claude-engine.js +38 -17
- package/lib/codex-engine.js +27 -9
- package/lib/config.js +10 -3
- package/lib/cursor.js +10 -1
- package/lib/entrada.js +77 -11
- package/lib/estudio/cinema.js +27 -9
- package/lib/estudio/diretor.js +21 -1
- package/lib/estudio/ende.js +16 -5
- package/lib/estudio/estilo.js +66 -0
- package/lib/estudio/gerativo.js +1453 -0
- package/lib/estudio/glifos.js +215 -0
- package/lib/estudio/hibrido.js +165 -0
- package/lib/estudio/index.js +284 -53
- package/lib/estudio/kit/base.jsx +285 -0
- package/lib/estudio/kit/gerativo.jsx +980 -0
- package/lib/estudio/kit/index.jsx +13 -0
- package/lib/estudio/kit/marca.jsx +741 -0
- package/lib/estudio/kit/tres.jsx +308 -0
- package/lib/estudio/kit/vox.jsx +357 -0
- package/lib/estudio/kit.js +78 -0
- package/lib/estudio/lancamento.js +441 -0
- package/lib/estudio/motion.js +2 -5
- package/lib/estudio/plano.js +28 -3
- package/lib/estudio/referencias.js +105 -0
- package/lib/estudio/remotion.js +139 -51
- package/lib/estudio/ritmo.js +77 -0
- package/lib/estudio/sons-leves.js +38 -1
- package/lib/estudio/variedade.js +105 -0
- package/lib/estudio/vox.js +414 -0
- package/lib/fala.js +1 -94
- package/lib/mcp.js +156 -0
- package/lib/personas.js +67 -0
- package/lib/pipeline.js +55 -56
- package/lib/repeticao.js +3 -1
- package/lib/skills.js +44 -2
- package/lib/snapshot.js +111 -0
- package/lib/tools.js +100 -22
- package/lib/ui.js +50 -8
- package/lib/video/analise.js +89 -0
- package/lib/video/auto.js +329 -0
- package/lib/video/camadas.js +207 -0
- package/lib/video/cli.js +74 -0
- package/lib/video/delta.js +90 -0
- package/lib/video/edits.js +379 -0
- package/lib/video/filters.js +280 -0
- package/lib/video/fx3d/Camadas.jsx +64 -0
- package/lib/video/fx3d/Objetos.jsx +128 -0
- package/lib/video/fx3d/Root.jsx +18 -0
- package/lib/video/fx3d/modelos.jsx +304 -0
- package/lib/video/fx3d.js +108 -0
- package/lib/video/gerados.js +119 -0
- package/lib/video/objetos.js +111 -0
- package/lib/video/ops-fx.js +453 -0
- package/lib/video/ops.js +347 -0
- package/lib/video/paralelo.js +50 -0
- package/lib/video/pedido.js +191 -0
- package/lib/video/projeto.js +133 -0
- package/lib/video/rastrear.js +95 -0
- package/lib/video/rastro.js +276 -0
- package/lib/video/render.js +274 -0
- package/lib/video/texto.js +144 -0
- package/lib/video/timeline.js +318 -0
- package/lib/video/transcript.js +124 -0
- package/lib/voz.js +75 -223
- package/package.json +2 -3
- package/skills/motion-de-primeira/SKILL.md +5 -5
- package/studio/narrar.py +10 -7
- package/studio/rastrear.py +399 -0
- package/studio/texto.py +208 -0
- package/voz/primo_voz/__main__.py +96 -236
- package/voz/primo_voz/transcrever.py +31 -1
- package/app/ansi.js +0 -55
- package/app/app.css +0 -500
- package/app/app.js +0 -1017
- package/app/icone.svg +0 -6
- package/app/index.html +0 -220
- package/app/primo-marca.js +0 -192
- package/lib/app.js +0 -615
- package/lib/icone.js +0 -263
- package/lib/instalar-app.js +0 -231
- package/lib/painel.js +0 -291
- package/voz/primo_voz/desperta.py +0 -195
- package/voz/primo_voz/orbe.py +0 -406
package/voz/primo_voz/orbe.py
DELETED
|
@@ -1,406 +0,0 @@
|
|
|
1
|
-
"""orbe.py — a única coisa que aparece na tela no modo voz.
|
|
2
|
-
|
|
3
|
-
"Quero que ele fique na parte inferior da tela, bem centralizado, um pouco
|
|
4
|
-
maior [...] mas ele tá rodando em uma caixa, deve ser totalmente
|
|
5
|
-
transparente." (23/09/2026)
|
|
6
|
-
|
|
7
|
-
── O QUE ERA A "CAIXA" ─────────────────────────────────────────────────────
|
|
8
|
-
Medido com uma captura da própria janela, no Tk 9 do macOS:
|
|
9
|
-
|
|
10
|
-
1. `overrideredirect(True)` NÃO tira a moldura: a janela nascia com barra de
|
|
11
|
-
título, os três botões e o nome "Primo Code Voice".
|
|
12
|
-
2. O fundo `systemTransparent` saía PRETO.
|
|
13
|
-
3. O Canvas não apaga o quadro anterior sobre fundo transparente: cada
|
|
14
|
-
partícula deixava um rastro, e em segundos a janela virava um borrão.
|
|
15
|
-
|
|
16
|
-
Nenhum dos três se conserta de dentro do Tk. Então, no macOS, a orb é
|
|
17
|
-
desenhada pelo próprio sistema (PyObjC): uma NSWindow sem borda, sem fundo e
|
|
18
|
-
sem sombra, e uma NSView que limpa e pinta cada quadro com transparência de
|
|
19
|
-
verdade — o brilho é um gradiente com alfa, não discos misturados contra uma
|
|
20
|
-
cor de fundo adivinhada. Só a orb aparece; o resto da janela não existe.
|
|
21
|
-
|
|
22
|
-
O Tk continua sendo quem roda o laço de eventos (é ele que o resto do modo
|
|
23
|
-
voz já usa), e segue como desenho de reserva fora do macOS ou sem PyObjC.
|
|
24
|
-
|
|
25
|
-
── ONDE ELA MORA ───────────────────────────────────────────────────────────
|
|
26
|
-
Embaixo, centralizada na ÁREA ÚTIL da tela — acima do Dock, não atrás dele.
|
|
27
|
-
A posição é aplicada depois de a janela existir: dada cedo demais, a janela
|
|
28
|
-
sem moldura ignora.
|
|
29
|
-
|
|
30
|
-
── O QUE ELA DESENHA ───────────────────────────────────────────────────────
|
|
31
|
-
Um núcleo com brilho suave e um enxame de partículas em órbita. A cor diz o
|
|
32
|
-
estado; o MOVIMENTO diz a intensidade — as partículas se afastam e aceleram
|
|
33
|
-
com a voz, e respiram devagar quando ele está pensando. Nenhuma palavra: quem
|
|
34
|
-
está falando não lê.
|
|
35
|
-
"""
|
|
36
|
-
from __future__ import annotations
|
|
37
|
-
|
|
38
|
-
import math
|
|
39
|
-
import queue
|
|
40
|
-
import random
|
|
41
|
-
import sys
|
|
42
|
-
import tkinter as tk
|
|
43
|
-
from typing import Callable, List, Optional, Tuple
|
|
44
|
-
|
|
45
|
-
# Os quatro estados, e a cor de cada um. A paleta é a do PrimoCode (ciano de
|
|
46
|
-
# marca, âmbar de aviso) para o modo voz não parecer outro produto.
|
|
47
|
-
CORES = {
|
|
48
|
-
"esperando": ("#22b8d8", "#0e3a4a"), # ciano calmo: pronto, sem pressa
|
|
49
|
-
"ouvindo": ("#22d3ee", "#0e7490"), # ciano vivo: a vez é sua
|
|
50
|
-
"pensando": ("#fbbf24", "#78350f"), # âmbar: ele está trabalhando
|
|
51
|
-
"falando": ("#67e8f9", "#0891b2"), # ciano claro: a voz é dele
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
LARGURA = 360 # só a área da orb e do brilho — o resto é transparente
|
|
55
|
-
ALTURA = 220
|
|
56
|
-
RAIO_BASE = 34 # o núcleo
|
|
57
|
-
MARGEM_INFERIOR = 24 # acima da área útil (o Dock já fica de fora)
|
|
58
|
-
PARTICULAS = 44
|
|
59
|
-
FUNDO_FALSO = "#05070a" # só no desenho de reserva, onde não há alfa
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _rgb(cor: str) -> Tuple[float, float, float]:
|
|
63
|
-
return int(cor[1:3], 16) / 255, int(cor[3:5], 16) / 255, int(cor[5:7], 16) / 255
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _mistura(a: str, b: str, quanto: float) -> str:
|
|
67
|
-
"""Duas cores hex, uma fração — a cor no meio do caminho."""
|
|
68
|
-
quanto = max(0.0, min(1.0, quanto))
|
|
69
|
-
ra, ga, ba = int(a[1:3], 16), int(a[3:5], 16), int(a[5:7], 16)
|
|
70
|
-
rb, gb, bb = int(b[1:3], 16), int(b[3:5], 16), int(b[5:7], 16)
|
|
71
|
-
return "#%02x%02x%02x" % (
|
|
72
|
-
int(ra + (rb - ra) * quanto),
|
|
73
|
-
int(ga + (gb - ga) * quanto),
|
|
74
|
-
int(ba + (bb - ba) * quanto),
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class Particula:
|
|
79
|
-
"""Um ponto em órbita.
|
|
80
|
-
|
|
81
|
-
Cada uma tem ângulo, raio e velocidade PRÓPRIOS, sorteados uma vez. É o
|
|
82
|
-
que separa um enxame de um relógio: com os mesmos valores para todas,
|
|
83
|
-
elas giram em formação e o olho lê como engrenagem, não como vida.
|
|
84
|
-
"""
|
|
85
|
-
|
|
86
|
-
def __init__(self, rnd: random.Random) -> None:
|
|
87
|
-
self.ang = rnd.uniform(0, math.tau)
|
|
88
|
-
self.raio = rnd.uniform(1.25, 2.3) # múltiplo do raio do núcleo
|
|
89
|
-
self.giro = rnd.uniform(-0.9, 0.9) or 0.4
|
|
90
|
-
self.tam = rnd.uniform(1.3, 3.0)
|
|
91
|
-
self.fase = rnd.uniform(0, math.tau)
|
|
92
|
-
self.brilho = rnd.uniform(0.45, 1.0)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class Quadro:
|
|
96
|
-
"""Um quadro da animação, sem saber quem vai pintá-lo."""
|
|
97
|
-
|
|
98
|
-
def __init__(self, vivo: str, fundo: str, raio: float, pulso: float,
|
|
99
|
-
particulas: List[Tuple[float, float, float, float]]):
|
|
100
|
-
self.vivo, self.fundo = vivo, fundo
|
|
101
|
-
self.raio, self.pulso = raio, pulso
|
|
102
|
-
self.particulas = particulas # (x, y, tamanho, intensidade 0..1)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# ── O desenho nativo do macOS ────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
def _criar_nativa(orbe: "Orbe"):
|
|
108
|
-
"""A janela sem borda e sem fundo, desenhada pelo macOS. None se não der."""
|
|
109
|
-
if sys.platform != "darwin":
|
|
110
|
-
return None
|
|
111
|
-
try:
|
|
112
|
-
import objc
|
|
113
|
-
from AppKit import (NSBackingStoreBuffered, NSBezierPath, NSColor,
|
|
114
|
-
NSCompositingOperationClear, NSGradient, NSMakeRect,
|
|
115
|
-
NSRectFillUsingOperation, NSScreen, NSView, NSWindow)
|
|
116
|
-
except Exception:
|
|
117
|
-
return None
|
|
118
|
-
|
|
119
|
-
def cor(hexa: str, alfa: float):
|
|
120
|
-
r, g, b = _rgb(hexa)
|
|
121
|
-
return NSColor.colorWithSRGBRed_green_blue_alpha_(r, g, b, max(0.0, min(1.0, alfa)))
|
|
122
|
-
|
|
123
|
-
# A classe Objective-C só pode ser registrada uma vez por processo.
|
|
124
|
-
try:
|
|
125
|
-
Vista = objc.lookUpClass("PrimoOrbeVista")
|
|
126
|
-
except objc.nosuchclass_error:
|
|
127
|
-
class PrimoOrbeVista(NSView):
|
|
128
|
-
def isOpaque(self):
|
|
129
|
-
return False
|
|
130
|
-
|
|
131
|
-
def acceptsFirstMouse_(self, evento):
|
|
132
|
-
return True
|
|
133
|
-
|
|
134
|
-
def drawRect_(self, rect):
|
|
135
|
-
NSColor.clearColor().set()
|
|
136
|
-
NSRectFillUsingOperation(self.bounds(), NSCompositingOperationClear)
|
|
137
|
-
q = getattr(self, "quadro", None)
|
|
138
|
-
if q is None:
|
|
139
|
-
return
|
|
140
|
-
cx, cy = LARGURA / 2, ALTURA / 2
|
|
141
|
-
centro = (cx, cy)
|
|
142
|
-
|
|
143
|
-
# BRILHO: um gradiente radial com alfa, que some no nada. É o
|
|
144
|
-
# que o Tk não sabia fazer — e o que tira o "disco escuro".
|
|
145
|
-
alcance = q.raio * (2.6 + q.pulso * 0.9)
|
|
146
|
-
brilho = NSGradient.alloc().initWithColors_(
|
|
147
|
-
[cor(q.vivo, 0.34 + q.pulso * 0.20), cor(q.vivo, 0.10), cor(q.vivo, 0.0)])
|
|
148
|
-
brilho.drawFromCenter_radius_toCenter_radius_options_(centro, q.raio * 0.6, centro, alcance, 0)
|
|
149
|
-
|
|
150
|
-
# PARTÍCULAS
|
|
151
|
-
for (px, py, t, forca) in q.particulas:
|
|
152
|
-
cor(q.vivo, 0.25 + forca * 0.75).set()
|
|
153
|
-
NSBezierPath.bezierPathWithOvalInRect_(NSMakeRect(px - t, py - t, 2 * t, 2 * t)).fill()
|
|
154
|
-
|
|
155
|
-
# NÚCLEO: gradiente do claro ao vivo, com um anel fino por fora.
|
|
156
|
-
r = q.raio
|
|
157
|
-
nucleo = NSGradient.alloc().initWithColors_(
|
|
158
|
-
[cor(_mistura(q.vivo, "#ffffff", 0.55), 1.0), cor(q.vivo, 1.0),
|
|
159
|
-
cor(_mistura(q.vivo, q.fundo, 0.45), 1.0)])
|
|
160
|
-
nucleo.drawFromCenter_radius_toCenter_radius_options_(
|
|
161
|
-
(cx - r * 0.25, cy + r * 0.25), 0, centro, r, 0)
|
|
162
|
-
cor(q.vivo, 0.55).set()
|
|
163
|
-
anel = NSBezierPath.bezierPathWithOvalInRect_(
|
|
164
|
-
NSMakeRect(cx - r * 1.18, cy - r * 1.18, r * 2.36, r * 2.36))
|
|
165
|
-
anel.setLineWidth_(1.5)
|
|
166
|
-
anel.stroke()
|
|
167
|
-
|
|
168
|
-
Vista = PrimoOrbeVista
|
|
169
|
-
|
|
170
|
-
try:
|
|
171
|
-
janela = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
|
|
172
|
-
NSMakeRect(0, 0, LARGURA, ALTURA), 0, NSBackingStoreBuffered, False) # 0 = sem borda
|
|
173
|
-
janela.setOpaque_(False)
|
|
174
|
-
janela.setBackgroundColor_(NSColor.clearColor())
|
|
175
|
-
janela.setHasShadow_(False)
|
|
176
|
-
janela.setLevel_(25) # NSStatusWindowLevel: por cima de tudo
|
|
177
|
-
# O clique ATRAVESSA a orb: ela mora no pé da tela, em cima do Dock e
|
|
178
|
-
# do que o cursor do Primo precisa clicar. Uma orb que segura o clique
|
|
179
|
-
# faria o agente clicar nela em vez de no app.
|
|
180
|
-
janela.setIgnoresMouseEvents_(True)
|
|
181
|
-
janela.setCollectionBehavior_(1 | 16) # todos os espaços; fora do Exposé
|
|
182
|
-
janela.setReleasedWhenClosed_(False)
|
|
183
|
-
vista = Vista.alloc().initWithFrame_(NSMakeRect(0, 0, LARGURA, ALTURA))
|
|
184
|
-
janela.setContentView_(vista)
|
|
185
|
-
|
|
186
|
-
# Embaixo, centralizada na área útil (acima do Dock). O Cocoa conta
|
|
187
|
-
# de baixo para cima, então a margem é somada ao pé da área útil.
|
|
188
|
-
util = NSScreen.mainScreen().visibleFrame()
|
|
189
|
-
x = util.origin.x + (util.size.width - LARGURA) / 2
|
|
190
|
-
y = util.origin.y + MARGEM_INFERIOR
|
|
191
|
-
janela.setFrameOrigin_((x, y))
|
|
192
|
-
janela.orderFrontRegardless()
|
|
193
|
-
return janela, vista
|
|
194
|
-
except Exception:
|
|
195
|
-
return None
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
class Orbe:
|
|
199
|
-
"""A janela da orb. Roda na thread principal, como toda interface."""
|
|
200
|
-
|
|
201
|
-
def __init__(self, ao_fechar: Optional[Callable] = None):
|
|
202
|
-
self.raiz = tk.Tk()
|
|
203
|
-
self.raiz.title("Primo Code Voice")
|
|
204
|
-
self.estado = "esperando"
|
|
205
|
-
self.visivel = True
|
|
206
|
-
self.nivel = 0.0 # 0..1, o quanto a pessoa está falando
|
|
207
|
-
self._fase = 0.0
|
|
208
|
-
self._fila = queue.Queue()
|
|
209
|
-
self._ao_fechar = ao_fechar
|
|
210
|
-
self._vivo = True
|
|
211
|
-
self._rnd = random.Random(7) # semente fixa: o enxame é sempre o mesmo
|
|
212
|
-
self._particulas = [Particula(self._rnd) for _ in range(PARTICULAS)]
|
|
213
|
-
|
|
214
|
-
nativa = _criar_nativa(self)
|
|
215
|
-
if nativa:
|
|
216
|
-
# O macOS desenha; a janela do Tk fica escondida e só roda o laço.
|
|
217
|
-
self.janela, self.vista = nativa
|
|
218
|
-
self.nativa = True
|
|
219
|
-
self.raiz.withdraw()
|
|
220
|
-
else:
|
|
221
|
-
self.janela = self.vista = None
|
|
222
|
-
self.nativa = False
|
|
223
|
-
self._montar_tk()
|
|
224
|
-
|
|
225
|
-
self.raiz.protocol("WM_DELETE_WINDOW", self.fechar)
|
|
226
|
-
self.raiz.bind("<Escape>", lambda e: self.fechar())
|
|
227
|
-
self._pintar()
|
|
228
|
-
|
|
229
|
-
# ── a reserva em Tk (fora do macOS, ou sem PyObjC) ───────────────────
|
|
230
|
-
def _montar_tk(self) -> None:
|
|
231
|
-
self.raiz.overrideredirect(True)
|
|
232
|
-
self.fundo = FUNDO_FALSO
|
|
233
|
-
if sys.platform == "win32":
|
|
234
|
-
# No Windows o Tk dá transparência por cor-chave, e ela funciona.
|
|
235
|
-
try:
|
|
236
|
-
self.raiz.attributes("-transparentcolor", FUNDO_FALSO)
|
|
237
|
-
except tk.TclError:
|
|
238
|
-
pass
|
|
239
|
-
try:
|
|
240
|
-
self.raiz.attributes("-topmost", True)
|
|
241
|
-
except tk.TclError:
|
|
242
|
-
pass
|
|
243
|
-
self.raiz.configure(bg=self.fundo)
|
|
244
|
-
self.tela = tk.Canvas(self.raiz, width=LARGURA, height=ALTURA,
|
|
245
|
-
bg=self.fundo, highlightthickness=0, bd=0)
|
|
246
|
-
self.tela.pack()
|
|
247
|
-
self.raiz.update_idletasks()
|
|
248
|
-
self._posicionar_tk()
|
|
249
|
-
self.raiz.after(120, self._posicionar_tk)
|
|
250
|
-
self.raiz.bind("<Button-1>", self._pegar)
|
|
251
|
-
self.raiz.bind("<B1-Motion>", self._arrastar)
|
|
252
|
-
self._pego = (0, 0)
|
|
253
|
-
|
|
254
|
-
def _posicionar_tk(self) -> None:
|
|
255
|
-
x = (self.raiz.winfo_screenwidth() - LARGURA) // 2
|
|
256
|
-
y = self.raiz.winfo_screenheight() - ALTURA - MARGEM_INFERIOR - 48
|
|
257
|
-
try:
|
|
258
|
-
self.raiz.geometry(f"{LARGURA}x{ALTURA}+{x}+{max(0, y)}")
|
|
259
|
-
except tk.TclError:
|
|
260
|
-
pass
|
|
261
|
-
|
|
262
|
-
def _pegar(self, e) -> None:
|
|
263
|
-
self._pego = (e.x_root - self.raiz.winfo_x(), e.y_root - self.raiz.winfo_y())
|
|
264
|
-
|
|
265
|
-
def _arrastar(self, e) -> None:
|
|
266
|
-
self.raiz.geometry(f"+{e.x_root - self._pego[0]}+{e.y_root - self._pego[1]}")
|
|
267
|
-
|
|
268
|
-
# ── a animação ───────────────────────────────────────────────────────
|
|
269
|
-
def _quadro(self) -> Quadro:
|
|
270
|
-
self._fase += 0.045
|
|
271
|
-
vivo, fundo = CORES.get(self.estado, CORES["esperando"])
|
|
272
|
-
cx, cy = LARGURA / 2, ALTURA / 2
|
|
273
|
-
|
|
274
|
-
# A respiração de cada estado. Quem espera respira devagar; quem
|
|
275
|
-
# pensa, num ritmo próprio; quem ouve pulsa com a VOZ, não com o
|
|
276
|
-
# relógio — é o que faz a orb parecer estar escutando de verdade.
|
|
277
|
-
if self.estado == "ouvindo":
|
|
278
|
-
pulso = 0.25 + self.nivel * 1.15
|
|
279
|
-
elif self.estado == "pensando":
|
|
280
|
-
pulso = 0.42 + 0.30 * math.sin(self._fase * 2.1)
|
|
281
|
-
elif self.estado == "falando":
|
|
282
|
-
pulso = 0.55 + 0.38 * math.sin(self._fase * 3.4)
|
|
283
|
-
else:
|
|
284
|
-
pulso = 0.16 + 0.10 * math.sin(self._fase * 0.9)
|
|
285
|
-
raio = RAIO_BASE * (1 + pulso * 0.30)
|
|
286
|
-
|
|
287
|
-
pontos = []
|
|
288
|
-
for p in self._particulas:
|
|
289
|
-
p.ang += p.giro * 0.012 * (0.6 + pulso)
|
|
290
|
-
# O raio respira junto com a voz: quanto mais alto se fala, mais
|
|
291
|
-
# longe o enxame se abre.
|
|
292
|
-
osc = math.sin(self._fase * 1.7 + p.fase)
|
|
293
|
-
r = raio * (p.raio + 0.25 * osc + pulso * 0.40)
|
|
294
|
-
# Elipse leve, mais larga que alta — e dentro da janela: o que
|
|
295
|
-
# passa da borda é cortado seco, e partícula cortada é defeito.
|
|
296
|
-
px = cx + math.cos(p.ang) * r * 1.2
|
|
297
|
-
py = cy + math.sin(p.ang) * r * 0.74
|
|
298
|
-
t = p.tam * (0.75 + pulso * 0.6)
|
|
299
|
-
pontos.append((px, py, t, p.brilho * (0.5 + pulso * 0.5)))
|
|
300
|
-
return Quadro(vivo, fundo, raio, pulso, pontos)
|
|
301
|
-
|
|
302
|
-
def _pintar(self) -> None:
|
|
303
|
-
if not self._vivo:
|
|
304
|
-
return
|
|
305
|
-
while True:
|
|
306
|
-
try:
|
|
307
|
-
estado, nivel = self._fila.get_nowait()
|
|
308
|
-
if estado == "__esconder__":
|
|
309
|
-
self._aplicarVisibilidade(False)
|
|
310
|
-
continue
|
|
311
|
-
if estado == "__mostrar__":
|
|
312
|
-
self._aplicarVisibilidade(True)
|
|
313
|
-
continue
|
|
314
|
-
if estado:
|
|
315
|
-
self.estado = estado
|
|
316
|
-
if nivel is not None:
|
|
317
|
-
self.nivel = max(0.0, min(1.0, nivel))
|
|
318
|
-
except queue.Empty:
|
|
319
|
-
break
|
|
320
|
-
|
|
321
|
-
q = self._quadro()
|
|
322
|
-
if self.nativa:
|
|
323
|
-
self.vista.quadro = q
|
|
324
|
-
self.vista.setNeedsDisplay_(True)
|
|
325
|
-
else:
|
|
326
|
-
self._pintar_tk(q)
|
|
327
|
-
self.raiz.after(33, self._pintar)
|
|
328
|
-
|
|
329
|
-
def _pintar_tk(self, q: Quadro) -> None:
|
|
330
|
-
"""A reserva: forma chapada, sem alfa. Misturas contra o fundo."""
|
|
331
|
-
cx, cy = LARGURA / 2, ALTURA / 2
|
|
332
|
-
self.tela.delete("all")
|
|
333
|
-
for i in range(6, 0, -1):
|
|
334
|
-
f = i / 6
|
|
335
|
-
r = q.raio * (1 + f * 1.3)
|
|
336
|
-
self.tela.create_oval(cx - r, cy - r, cx + r, cy + r,
|
|
337
|
-
outline=_mistura(self.fundo, q.vivo, (1 - f) ** 1.6 * 0.7), width=2)
|
|
338
|
-
for (px, py, t, forca) in q.particulas:
|
|
339
|
-
self.tela.create_oval(px - t, py - t, px + t, py + t,
|
|
340
|
-
fill=_mistura(self.fundo, q.vivo, 0.3 + forca * 0.7), outline="")
|
|
341
|
-
r = q.raio
|
|
342
|
-
self.tela.create_oval(cx - r, cy - r, cx + r, cy + r,
|
|
343
|
-
fill=_mistura(q.fundo, q.vivo, 0.8), outline="")
|
|
344
|
-
rn = r * 0.55
|
|
345
|
-
self.tela.create_oval(cx - rn, cy - rn, cx + rn, cy + rn,
|
|
346
|
-
fill=_mistura(q.vivo, "#ffffff", 0.4), outline="")
|
|
347
|
-
|
|
348
|
-
# ── visibilidade ─────────────────────────────────────────────────────
|
|
349
|
-
def esconder(self) -> None:
|
|
350
|
-
"""Some da tela sem morrer.
|
|
351
|
-
|
|
352
|
-
No modo de espera ele fica horas sem nada acontecer, e uma orb parada
|
|
353
|
-
na tela todo esse tempo é estorvo. Destruir e recriar a janela a cada
|
|
354
|
-
despertar pisca e, em alguns sistemas, rouba o foco de onde a pessoa
|
|
355
|
-
estava digitando. Esconder guarda tudo de pé.
|
|
356
|
-
"""
|
|
357
|
-
self._fila.put(("__esconder__", None))
|
|
358
|
-
|
|
359
|
-
def mostrar(self) -> None:
|
|
360
|
-
self._fila.put(("__mostrar__", None))
|
|
361
|
-
|
|
362
|
-
def _aplicarVisibilidade(self, visivel: bool) -> None:
|
|
363
|
-
# Só na thread da interface: nem o Tk nem o AppKit perdoam chamada de fora.
|
|
364
|
-
try:
|
|
365
|
-
if self.nativa:
|
|
366
|
-
if visivel:
|
|
367
|
-
self.janela.orderFrontRegardless()
|
|
368
|
-
else:
|
|
369
|
-
self.janela.orderOut_(None)
|
|
370
|
-
elif visivel:
|
|
371
|
-
self.raiz.deiconify()
|
|
372
|
-
self.raiz.lift()
|
|
373
|
-
if sys.platform != "linux":
|
|
374
|
-
self.raiz.attributes("-topmost", True)
|
|
375
|
-
else:
|
|
376
|
-
self.raiz.withdraw()
|
|
377
|
-
except Exception:
|
|
378
|
-
pass # janela já fechando: nada a fazer
|
|
379
|
-
self.visivel = visivel
|
|
380
|
-
|
|
381
|
-
# ── ciclo de vida ────────────────────────────────────────────────────
|
|
382
|
-
def mudar(self, estado: Optional[str] = None, nivel: Optional[float] = None) -> None:
|
|
383
|
-
self._fila.put((estado, nivel))
|
|
384
|
-
|
|
385
|
-
def fechar(self) -> None:
|
|
386
|
-
if not self._vivo:
|
|
387
|
-
return
|
|
388
|
-
self._vivo = False
|
|
389
|
-
try:
|
|
390
|
-
if self._ao_fechar:
|
|
391
|
-
self._ao_fechar()
|
|
392
|
-
except Exception:
|
|
393
|
-
pass
|
|
394
|
-
try:
|
|
395
|
-
if self.janela is not None:
|
|
396
|
-
self.janela.orderOut_(None)
|
|
397
|
-
self.janela.close()
|
|
398
|
-
except Exception:
|
|
399
|
-
pass
|
|
400
|
-
try:
|
|
401
|
-
self.raiz.destroy()
|
|
402
|
-
except Exception:
|
|
403
|
-
pass
|
|
404
|
-
|
|
405
|
-
def rodar(self) -> None:
|
|
406
|
-
self.raiz.mainloop()
|