meltygui 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
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.
- meltygui/core/cache/mask_batch.py +16 -4
- meltygui/core/cache/tile_cache.py +79 -45
- meltygui/core/graphics/overlay_renderer.py +6 -2
- meltygui/core/graphics/screenshot.py +3 -2
- meltygui/core/melty.py +22 -8
- meltygui/core/runtime/app.py +71 -7
- meltygui/core/services/terminal_core.py +4 -1
- meltygui/core/styling/fonts.py +31 -14
- meltygui/core/windowing/glfw_utils.py +14 -1
- meltygui/core/windowing/os_frame.py +20 -12
- meltygui/core/windowing/surface.py +16 -11
- meltygui/core/windowing/titlebar.py +49 -3
- meltygui/graphics/shader_compiler.py +9 -3
- {meltygui-0.1.2.dist-info → meltygui-0.1.3.dist-info}/METADATA +6 -3
- {meltygui-0.1.2.dist-info → meltygui-0.1.3.dist-info}/RECORD +17 -17
- {meltygui-0.1.2.dist-info → meltygui-0.1.3.dist-info}/WHEEL +0 -0
- {meltygui-0.1.2.dist-info → meltygui-0.1.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -92,6 +92,7 @@ def batches(stamps):
|
|
|
92
92
|
class MaskBatch:
|
|
93
93
|
def __init__(self):
|
|
94
94
|
self.program = self.vao = self.vbo = None
|
|
95
|
+
self.flat_texture = None
|
|
95
96
|
self.source = None
|
|
96
97
|
self.multi_bind = False
|
|
97
98
|
self.fb_location = self.textures_location = None
|
|
@@ -103,6 +104,9 @@ class MaskBatch:
|
|
|
103
104
|
gl.glDeleteBuffers(1, [self.vbo])
|
|
104
105
|
if self.vao is not None:
|
|
105
106
|
gl.glDeleteVertexArrays(1, [self.vao])
|
|
107
|
+
if getattr(self, 'flat_texture', None) is not None:
|
|
108
|
+
gl.glDeleteTextures([self.flat_texture])
|
|
109
|
+
self.flat_texture = None
|
|
106
110
|
self.program = self.vao = self.vbo = None
|
|
107
111
|
self.source = None
|
|
108
112
|
|
|
@@ -155,10 +159,18 @@ class MaskBatch:
|
|
|
155
159
|
gl.glBufferData(gl.GL_ARRAY_BUFFER, len(data), data, gl.GL_STREAM_DRAW)
|
|
156
160
|
first_instance = 0
|
|
157
161
|
for textures, data, count in packed:
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
# Apple requires complete samplers even for the flat branch.
|
|
163
|
+
# Own a tiny source for that case; never sample the destination.
|
|
164
|
+
if not textures and self.flat_texture is None:
|
|
165
|
+
self.flat_texture = int(gl.glGenTextures(1))
|
|
166
|
+
gl.glActiveTexture(gl.GL_TEXTURE0)
|
|
167
|
+
gl.glBindTexture(gl.GL_TEXTURE_2D, self.flat_texture)
|
|
168
|
+
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST)
|
|
169
|
+
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST)
|
|
170
|
+
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8, 1, 1, 0,
|
|
171
|
+
gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, bytes(4))
|
|
172
|
+
bank = (textures + [textures[0]] * (TEXTURES_PER_BATCH - len(textures))
|
|
173
|
+
if textures else [self.flat_texture] * TEXTURES_PER_BATCH)
|
|
162
174
|
if self.multi_bind and textures:
|
|
163
175
|
gl.glBindTextures(0, TEXTURES_PER_BATCH, bank)
|
|
164
176
|
else:
|
|
@@ -130,6 +130,19 @@ def _tile_alloc(t) -> Tuple[int, int]:
|
|
|
130
130
|
return getattr(t, "alloc_size", None) or t.size
|
|
131
131
|
|
|
132
132
|
|
|
133
|
+
def _pixel_rect(rect, scale):
|
|
134
|
+
"""Window-point bounds to integer texel bounds, including fractional DPI."""
|
|
135
|
+
x, y, w, h = rect
|
|
136
|
+
sx, sy = scale
|
|
137
|
+
left, bottom = snap_int(x * sx), snap_int(y * sy)
|
|
138
|
+
return left, bottom, snap_int((x + w) * sx) - left, snap_int((y + h) * sy) - bottom
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _tile_pixel_size(tile, *, allocated=False):
|
|
142
|
+
size = _tile_alloc(tile) if allocated else tile.size
|
|
143
|
+
return _pixel_rect((0, 0, *size), tile.pixel_scale)[2:]
|
|
144
|
+
|
|
145
|
+
|
|
133
146
|
def _tile_uv_rect(t) -> Tuple[float, float, float, float]:
|
|
134
147
|
"""uUVRect (xy scale, zw offset) mapping a 0..1 dest-rect UV onto the
|
|
135
148
|
top-anchored logical subrect of a bucket-padded tile texture."""
|
|
@@ -150,7 +163,8 @@ class Tile:
|
|
|
150
163
|
mask_tex: int # Cached subtree mask for this tile
|
|
151
164
|
rbo: Optional[int]
|
|
152
165
|
size: Tuple[int, int] # LOGICAL view size; the texture may be larger (alloc_size)
|
|
153
|
-
alloc_size: Optional[Tuple[int, int]] = None # bucketed
|
|
166
|
+
alloc_size: Optional[Tuple[int, int]] = None # bucketed window-point dims, None = same as size
|
|
167
|
+
pixel_scale: Tuple[float, float] = (1.0, 1.0) # physical texels per window point
|
|
154
168
|
dirty: bool = True
|
|
155
169
|
last_clean_frame: int = -1
|
|
156
170
|
last_invalidated_frame: int = 3
|
|
@@ -364,7 +378,8 @@ def _clear_mask_regions(mask_tex: int, rects) -> None:
|
|
|
364
378
|
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
|
365
379
|
|
|
366
380
|
|
|
367
|
-
def _ensure_tile(existing: Optional[Tile], w: int, h: int, frame_id: int = 0, draw_state=None, tile_id=None
|
|
381
|
+
def _ensure_tile(existing: Optional[Tile], w: int, h: int, frame_id: int = 0, draw_state=None, tile_id=None,
|
|
382
|
+
pixel_scale=(1.0, 1.0)) -> \
|
|
368
383
|
Optional[Tile]:
|
|
369
384
|
# Layout occasionally hands us fractional or negative dims (e.g. a
|
|
370
385
|
# midline is 0.333... width). glTexImage2D coerces those to int and lands on
|
|
@@ -374,7 +389,8 @@ def _ensure_tile(existing: Optional[Tile], w: int, h: int, frame_id: int = 0, dr
|
|
|
374
389
|
h = int(h) if h and h > 0 else 0
|
|
375
390
|
if w <= 0 or h <= 0:
|
|
376
391
|
return None
|
|
377
|
-
|
|
392
|
+
same_scale = existing is not None and existing.pixel_scale == pixel_scale
|
|
393
|
+
if same_scale and existing.size == (w, h):
|
|
378
394
|
return existing
|
|
379
395
|
|
|
380
396
|
aw = _bucket(w)
|
|
@@ -390,7 +406,7 @@ def _ensure_tile(existing: Optional[Tile], w: int, h: int, frame_id: int = 0, dr
|
|
|
390
406
|
aw = max(aw, snap_int(eaw))
|
|
391
407
|
ah = max(ah, snap_int(eah))
|
|
392
408
|
|
|
393
|
-
if
|
|
409
|
+
if same_scale and _tile_alloc(existing) == (aw, ah):
|
|
394
410
|
# Same bucket: update the logical size in place - no GL realloc, no
|
|
395
411
|
# crop-blit (top-anchored content keeps the screen-top-left corner on
|
|
396
412
|
# the same texels). Returning the SAME object is the caller's signal
|
|
@@ -424,6 +440,7 @@ def _ensure_tile(existing: Optional[Tile], w: int, h: int, frame_id: int = 0, dr
|
|
|
424
440
|
# one) would leave stale G/B/A inside the new logical rect.
|
|
425
441
|
gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE)
|
|
426
442
|
gl.glClearColor(0, 0, 0, 0.0)
|
|
443
|
+
bands = [_pixel_rect(rect, pixel_scale) for rect in bands]
|
|
427
444
|
for x, y, cw, ch in bands:
|
|
428
445
|
gl.glScissor(int(x), int(y), int(cw), int(ch))
|
|
429
446
|
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
|
@@ -480,7 +497,8 @@ def _ensure_tile(existing: Optional[Tile], w: int, h: int, frame_id: int = 0, dr
|
|
|
480
497
|
except Exception:
|
|
481
498
|
pass
|
|
482
499
|
try:
|
|
483
|
-
|
|
500
|
+
pixel_aw, pixel_ah = _pixel_rect((0, 0, aw, ah), pixel_scale)[2:]
|
|
501
|
+
new_tex = _create_color_tex(pixel_aw, pixel_ah)
|
|
484
502
|
except Exception as e:
|
|
485
503
|
existing_size = existing.size if existing else None
|
|
486
504
|
reset = "\033[0m"
|
|
@@ -490,13 +508,13 @@ def _ensure_tile(existing: Optional[Tile], w: int, h: int, frame_id: int = 0, dr
|
|
|
490
508
|
f"\nCurrent size {existing_size}\n{'=' * 10}{reset}")
|
|
491
509
|
return None
|
|
492
510
|
|
|
493
|
-
new_mask_tex = _create_mask_tex(
|
|
511
|
+
new_mask_tex = _create_mask_tex(pixel_aw, pixel_ah)
|
|
494
512
|
# No depth-stencil renderbuffer: tiles are only ever written by the PASS 3
|
|
495
513
|
# copy shader and the crop-blit, neither of which depth/stencil-tests, and
|
|
496
514
|
# the D24S8 attachment was 4 B/px of VRAM plus the slowest part of the
|
|
497
515
|
# create/destroy/delete cycle. Tile.rbo stays None; the guarded delete
|
|
498
516
|
# sites will free RBOs on tiles created before this change.
|
|
499
|
-
new_fbo, new_rbo = _create_fbo_with_tex(new_tex, False,
|
|
517
|
+
new_fbo, new_rbo = _create_fbo_with_tex(new_tex, False, pixel_aw, pixel_ah)
|
|
500
518
|
|
|
501
519
|
if existing:
|
|
502
520
|
st = _GLState()
|
|
@@ -533,13 +551,15 @@ def _ensure_tile(existing: Optional[Tile], w: int, h: int, frame_id: int = 0, dr
|
|
|
533
551
|
cw = max(0, min(ow, w))
|
|
534
552
|
ch = max(0, min(oh, h))
|
|
535
553
|
if cw > 0 and ch > 0:
|
|
554
|
+
src_x, src_y, src_w, src_h = _pixel_rect((0, old_ah - ch, cw, ch), existing.pixel_scale)
|
|
555
|
+
dst_x, dst_y, dst_w, dst_h = _pixel_rect((0, ah - ch, cw, ch), pixel_scale)
|
|
536
556
|
gl.glBlitFramebuffer(
|
|
537
|
-
|
|
538
|
-
|
|
557
|
+
src_x, src_y, src_x + src_w, src_y + src_h,
|
|
558
|
+
dst_x, dst_y, dst_x + dst_w, dst_y + dst_h,
|
|
539
559
|
gl.GL_COLOR_BUFFER_BIT,
|
|
540
560
|
gl.GL_NEAREST, # No scaling -> NEAREST is exact and cheap
|
|
541
561
|
)
|
|
542
|
-
_clear_mask_regions(new_mask_tex, [(0, 0,
|
|
562
|
+
_clear_mask_regions(new_mask_tex, [(0, 0, pixel_aw, pixel_ah)])
|
|
543
563
|
finally:
|
|
544
564
|
st.restore()
|
|
545
565
|
|
|
@@ -562,12 +582,12 @@ def _ensure_tile(existing: Optional[Tile], w: int, h: int, frame_id: int = 0, dr
|
|
|
562
582
|
gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE)
|
|
563
583
|
gl.glClearColor(0, 0, 0, 0.0)
|
|
564
584
|
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
|
|
565
|
-
_clear_mask_regions(new_mask_tex, [(0, 0,
|
|
585
|
+
_clear_mask_regions(new_mask_tex, [(0, 0, pixel_aw, pixel_ah)])
|
|
566
586
|
finally:
|
|
567
587
|
st.restore()
|
|
568
588
|
|
|
569
589
|
t = Tile(draw_state=draw_state, fbo=new_fbo, tex=new_tex, mask_tex=new_mask_tex, rbo=new_rbo, size=(w, h),
|
|
570
|
-
alloc_size=(aw, ah), dirty=True)
|
|
590
|
+
alloc_size=(aw, ah), pixel_scale=pixel_scale, dirty=True)
|
|
571
591
|
# Seed filled_bbox to what the crop-copy above covered (in tile-local
|
|
572
592
|
# top-left coords). On a shrink it's the whole tile -> tile reads are fully
|
|
573
593
|
# covered and the scroll-invalidate gate stops early; on a grow it's
|
|
@@ -1194,6 +1214,9 @@ void main() { oColor = uColor; }
|
|
|
1194
1214
|
# Main class
|
|
1195
1215
|
# ==============================
|
|
1196
1216
|
class TileCacheMasked:
|
|
1217
|
+
# Existing live caches acquire per-window backing scale on their next
|
|
1218
|
+
# frame after a hotswap; unscaled caches retain their previous behavior.
|
|
1219
|
+
_pixel_scale = (1.0, 1.0)
|
|
1197
1220
|
def __init__(self):
|
|
1198
1221
|
self.initial_value = {}
|
|
1199
1222
|
self.did_deviate = {}
|
|
@@ -1260,6 +1283,7 @@ class TileCacheMasked:
|
|
|
1260
1283
|
self.all_keys = set()
|
|
1261
1284
|
|
|
1262
1285
|
self._fb_size: Tuple[int, int] = (0, 0)
|
|
1286
|
+
self._pixel_scale = (1.0, 1.0)
|
|
1263
1287
|
# Allocated dims of the four co-sized internal surfaces (_mask,
|
|
1264
1288
|
# _sub_mask, _full_sub_mask, snapshot). For monitor only, grow-only.
|
|
1265
1289
|
# _fb_size stays the LOGICAL framebuffer size everywhere.
|
|
@@ -1537,10 +1561,10 @@ class TileCacheMasked:
|
|
|
1537
1561
|
fl, ft, fr, fb = t.filled_bbox
|
|
1538
1562
|
return fl > vl + 0.5 or ft > vt + 0.5 or fr < vr - 0.5 or fb < vb - 0.5
|
|
1539
1563
|
|
|
1540
|
-
|
|
1541
|
-
def _oversized(size: Optional[Tuple[int, int]]) -> bool:
|
|
1564
|
+
def _oversized(self, size: Optional[Tuple[int, int]]) -> bool:
|
|
1542
1565
|
"""True if a view this size is too large to back with an offscreen tile."""
|
|
1543
|
-
return size is not None and (size[0]
|
|
1566
|
+
return size is not None and (size[0] * self._pixel_scale[0] > MAX_TILE_DIM
|
|
1567
|
+
or size[1] * self._pixel_scale[1] > MAX_TILE_DIM)
|
|
1544
1568
|
|
|
1545
1569
|
def _discard_tile(self, key: str) -> None:
|
|
1546
1570
|
"""Drop a tile and free its GL resources (e.g. a view grew too large to cache)."""
|
|
@@ -2245,8 +2269,13 @@ class TileCacheMasked:
|
|
|
2245
2269
|
key = self.key_to_parent_key.get(key)
|
|
2246
2270
|
self._resize_input_keys = keys
|
|
2247
2271
|
|
|
2248
|
-
def mask_begin_frame(self, framebuffer_size: Tuple[int, int]) -> None:
|
|
2272
|
+
def mask_begin_frame(self, framebuffer_size: Tuple[int, int], pixel_scale=None) -> None:
|
|
2249
2273
|
fb_w, fb_h = map(int, framebuffer_size)
|
|
2274
|
+
if pixel_scale is not None and tuple(pixel_scale) != getattr(self, '_pixel_scale', (1.0, 1.0)):
|
|
2275
|
+
self._pixel_scale = tuple(pixel_scale)
|
|
2276
|
+
# A move between displays changes the backing resolution once.
|
|
2277
|
+
# Wake cached ancestors as well as leaves so every tile recaptures.
|
|
2278
|
+
self.invalidate_all(other_windows=False)
|
|
2250
2279
|
self._note_frame_stats()
|
|
2251
2280
|
self._refresh_resize_input_keys()
|
|
2252
2281
|
self._frame_id += 1
|
|
@@ -3186,7 +3215,7 @@ class TileCacheMasked:
|
|
|
3186
3215
|
(ix0, iy0, ix1 - ix0, iy1 - iy0),
|
|
3187
3216
|
(d_and_l[0] * rank_scale, d_and_l[1] * rank_scale,
|
|
3188
3217
|
d_and_l[2] * rank_scale, d_and_l[3] * rank_scale,
|
|
3189
|
-
|
|
3218
|
+
max(0.0, cr) * s_x, margin * s_x))
|
|
3190
3219
|
_, ix0, iy0, ix1, iy1, own_clip, _owner, inset, rect4, tail6 = base
|
|
3191
3220
|
if ix1 <= ix0 or iy1 <= iy0:
|
|
3192
3221
|
continue
|
|
@@ -3222,8 +3251,9 @@ class TileCacheMasked:
|
|
|
3222
3251
|
gl.glUseProgram(self._prog_shadow_batch)
|
|
3223
3252
|
_win_tex = getattr(self, "_win_mask_tex", None)
|
|
3224
3253
|
gl.glActiveTexture(gl.GL_TEXTURE1)
|
|
3225
|
-
|
|
3226
|
-
|
|
3254
|
+
# WinZ=1 disables occlusion for tile bakes. The sampler still
|
|
3255
|
+
# needs a complete texture on Apple even when that branch is off.
|
|
3256
|
+
gl.glBindTexture(gl.GL_TEXTURE_2D, _win_tex or 0)
|
|
3227
3257
|
gl.glActiveTexture(gl.GL_TEXTURE0)
|
|
3228
3258
|
gl.glUniform1i(self._loc_sb_uWinMask, 1)
|
|
3229
3259
|
gl.glUniform2f(self._loc_sb_uFBSize,
|
|
@@ -3313,8 +3343,7 @@ class TileCacheMasked:
|
|
|
3313
3343
|
gl.glUseProgram(self._prog_shadow_grad)
|
|
3314
3344
|
_win_tex = getattr(self, "_win_mask_tex", None)
|
|
3315
3345
|
gl.glActiveTexture(gl.GL_TEXTURE1)
|
|
3316
|
-
gl.glBindTexture(gl.GL_TEXTURE_2D,
|
|
3317
|
-
_win_tex if (win_gate and _win_tex) else 0)
|
|
3346
|
+
gl.glBindTexture(gl.GL_TEXTURE_2D, _win_tex or 0)
|
|
3318
3347
|
gl.glActiveTexture(gl.GL_TEXTURE0)
|
|
3319
3348
|
gl.glUniform1i(self._loc_sg_uWinMask, 1)
|
|
3320
3349
|
_fbw, _fbh = self._fb_size
|
|
@@ -3431,8 +3460,8 @@ class TileCacheMasked:
|
|
|
3431
3460
|
float(d_and_l[2]) / 65535.5,
|
|
3432
3461
|
float(d_and_l[3]) / 65535.5)
|
|
3433
3462
|
gl.glUniform2f(self._loc_sg_uRectSize, float(iw), float(ih))
|
|
3434
|
-
gl.glUniform1f(self._loc_sg_uCornerRadius, max(0.0, cr))
|
|
3435
|
-
gl.glUniform1f(self._loc_sg_uMargin, margin)
|
|
3463
|
+
gl.glUniform1f(self._loc_sg_uCornerRadius, max(0.0, cr) * s_x)
|
|
3464
|
+
gl.glUniform1f(self._loc_sg_uMargin, margin * s_x)
|
|
3436
3465
|
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3)
|
|
3437
3466
|
gl.glDisable(gl.GL_SCISSOR_TEST)
|
|
3438
3467
|
gl.glBlendEquation(gl.GL_FUNC_ADD)
|
|
@@ -3505,7 +3534,7 @@ class TileCacheMasked:
|
|
|
3505
3534
|
gl.glUniform2f(self._loc_maskr_uRectSize, float(iw), float(ih))
|
|
3506
3535
|
gl.glUniform1f(self._loc_maskr_uCornerRadius,
|
|
3507
3536
|
max(0.0, float(getattr(wds, "corner_radius", 6.0)
|
|
3508
|
-
or 0.0)))
|
|
3537
|
+
or 0.0)) * s_x)
|
|
3509
3538
|
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3)
|
|
3510
3539
|
gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE)
|
|
3511
3540
|
|
|
@@ -3726,10 +3755,10 @@ class TileCacheMasked:
|
|
|
3726
3755
|
right texels, and fb_h is the REAL height for the y flip."""
|
|
3727
3756
|
dd = imgui.get_draw_data()
|
|
3728
3757
|
dp_x, dp_y = dd.display_pos
|
|
3729
|
-
s_x, s_y =
|
|
3758
|
+
s_x, s_y = imgui.get_io().display_fb_scale
|
|
3730
3759
|
ox, oy = getattr(Melty, "frame_origin", None) or (0, 0)
|
|
3731
|
-
dp_x -= int(ox)
|
|
3732
|
-
dp_y -= int(oy)
|
|
3760
|
+
dp_x -= int(ox) / s_x
|
|
3761
|
+
dp_y -= int(oy) / s_y
|
|
3733
3762
|
real = getattr(Melty, "framebuffer_size", None)
|
|
3734
3763
|
if real:
|
|
3735
3764
|
fb_w, fb_h = snap_int(real[0]), snap_int(real[1])
|
|
@@ -3841,7 +3870,8 @@ class TileCacheMasked:
|
|
|
3841
3870
|
if key in getattr(self, '_resize_input_keys', ()):
|
|
3842
3871
|
return False
|
|
3843
3872
|
tile = self._tiles.get(key)
|
|
3844
|
-
if tile is None or not tile.tex or tile.last_clean_frame < 0
|
|
3873
|
+
if (tile is None or not tile.tex or tile.last_clean_frame < 0
|
|
3874
|
+
or tile.pixel_scale != self._pixel_scale):
|
|
3845
3875
|
return False
|
|
3846
3876
|
if key in self._frozen_served:
|
|
3847
3877
|
return True
|
|
@@ -4149,6 +4179,7 @@ class TileCacheMasked:
|
|
|
4149
4179
|
bands.append((0, ah - ch, cw, ch - h))
|
|
4150
4180
|
if cw > w: # right band: screen cols [w, cw), full content height
|
|
4151
4181
|
bands.append((w, ah - ch, cw - w, ch))
|
|
4182
|
+
bands = [_pixel_rect(rect, t.pixel_scale) for rect in bands]
|
|
4152
4183
|
st = _GLState()
|
|
4153
4184
|
try:
|
|
4154
4185
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, t.fbo)
|
|
@@ -4257,6 +4288,7 @@ class TileCacheMasked:
|
|
|
4257
4288
|
if t is not None and getattr(t, 'content_stale', False):
|
|
4258
4289
|
self._scrub_stale_content(t, draw_state)
|
|
4259
4290
|
use_image = (t and has_area
|
|
4291
|
+
and t.pixel_scale == self._pixel_scale
|
|
4260
4292
|
and (t.size == (size[0], size[1]))
|
|
4261
4293
|
and (not self._is_dirty(t))
|
|
4262
4294
|
and (not draw_state.size_change))
|
|
@@ -4276,6 +4308,7 @@ class TileCacheMasked:
|
|
|
4276
4308
|
dragging = Melty.resize_gesture_live()
|
|
4277
4309
|
frozen = False
|
|
4278
4310
|
if (draw_state.freeze_resize and t is not None and has_area and dragging
|
|
4311
|
+
and t.pixel_scale == self._pixel_scale
|
|
4279
4312
|
and rkey not in getattr(self, '_resize_input_keys', ())
|
|
4280
4313
|
and (t.size != (size[0], size[1])
|
|
4281
4314
|
or Melty.resize_press_frame == Melty.frame_count
|
|
@@ -4600,7 +4633,8 @@ class TileCacheMasked:
|
|
|
4600
4633
|
and not imgui.is_mouse_down(1) and not imgui.is_mouse_down(2)
|
|
4601
4634
|
and not Melty.space_mouse_drag):
|
|
4602
4635
|
old_t = t
|
|
4603
|
-
t = _ensure_tile(t, ctx.size[0], ctx.size[1], frame_id=self._frame_id,
|
|
4636
|
+
t = _ensure_tile(t, ctx.size[0], ctx.size[1], frame_id=self._frame_id,
|
|
4637
|
+
draw_state=ctx.draw_state, pixel_scale=self._pixel_scale)
|
|
4604
4638
|
|
|
4605
4639
|
if t is not None and t is old_t:
|
|
4606
4640
|
# Within-bucket logical resize: the tile was updated in
|
|
@@ -4854,8 +4888,8 @@ class TileCacheMasked:
|
|
|
4854
4888
|
gl.glUseProgram(self._prog_mask_rounded)
|
|
4855
4889
|
gl.glUniform1f(self._loc_maskr_uRankNorm, rank_norm)
|
|
4856
4890
|
gl.glUniform2f(self._loc_maskr_uRectSize, float(iw), float(ih))
|
|
4857
|
-
gl.glUniform1f(self._loc_maskr_uCornerRadius, r.corner_radius)
|
|
4858
|
-
gl.glUniform1f(self._loc_maskr_uMargin, shadow_margin)
|
|
4891
|
+
gl.glUniform1f(self._loc_maskr_uCornerRadius, r.corner_radius * s_x)
|
|
4892
|
+
gl.glUniform1f(self._loc_maskr_uMargin, shadow_margin * s_x)
|
|
4859
4893
|
|
|
4860
4894
|
else:
|
|
4861
4895
|
gl.glUseProgram(self._prog_mask)
|
|
@@ -4920,15 +4954,15 @@ class TileCacheMasked:
|
|
|
4920
4954
|
gl.glBindTexture(gl.GL_TEXTURE_2D, t.mask_tex)
|
|
4921
4955
|
gl.glUniform1i(self._loc_texr_uTex, 0)
|
|
4922
4956
|
gl.glUniform2f(self._loc_texr_uRectSize, float(iw), float(ih))
|
|
4923
|
-
gl.glUniform1f(self._loc_texr_uCornerRadius, r.corner_radius)
|
|
4924
|
-
gl.glUniform1f(self._loc_texr_uMargin, shadow_margin)
|
|
4957
|
+
gl.glUniform1f(self._loc_texr_uCornerRadius, r.corner_radius * s_x)
|
|
4958
|
+
gl.glUniform1f(self._loc_texr_uMargin, shadow_margin * s_x)
|
|
4925
4959
|
|
|
4926
4960
|
else:
|
|
4927
4961
|
gl.glUseProgram(self._prog_mask_rounded)
|
|
4928
4962
|
gl.glUniform1f(self._loc_maskr_uRankNorm, float(r.layer) * INV_65535)
|
|
4929
4963
|
gl.glUniform2f(self._loc_maskr_uRectSize, float(iw), float(ih))
|
|
4930
|
-
gl.glUniform1f(self._loc_maskr_uCornerRadius, r.corner_radius)
|
|
4931
|
-
gl.glUniform1f(self._loc_maskr_uMargin, shadow_margin)
|
|
4964
|
+
gl.glUniform1f(self._loc_maskr_uCornerRadius, r.corner_radius * s_x)
|
|
4965
|
+
gl.glUniform1f(self._loc_maskr_uMargin, shadow_margin * s_x)
|
|
4932
4966
|
|
|
4933
4967
|
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3)
|
|
4934
4968
|
|
|
@@ -5122,8 +5156,8 @@ class TileCacheMasked:
|
|
|
5122
5156
|
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, p.tile.fbo)
|
|
5123
5157
|
# Write into the top-anchored logical subrect of the
|
|
5124
5158
|
# (possibly bucket-padded) texture.
|
|
5125
|
-
t_lw, t_lh =
|
|
5126
|
-
t_ah =
|
|
5159
|
+
t_lw, t_lh = _tile_pixel_size(p.tile)
|
|
5160
|
+
t_ah = _tile_pixel_size(p.tile, allocated=True)[1]
|
|
5127
5161
|
gl.glViewport(0, t_ah - t_lh, t_lw, t_lh)
|
|
5128
5162
|
|
|
5129
5163
|
# Pre-tint: multiplicative blending drifts stale pixels toward blue
|
|
@@ -5341,8 +5375,8 @@ class TileCacheMasked:
|
|
|
5341
5375
|
iw,
|
|
5342
5376
|
ih,
|
|
5343
5377
|
offset,
|
|
5344
|
-
r.corner_radius,
|
|
5345
|
-
r.draw_state.shadow_margin if r.draw_state is not None else 0.0,
|
|
5378
|
+
r.corner_radius * s_x,
|
|
5379
|
+
r.draw_state.shadow_margin * s_x if r.draw_state is not None else 0.0,
|
|
5346
5380
|
uv_rect=_tile_uv_rect(t_child),
|
|
5347
5381
|
)
|
|
5348
5382
|
else:
|
|
@@ -5357,9 +5391,9 @@ class TileCacheMasked:
|
|
|
5357
5391
|
gl.glUseProgram(self._prog_mask_rounded)
|
|
5358
5392
|
gl.glUniform1f(self._loc_maskr_uRankNorm, rank_norm)
|
|
5359
5393
|
gl.glUniform2f(self._loc_maskr_uRectSize, float(iw), float(ih))
|
|
5360
|
-
gl.glUniform1f(self._loc_maskr_uCornerRadius, r.corner_radius)
|
|
5394
|
+
gl.glUniform1f(self._loc_maskr_uCornerRadius, r.corner_radius * s_x)
|
|
5361
5395
|
shadow_margin = r.draw_state.shadow_margin if r.draw_state is not None else 0.0
|
|
5362
|
-
gl.glUniform1f(self._loc_maskr_uMargin, shadow_margin)
|
|
5396
|
+
gl.glUniform1f(self._loc_maskr_uMargin, shadow_margin * s_x)
|
|
5363
5397
|
|
|
5364
5398
|
else:
|
|
5365
5399
|
gl.glUseProgram(self._prog_mask)
|
|
@@ -5402,8 +5436,8 @@ class TileCacheMasked:
|
|
|
5402
5436
|
|
|
5403
5437
|
# Dst is the top-anchored logical subrect of the (possibly
|
|
5404
5438
|
# bucket-ized) mask texture, mirroring PASS 3's viewport.
|
|
5405
|
-
m_lw, m_lh =
|
|
5406
|
-
m_ah =
|
|
5439
|
+
m_lw, m_lh = _tile_pixel_size(p.tile)
|
|
5440
|
+
m_ah = _tile_pixel_size(p.tile, allocated=True)[1]
|
|
5407
5441
|
gl.glBlitFramebuffer(
|
|
5408
5442
|
int(x0),
|
|
5409
5443
|
int(y0),
|
|
@@ -5488,13 +5522,13 @@ class TileCacheMasked:
|
|
|
5488
5522
|
|
|
5489
5523
|
mask_stamps.append((t.mask_tex, (ix0, iy0, iw, ih),
|
|
5490
5524
|
(clip_ix0, clip_iy0, clip_iw, clip_ih),
|
|
5491
|
-
_tile_uv_rect(t), offset, r.corner_radius, shadow_margin))
|
|
5525
|
+
_tile_uv_rect(t), offset, r.corner_radius * s_x, shadow_margin * s_x))
|
|
5492
5526
|
else:
|
|
5493
5527
|
clip = (clip_ix0, clip_iy0, clip_iw, clip_ih)
|
|
5494
5528
|
shadow_margin = r.draw_state.shadow_margin if r.draw_state is not None else 0.0
|
|
5495
5529
|
mask_stamps.append((None, clip, clip, (1, 1, 0, 0),
|
|
5496
5530
|
float(depth_and_layer) / 65535.5,
|
|
5497
|
-
r.corner_radius, shadow_margin))
|
|
5531
|
+
r.corner_radius * s_x, shadow_margin * s_x))
|
|
5498
5532
|
|
|
5499
5533
|
if getattr(self, "_mask_batch", None) is None:
|
|
5500
5534
|
from meltygui.core.cache.mask_batch import MaskBatch
|
|
@@ -829,7 +829,8 @@ class SplitOverlayRenderer(WindowRenderer):
|
|
|
829
829
|
from OpenGL.GL import shaders
|
|
830
830
|
return shaders.compileProgram(
|
|
831
831
|
shaders.compileShader(self._STYLE_CONTEXT_VERT, gl.GL_VERTEX_SHADER),
|
|
832
|
-
shaders.compileShader(self._STYLE_CONTEXT_FRAG, gl.GL_FRAGMENT_SHADER)
|
|
832
|
+
shaders.compileShader(self._STYLE_CONTEXT_FRAG, gl.GL_FRAGMENT_SHADER),
|
|
833
|
+
validate=False) # The VAO is constructed below, after linking.
|
|
833
834
|
program = self._style_gl.get('style_context_program', build_program, gl.glDeleteProgram)
|
|
834
835
|
|
|
835
836
|
def build_vao():
|
|
@@ -880,7 +881,10 @@ class SplitOverlayRenderer(WindowRenderer):
|
|
|
880
881
|
"""Texture unit 1 = StyleContext for the whole command render."""
|
|
881
882
|
gl.glActiveTexture(gl.GL_TEXTURE1)
|
|
882
883
|
previous = int(gl.glGetIntegerv(gl.GL_TEXTURE_BINDING_2D))
|
|
883
|
-
|
|
884
|
+
# Even an unused sampler must name a complete texture on Apple's
|
|
885
|
+
# driver. The atlas is already resident; DynamicStyles=0 prevents
|
|
886
|
+
# sampling it as a background when no context texture is needed.
|
|
887
|
+
gl.glBindTexture(gl.GL_TEXTURE_2D, int(texture_id or self.io.fonts.texture_id))
|
|
884
888
|
gl.glActiveTexture(gl.GL_TEXTURE0)
|
|
885
889
|
return previous
|
|
886
890
|
|
|
@@ -396,6 +396,7 @@ def _capture_tile(name):
|
|
|
396
396
|
import OpenGL.GL as gl
|
|
397
397
|
from PIL import Image
|
|
398
398
|
from meltygui.core.melty import Melty
|
|
399
|
+
from meltygui.core.cache.tile_cache import _tile_pixel_size
|
|
399
400
|
|
|
400
401
|
mw = _find_window(name)
|
|
401
402
|
if mw is None:
|
|
@@ -409,7 +410,7 @@ def _capture_tile(name):
|
|
|
409
410
|
tile = tiles.get(tile_id)
|
|
410
411
|
if tile is None:
|
|
411
412
|
return None
|
|
412
|
-
w, h = tile
|
|
413
|
+
w, h = _tile_pixel_size(tile)
|
|
413
414
|
if not w or not h or tile.fbo in (None, -1):
|
|
414
415
|
return None
|
|
415
416
|
|
|
@@ -418,7 +419,7 @@ def _capture_tile(name):
|
|
|
418
419
|
gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1)
|
|
419
420
|
# Content is top-anchored in a possibly bottom-padded texture: the logical
|
|
420
421
|
# w x h pixels live in the texture rows [alloc_h - h, alloc_h), not at y=0.
|
|
421
|
-
alloc_h = (
|
|
422
|
+
alloc_h = _tile_pixel_size(tile, allocated=True)[1]
|
|
422
423
|
# Tiles hold LINEAR scRGB (fp16, hdr_color.py): read floats and sRGB-encode
|
|
423
424
|
# for the PNG the way the presentation pass does for the screen.
|
|
424
425
|
data = gl.glReadPixels(0, int(alloc_h) - int(h), int(w), int(h), gl.GL_RGBA, gl.GL_FLOAT)
|
meltygui/core/melty.py
CHANGED
|
@@ -613,10 +613,19 @@ class FileWatch:
|
|
|
613
613
|
return cls._self_write_text.get(resolved)
|
|
614
614
|
|
|
615
615
|
@classmethod
|
|
616
|
-
def
|
|
616
|
+
def stop(cls):
|
|
617
|
+
"""Join native file callbacks before the Python runtime goes away.
|
|
618
|
+
|
|
619
|
+
Standalone apps own their save policy and need this resource cleanup
|
|
620
|
+
without importing the code stack or flushing edits from a failed frame.
|
|
621
|
+
"""
|
|
622
|
+
cls.observer.stop()
|
|
617
623
|
if cls.observer.is_alive():
|
|
618
|
-
cls.observer.stop()
|
|
619
624
|
cls.observer.join()
|
|
625
|
+
|
|
626
|
+
@classmethod
|
|
627
|
+
def shutdown(cls):
|
|
628
|
+
cls.stop()
|
|
620
629
|
from meltygui.code.libcst_conversion import shutdown_jedi_pool
|
|
621
630
|
from meltygui.code.libcst_conversion import shutdown_symbol_index_daemon
|
|
622
631
|
shutdown_jedi_pool()
|
|
@@ -2300,8 +2309,13 @@ class Melty:
|
|
|
2300
2309
|
|
|
2301
2310
|
if cls.font_mgr is None:
|
|
2302
2311
|
return
|
|
2303
|
-
|
|
2304
|
-
|
|
2312
|
+
io = imgui.get_io()
|
|
2313
|
+
# Surfaces share one atlas. Keep the highest backing density seen so
|
|
2314
|
+
# windows on different displays cannot continually rebuild each other.
|
|
2315
|
+
pixel_scale = max(cls.font_mgr.pixel_scale, *io.display_fb_scale)
|
|
2316
|
+
io.font_global_scale = 1.0 / pixel_scale
|
|
2317
|
+
if cls.font_mgr.scale != cls.ui_scale or cls.font_mgr.pixel_scale != pixel_scale:
|
|
2318
|
+
if not cls.font_mgr.rebuild(cls.ui_scale, impl, pixel_scale=pixel_scale):
|
|
2305
2319
|
return
|
|
2306
2320
|
# Lazy fonts: bake whatever last-frame's get()s queued (fonts
|
|
2307
2321
|
# are seeded minimally at boot - see FontManager.prewarm). Same
|
|
@@ -2965,7 +2979,7 @@ class Melty:
|
|
|
2965
2979
|
cls.display_size = (fb_w, fb_h)
|
|
2966
2980
|
# Masks/tiles cover the WHOLE framebuffer (shadow margin included).
|
|
2967
2981
|
real_fb = cls.framebuffer_size or (fb_w, fb_h)
|
|
2968
|
-
cls.cache.mask_begin_frame((int(real_fb[0]), int(real_fb[1])))
|
|
2982
|
+
cls.cache.mask_begin_frame((int(real_fb[0]), int(real_fb[1])), imgui.get_io().display_fb_scale)
|
|
2969
2983
|
|
|
2970
2984
|
# Channel-split the foreground/overlay draw list the same way as the
|
|
2971
2985
|
# window draw list (max_depth channels) so per-window overlays can be
|
|
@@ -5058,8 +5072,8 @@ class Melty:
|
|
|
5058
5072
|
# Glow rects as light sources: hand the composite the low-res
|
|
5059
5073
|
# glow buffer (add_glow marks, stamped in finalize_captures
|
|
5060
5074
|
# PASS 6). glow_strength=0 skips the path in the shader, so an
|
|
5061
|
-
# empty/absent glow costs nothing
|
|
5062
|
-
#
|
|
5075
|
+
# empty/absent glow costs nothing. Still bind a complete
|
|
5076
|
+
# texture for Apple's driver, even when the branch is off.
|
|
5063
5077
|
_glow_tex = (Melty.cache.glow_tex
|
|
5064
5078
|
if Melty.cache.glow_active else None)
|
|
5065
5079
|
_glow_on = _glow_tex is not None and Toggles.glow
|
|
@@ -5083,7 +5097,7 @@ class Melty:
|
|
|
5083
5097
|
shadow_size=(float(composite_shadow_size[0]),
|
|
5084
5098
|
float(composite_shadow_size[1])),
|
|
5085
5099
|
depth_sharpness=float(Toggles.shadow_edge_sharpness),
|
|
5086
|
-
glow_map=_glow_tex if _glow_on else
|
|
5100
|
+
glow_map=_glow_tex if _glow_on else shadow_raw,
|
|
5087
5101
|
glow_strength=(float(Toggles.glow_strength)
|
|
5088
5102
|
if _glow_on else 0.0),
|
|
5089
5103
|
glow_shadow_cut=float(Toggles.glow_shadow_cut),
|
meltygui/core/runtime/app.py
CHANGED
|
@@ -141,7 +141,7 @@ def boot(app_id=None):
|
|
|
141
141
|
# without xdg-decoration), which also means the compositor's frame.
|
|
142
142
|
if backend == 'glfw' and os.environ.get('MELTY_LIBDECOR'):
|
|
143
143
|
sys._lsd_wayland_libdecor_disabled = False
|
|
144
|
-
from meltygui.core.windowing.glfw_utils import apply_wayland_frame_hint
|
|
144
|
+
from meltygui.core.windowing.glfw_utils import apply_wayland_frame_hint, apply_opengl_context_hints
|
|
145
145
|
apply_wayland_frame_hint()
|
|
146
146
|
if not glfw.init():
|
|
147
147
|
raise SystemExit('glfw.init failed')
|
|
@@ -149,9 +149,7 @@ def boot(app_id=None):
|
|
|
149
149
|
if backend == 'glfw':
|
|
150
150
|
warm_start.remember_glfw_library(cache)
|
|
151
151
|
glfw.window_hint(glfw.VISIBLE, False)
|
|
152
|
-
|
|
153
|
-
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
|
|
154
|
-
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
|
|
152
|
+
apply_opengl_context_hints()
|
|
155
153
|
owner = glfw.create_window(1, 1, 'meltygui owner', None, None)
|
|
156
154
|
if not owner:
|
|
157
155
|
raise SystemExit('glfw.create_window (owner) failed')
|
|
@@ -232,7 +230,10 @@ def _init_melty():
|
|
|
232
230
|
imgui.get_io().ini_file_name = None
|
|
233
231
|
imgui.get_io().display_size = (1.0, 1.0) # the atlas hinting pass frames on it
|
|
234
232
|
Melty.glfw_window = owner
|
|
235
|
-
|
|
233
|
+
window_size, framebuffer_size = glfw.get_window_size(owner), glfw.get_framebuffer_size(owner)
|
|
234
|
+
pixel_scale = max(1.0, *(pixels / max(1, points)
|
|
235
|
+
for pixels, points in zip(framebuffer_size, window_size)))
|
|
236
|
+
Melty.font_mgr = FontManager(imgui.get_io(), Melty.resolve_ui_scale(), pixel_scale=pixel_scale)
|
|
236
237
|
Melty.font_mgr.prewarm()
|
|
237
238
|
warm_start.cache_hinted_atlas(Melty.font_mgr, _state['cache'])
|
|
238
239
|
mark('fonts loaded')
|
|
@@ -596,6 +597,65 @@ def _unhook_main_return():
|
|
|
596
597
|
_state['hooked'] = False
|
|
597
598
|
|
|
598
599
|
|
|
600
|
+
def _process_events(timeout=None):
|
|
601
|
+
"""Dispatch OS events at a point where a refresh may safely draw.
|
|
602
|
+
|
|
603
|
+
Cocoa can stay inside this call for a whole native resize gesture.
|
|
604
|
+
Window creation, pending geometry writes and rendering are outside it.
|
|
605
|
+
"""
|
|
606
|
+
import meltygui.core.windowing.window_api as glfw
|
|
607
|
+
_state['processing_events'] = True
|
|
608
|
+
try:
|
|
609
|
+
if timeout is None:
|
|
610
|
+
glfw.poll_events()
|
|
611
|
+
else:
|
|
612
|
+
glfw.wait_events_timeout(timeout)
|
|
613
|
+
finally:
|
|
614
|
+
_state['processing_events'] = False
|
|
615
|
+
# Exceptions cannot cross GLFW's ctypes callback boundary. Raise them
|
|
616
|
+
# back in the app loop, whose finally skips saving a failed session.
|
|
617
|
+
error = _state.pop('refresh_error', None)
|
|
618
|
+
if error is not None:
|
|
619
|
+
raise error
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def refresh_surface(surface):
|
|
623
|
+
"""Present fresh content while Cocoa owns the native resize event loop.
|
|
624
|
+
|
|
625
|
+
Only draw between ordinary frames, on the render thread. A refresh
|
|
626
|
+
emitted by a resize or swap during this frame stays queued. Creation,
|
|
627
|
+
destruction and event polling remain in the outer loop.
|
|
628
|
+
"""
|
|
629
|
+
if (sys.platform != 'darwin' or not _state.get('processing_events')
|
|
630
|
+
or _state.get('refreshing') or _state['failed']
|
|
631
|
+
or surface.closed or surface.frames == 0):
|
|
632
|
+
return
|
|
633
|
+
import meltygui.core.windowing.window_api as glfw
|
|
634
|
+
import meltygui_imgui as imgui
|
|
635
|
+
from meltygui.core.melty import Melty
|
|
636
|
+
from meltygui.core.windowing import glfw_utils
|
|
637
|
+
if threading.get_ident() != glfw_utils._render_thread_id:
|
|
638
|
+
return
|
|
639
|
+
previous_gl, previous_imgui = glfw.get_current_context(), imgui.get_current_context()
|
|
640
|
+
_state['refreshing'] = True
|
|
641
|
+
try:
|
|
642
|
+
if surface.wants_frame():
|
|
643
|
+
Melty.app_tick += 1
|
|
644
|
+
surface.frame()
|
|
645
|
+
# Mark children against THIS tick before the outer loop advances
|
|
646
|
+
# again. Their actual native-window teardown stays outside GLFW.
|
|
647
|
+
_close_stale_children()
|
|
648
|
+
except Exception as error:
|
|
649
|
+
_state['failed'] = True
|
|
650
|
+
_state['refresh_error'] = error
|
|
651
|
+
finally:
|
|
652
|
+
# The Surface callback wrapper restores per-window Python state;
|
|
653
|
+
# rendering also switched GL/ImGui, which ordinary input never does.
|
|
654
|
+
glfw.make_context_current(previous_gl)
|
|
655
|
+
imgui.set_current_context(previous_imgui)
|
|
656
|
+
_state['refreshing'] = False
|
|
657
|
+
|
|
658
|
+
|
|
599
659
|
def run():
|
|
600
660
|
"""Open every registered window and run until the last one closes."""
|
|
601
661
|
if _state['ran']:
|
|
@@ -635,7 +695,7 @@ def run():
|
|
|
635
695
|
glfw_utils.request_render()
|
|
636
696
|
try:
|
|
637
697
|
while Surface.all:
|
|
638
|
-
|
|
698
|
+
_process_events()
|
|
639
699
|
_open_requested_children()
|
|
640
700
|
# A frame only on a request (request_render: the input layer's
|
|
641
701
|
# callbacks, Surface's focus/resize/close hooks, animations via
|
|
@@ -690,10 +750,14 @@ def run():
|
|
|
690
750
|
# requests nothing: poll while any exist. The idle wait is
|
|
691
751
|
# bounded so a signal (Ctrl+C) gets a turn: Python runs its
|
|
692
752
|
# handler between bytecodes, never inside a blocked OS call.
|
|
693
|
-
|
|
753
|
+
_process_events(1 / 60 if any(s.children for s in Surface.all) else 1.0)
|
|
694
754
|
finally:
|
|
695
755
|
_debug(f'{frames} frames rendered')
|
|
696
756
|
input_recording_core.stop_on_exit()
|
|
757
|
+
# Standalone apps do not run Melty.cleanup. Cocoa's file watcher
|
|
758
|
+
# must stop before interpreter teardown, even after a failed frame.
|
|
759
|
+
from meltygui.core.melty import FileWatch
|
|
760
|
+
FileWatch.stop()
|
|
697
761
|
if not _state['failed']:
|
|
698
762
|
_flush_pending_saves()
|
|
699
763
|
_save_session()
|
|
@@ -17,6 +17,7 @@ import shutil
|
|
|
17
17
|
import signal
|
|
18
18
|
import struct
|
|
19
19
|
import subprocess
|
|
20
|
+
import sys
|
|
20
21
|
import threading
|
|
21
22
|
import time
|
|
22
23
|
import uuid
|
|
@@ -89,7 +90,9 @@ def _inheritable_fds():
|
|
|
89
90
|
"""Every fd >= 3 that would survive an exec (not FD_CLOEXEC). Python-created fds are
|
|
90
91
|
CLOEXEC by default (PEP 446); this catches the C libraries' (CUDA / GL / inotify)."""
|
|
91
92
|
fds = []
|
|
92
|
-
|
|
93
|
+
# macOS exposes descriptors through devfs, without Linux's procfs mount.
|
|
94
|
+
directory = "/dev/fd" if sys.platform == "darwin" else "/proc/self/fd"
|
|
95
|
+
for name in os.listdir(directory):
|
|
93
96
|
fd = int(name)
|
|
94
97
|
if fd < 3:
|
|
95
98
|
continue
|
meltygui/core/styling/fonts.py
CHANGED
|
@@ -159,6 +159,10 @@ def detect_auto_scale(window=None) -> float:
|
|
|
159
159
|
by which video mode contains the window's center; falls back to the
|
|
160
160
|
primary monitor when the window is None or sits off every monitor
|
|
161
161
|
(mid-drag between screens). 1.0 on any glfw failure."""
|
|
162
|
+
# Cocoa window geometry is already in the user's chosen logical points.
|
|
163
|
+
# Retina backing resolution controls raster quality, not layout size.
|
|
164
|
+
if sys.platform == 'darwin':
|
|
165
|
+
return 1.0
|
|
162
166
|
try:
|
|
163
167
|
import meltygui.core.windowing.window_api as glfw
|
|
164
168
|
target = None
|
|
@@ -233,10 +237,10 @@ def _expand_ranges(ranges: Tuple[int, ...]):
|
|
|
233
237
|
class FontManager:
|
|
234
238
|
# no external change
|
|
235
239
|
"""Owns the imgui font atlas: one handle per Font enum entry, all baked
|
|
236
|
-
at `scale` x their authored
|
|
240
|
+
at `scale` x their authored point size and `pixel_scale` texels per point.
|
|
237
241
|
|
|
238
242
|
The scale is baked into the RASTERIZED size rather than applied as a
|
|
239
|
-
draw-time
|
|
243
|
+
draw-time UI zoom so glyphs stay sharp — a
|
|
240
244
|
scaled atlas is resampled text, which is exactly the soft/aliased look
|
|
241
245
|
the whole-interface zoom had. The cost is that changing the scale means
|
|
242
246
|
re-rasterizing every font (see `rebuild`), so it happens only when the
|
|
@@ -247,9 +251,13 @@ class FontManager:
|
|
|
247
251
|
# can make imgui's atlas build fail outright.
|
|
248
252
|
MIN_SIZE = 4.0
|
|
249
253
|
|
|
250
|
-
|
|
254
|
+
# Existing managers pick up the backing density between frames on hotswap.
|
|
255
|
+
pixel_scale = 1.0
|
|
256
|
+
|
|
257
|
+
def __init__(self, io, scale: float = 1.0, pixel_scale: float = 1.0):
|
|
251
258
|
self.io = io
|
|
252
259
|
self.scale = float(scale)
|
|
260
|
+
self.pixel_scale = float(pixel_scale)
|
|
253
261
|
self._handles: dict = {}
|
|
254
262
|
# imgui keeps a POINTER to each font's glyph ranges until the atlas is
|
|
255
263
|
# built, and a GlyphRanges frees its array when collected: the bake
|
|
@@ -269,7 +277,7 @@ class FontManager:
|
|
|
269
277
|
# the per-frame get() of a steady style allocates nothing.
|
|
270
278
|
self._variant_memo = {}
|
|
271
279
|
|
|
272
|
-
def rebuild(self, scale: float, impl=None) -> bool:
|
|
280
|
+
def rebuild(self, scale: float, impl=None, *, pixel_scale=None) -> bool:
|
|
273
281
|
"""Re-bake every LOADED font at `scale` and hand the new atlas to the
|
|
274
282
|
renderer. No-op (False) when the scale is unchanged.
|
|
275
283
|
|
|
@@ -279,9 +287,11 @@ class FontManager:
|
|
|
279
287
|
cached one (Melty.large_font, LSDStudio.fa_font) have to re-`get` it.
|
|
280
288
|
"""
|
|
281
289
|
scale = float(scale)
|
|
282
|
-
|
|
290
|
+
pixel_scale = self.pixel_scale if pixel_scale is None else float(pixel_scale)
|
|
291
|
+
if scale == self.scale and pixel_scale == self.pixel_scale and self._handles:
|
|
283
292
|
return False
|
|
284
293
|
self.scale = scale
|
|
294
|
+
self.pixel_scale = pixel_scale
|
|
285
295
|
self._loaded |= self._pending
|
|
286
296
|
self._pending.clear()
|
|
287
297
|
self._bake(self._loaded)
|
|
@@ -355,6 +365,10 @@ class FontManager:
|
|
|
355
365
|
self._variant_last_used = {f: t for f, t in self._variant_last_used.items() if f in include}
|
|
356
366
|
self._handles.clear()
|
|
357
367
|
self.io.fonts.clear()
|
|
368
|
+
# Rasterize at physical resolution, then express glyph geometry in
|
|
369
|
+
# window points. This cancels the framebuffer scale, never upscales
|
|
370
|
+
# a low-resolution atlas. Every Surface applies the same reciprocal.
|
|
371
|
+
self.io.font_global_scale = 1.0 / self.pixel_scale
|
|
358
372
|
self._glyph_ranges.clear()
|
|
359
373
|
variants = sorted((f for f in include if isinstance(f, FontSpec)),
|
|
360
374
|
key=lambda f: (f.path, f.size, f.weight))
|
|
@@ -364,19 +378,19 @@ class FontManager:
|
|
|
364
378
|
if font not in include:
|
|
365
379
|
continue
|
|
366
380
|
spec = font if isinstance(font, FontSpec) else font.value
|
|
367
|
-
size = max(self.MIN_SIZE, spec.size * self.scale)
|
|
381
|
+
size = max(self.MIN_SIZE, spec.size * self.scale) * self.pixel_scale
|
|
368
382
|
if spec.merge:
|
|
369
383
|
merge_cfg = dict(
|
|
370
384
|
merge_mode=True,
|
|
371
|
-
glyph_extra_spacing_x=spec.extra_spacing * self.scale,
|
|
372
|
-
glyph_extra_spacing_y=spec.extra_spacing * self.scale,
|
|
385
|
+
glyph_extra_spacing_x=spec.extra_spacing * self.scale * self.pixel_scale,
|
|
386
|
+
glyph_extra_spacing_y=spec.extra_spacing * self.scale * self.pixel_scale,
|
|
373
387
|
)
|
|
374
388
|
# Merged icon fonts cover wide ranges, so they dominate the
|
|
375
389
|
# atlas - spread them out as the scale grows. Untouched at
|
|
376
390
|
# scale <= 1 because the authored atlas is unchanged: imgui's
|
|
377
391
|
# own defaults are (h=3, v=1), and spelling them out is
|
|
378
392
|
# what keeps v from silently becoming 3 here.
|
|
379
|
-
if self.scale > 1.0:
|
|
393
|
+
if self.scale * self.pixel_scale > 1.0:
|
|
380
394
|
merge_cfg["oversample_h"] = self._oversample(spec)
|
|
381
395
|
merge_cfg["oversample_v"] = 1
|
|
382
396
|
cfg = imgui.FontConfig(**merge_cfg)
|
|
@@ -521,10 +535,13 @@ class FontManager:
|
|
|
521
535
|
relative to the add_text pen, texel rect in the atlas. Codepoints
|
|
522
536
|
that emit no quad (spaces) are absent; imgui's fallback glyph shows
|
|
523
537
|
up as repeated rects, deduped by the caller."""
|
|
524
|
-
|
|
525
|
-
|
|
538
|
+
# Probe the active context (a Surface can share the owner's atlas).
|
|
539
|
+
# Hinting needs physical glyph metrics even when the UI uses points.
|
|
540
|
+
io = imgui.get_io()
|
|
541
|
+
saved = (io.display_size, io.delta_time, io.font_global_scale)
|
|
526
542
|
io.display_size = (4096.0, 4096.0)
|
|
527
543
|
io.delta_time = 1.0 / 60.0
|
|
544
|
+
io.font_global_scale = 1.0
|
|
528
545
|
out = {}
|
|
529
546
|
try:
|
|
530
547
|
imgui.new_frame()
|
|
@@ -556,7 +573,7 @@ class FontManager:
|
|
|
556
573
|
int(round(v0[2] * tex_w)), int(round(v0[3] * tex_h)),
|
|
557
574
|
int(round(v2[2] * tex_w)), int(round(v2[3] * tex_h)))
|
|
558
575
|
finally:
|
|
559
|
-
io.display_size, io.delta_time = saved
|
|
576
|
+
io.display_size, io.delta_time, io.font_global_scale = saved
|
|
560
577
|
return out
|
|
561
578
|
|
|
562
579
|
def hint_atlas(self, width: int, height: int, pixels: bytes):
|
|
@@ -583,7 +600,7 @@ class FontManager:
|
|
|
583
600
|
handle = self._handles.get(font)
|
|
584
601
|
if spec.merge or not spec.hint or handle is None:
|
|
585
602
|
continue
|
|
586
|
-
size = max(self.MIN_SIZE, spec.size * self.scale)
|
|
603
|
+
size = max(self.MIN_SIZE, spec.size * self.scale) * self.pixel_scale
|
|
587
604
|
if size > HINT_MAX_SIZE:
|
|
588
605
|
continue
|
|
589
606
|
cps = _expand_ranges(spec.glyph_ranges or _UI_RANGE)
|
|
@@ -636,4 +653,4 @@ class FontManager:
|
|
|
636
653
|
end_time = time.time()
|
|
637
654
|
print(f"FontManager: hinting pass took {end_time - start_time:.3f} s")
|
|
638
655
|
print(f"wall time {end_time}")
|
|
639
|
-
return atlas.tobytes()
|
|
656
|
+
return atlas.tobytes()
|
|
@@ -31,6 +31,19 @@ UI_SCALE_MIN = 0.5
|
|
|
31
31
|
UI_SCALE_MAX = 3.0
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def apply_opengl_context_hints():
|
|
35
|
+
"""Use the same core context for the owner and every shared window.
|
|
36
|
+
|
|
37
|
+
Apple's OpenGL driver stops at 4.1 and requires a forward-compatible
|
|
38
|
+
core profile. Other backends retain 4.3 for the compute-based tools.
|
|
39
|
+
"""
|
|
40
|
+
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 4)
|
|
41
|
+
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 1 if sys.platform == 'darwin' else 3)
|
|
42
|
+
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
|
|
43
|
+
if sys.platform == 'darwin':
|
|
44
|
+
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
|
|
45
|
+
|
|
46
|
+
|
|
34
47
|
def clamp_ui_scale(value) -> float:
|
|
35
48
|
"""Sanitize a candidate ui scale: a float within
|
|
36
49
|
[UI_SCALE_MIN, UI_SCALE_MAX] passes through; anything crazy — out of
|
|
@@ -1426,4 +1439,4 @@ def request_render(for_frames: int | None = None):
|
|
|
1426
1439
|
_requested_surfaces.add(scope)
|
|
1427
1440
|
else:
|
|
1428
1441
|
_all_surfaces_generation += 1
|
|
1429
|
-
_wake_render_loop()
|
|
1442
|
+
_wake_render_loop()
|
|
@@ -10,7 +10,7 @@ Parent-relative nested positions must be accounted for when applying results;
|
|
|
10
10
|
driving-edge phases below are an implementation strategy, not product rules.
|
|
11
11
|
Hand-drag replay restores displaced geometry when the gesture reverses.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
An available adjustment path AND geometry determine whether the native frame can move.
|
|
14
14
|
The GLFW and Wayland adapters must honor those capabilities. See
|
|
15
15
|
``docs/WINDOW_COLLISION_COLUMNS.md`` for the authoritative behavior contract.
|
|
16
16
|
"""
|
|
@@ -79,6 +79,7 @@ _STATE.setdefault("learned", {"x": [None, None], "y": [None, None]})
|
|
|
79
79
|
_STATE.setdefault("learned_area", None)
|
|
80
80
|
_STATE.setdefault("pin_rebases", {})
|
|
81
81
|
_STATE.setdefault("move_requests", {"x": [], "y": []})
|
|
82
|
+
_STATE.setdefault("pending_surface_collision", False)
|
|
82
83
|
|
|
83
84
|
|
|
84
85
|
def _trace(msg):
|
|
@@ -111,6 +112,7 @@ def reset(reason="studio start"):
|
|
|
111
112
|
_STATE["unapplied_far"] = [0.0, 0.0]
|
|
112
113
|
_STATE["pin_rebases"] = {}
|
|
113
114
|
_STATE["move_requests"] = {"x": [], "y": []}
|
|
115
|
+
_STATE["pending_surface_collision"] = False
|
|
114
116
|
_STATE["window_id"] = None
|
|
115
117
|
_STATE["feed_far"] = [None, None]
|
|
116
118
|
_STATE["generation"] += 1
|
|
@@ -125,7 +127,7 @@ def _enabled():
|
|
|
125
127
|
|
|
126
128
|
def mode():
|
|
127
129
|
""""feed" (Wayland, the extension's position), "x11" (glfw's position)
|
|
128
|
-
or "walls" (no
|
|
130
|
+
or "walls" (no reliable native adjustment: the OS edges are immovable)."""
|
|
129
131
|
return _STATE["mode"]
|
|
130
132
|
|
|
131
133
|
|
|
@@ -173,11 +175,13 @@ def _observe():
|
|
|
173
175
|
"""((x, y), (work_x, work_y, work_w, work_h), mode, (far_x, far_y),
|
|
174
176
|
window_id) of the content rect on screen — the far edges from the
|
|
175
177
|
SAME source as the position (the feed's own width / height; glfw's on
|
|
176
|
-
X11) — or None
|
|
178
|
+
X11) — or None without both geometry and a native adjustment path."""
|
|
177
179
|
import meltygui.core.windowing.titlebar as titlebar
|
|
180
|
+
window = titlebar._studio_window()
|
|
181
|
+
if not titlebar.can_adjust_window_edges(window):
|
|
182
|
+
return None
|
|
178
183
|
if titlebar._on_wayland():
|
|
179
184
|
import meltygui.core.windowing.geometry_feed as geometry_feed
|
|
180
|
-
geometry_feed.ensure_started() # a hotswap, not a restart (or a backend switch): start it here
|
|
181
185
|
# the Hyprland backend reports the SURFACE: shrink by the shadow
|
|
182
186
|
# inset to the content (a no-op on the GNOME feed's geometry rect)
|
|
183
187
|
rect, area = geometry_feed.frame_rect(inset=titlebar.window_inset()), geometry_feed.resize_workarea()
|
|
@@ -186,9 +190,6 @@ def _observe():
|
|
|
186
190
|
frame = geometry_feed._current_frame() or {} # the ACTIVE surface's window (extension.py)
|
|
187
191
|
return ((float(rect[0]), float(rect[1])), tuple(float(v) for v in area), "feed",
|
|
188
192
|
(float(rect[0] + rect[2]), float(rect[1] + rect[3])), frame.get("id"))
|
|
189
|
-
window = titlebar._studio_window()
|
|
190
|
-
if window is None:
|
|
191
|
-
return None
|
|
192
193
|
try:
|
|
193
194
|
x, y = glfw.get_window_pos(window)
|
|
194
195
|
w, h = glfw.get_window_size(window)
|
|
@@ -503,6 +504,11 @@ def _rebase(ds, axis, delta):
|
|
|
503
504
|
def _set_mode(new_mode):
|
|
504
505
|
if _STATE["mode"] != new_mode:
|
|
505
506
|
_STATE["mode"] = new_mode
|
|
507
|
+
if new_mode == "walls":
|
|
508
|
+
# The old coordinate space and requests no longer describe
|
|
509
|
+
# geometry we can apply. Never replay their pending rebases.
|
|
510
|
+
reset("native adjustments unavailable")
|
|
511
|
+
return
|
|
506
512
|
_STATE["generation"] += 1 # every root forgets where it saw the OS edges
|
|
507
513
|
_STATE["gestures"] = {}
|
|
508
514
|
_STATE["expected"] = [None, None]
|
|
@@ -555,7 +561,8 @@ def begin_frame():
|
|
|
555
561
|
for axis, i in _AXIS.items():
|
|
556
562
|
near, far = _STATE["edges"][axis]
|
|
557
563
|
near[axis], far[axis] = 0.0, size[i]
|
|
558
|
-
_STATE["os_seen"][i]
|
|
564
|
+
if _STATE["os_seen"][i] is None:
|
|
565
|
+
_STATE["os_seen"][i] = (0.0, size[i])
|
|
559
566
|
return
|
|
560
567
|
(pos, area, new_mode, feed_far, window_id) = observed
|
|
561
568
|
_set_mode(new_mode)
|
|
@@ -824,7 +831,7 @@ def queue_drag(axis, index, inc):
|
|
|
824
831
|
"""A cursor-driven drag of the OS window's own frame edge (``index`` 0
|
|
825
832
|
= near / left / top, 1 = far) by ``inc`` px — the background right-drag
|
|
826
833
|
(titlebar). Solved by the first root pass of the frame, else by flush."""
|
|
827
|
-
if inc:
|
|
834
|
+
if inc and available():
|
|
828
835
|
_STATE["pending"][axis].append((int(index), float(inc)))
|
|
829
836
|
|
|
830
837
|
|
|
@@ -1525,7 +1532,7 @@ def flush():
|
|
|
1525
1532
|
in one commit). Returns the requested content size or None."""
|
|
1526
1533
|
import meltygui.core.windowing.titlebar as titlebar
|
|
1527
1534
|
from meltygui.core.melty import Melty
|
|
1528
|
-
if not _enabled():
|
|
1535
|
+
if not _enabled() or not available():
|
|
1529
1536
|
return None
|
|
1530
1537
|
window = titlebar._studio_window()
|
|
1531
1538
|
display = Melty.display_size
|
|
@@ -1577,9 +1584,10 @@ def flush():
|
|
|
1577
1584
|
glfw.set_window_pos(window, x + offset[0], y + offset[1])
|
|
1578
1585
|
except Exception:
|
|
1579
1586
|
pass
|
|
1580
|
-
titlebar.request_surface_size(window, *surface)
|
|
1587
|
+
titlebar.request_surface_size(window, *surface, collision=True)
|
|
1581
1588
|
else:
|
|
1582
|
-
titlebar.request_surface_size(window, *surface, offset=tuple(offset) if any(offset) else None
|
|
1589
|
+
titlebar.request_surface_size(window, *surface, offset=tuple(offset) if any(offset) else None,
|
|
1590
|
+
collision=True)
|
|
1583
1591
|
_trace(f"flush: content {size} offset {offset} → surface {surface}")
|
|
1584
1592
|
return tuple(size)
|
|
1585
1593
|
|
|
@@ -162,9 +162,7 @@ class Surface:
|
|
|
162
162
|
|
|
163
163
|
glfw.default_window_hints()
|
|
164
164
|
glfw.window_hint(glfw.VISIBLE, True)
|
|
165
|
-
|
|
166
|
-
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
|
|
167
|
-
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
|
|
165
|
+
glfw_utils.apply_opengl_context_hints()
|
|
168
166
|
glfw.window_hint(glfw.TRANSPARENT_FRAMEBUFFER, transparent)
|
|
169
167
|
glfw.window_hint(glfw.ALPHA_BITS, 8 if transparent else 0)
|
|
170
168
|
glfw.window_hint(glfw.DECORATED, glfw.FALSE if self.chrome else glfw.TRUE)
|
|
@@ -215,6 +213,7 @@ class Surface:
|
|
|
215
213
|
# (the owner's is), so select ours before touching its io.
|
|
216
214
|
imgui.set_current_context(self.ctx)
|
|
217
215
|
imgui.get_io().ini_file_name = None
|
|
216
|
+
imgui.get_io().font_global_scale = owner_io.font_global_scale
|
|
218
217
|
# The renderer uploads the atlas in its constructor, BEFORE it stamps
|
|
219
218
|
# display_size: the FreeType hinting pass (fonts.hint_atlas) frames on
|
|
220
219
|
# the current context and dies on the (-1, -1) default.
|
|
@@ -343,9 +342,14 @@ class Surface:
|
|
|
343
342
|
hook(glfw.set_window_focus_callback, self._on_focus)
|
|
344
343
|
hook(glfw.set_framebuffer_size_callback, self._on_framebuffer_size)
|
|
345
344
|
hook(glfw.set_window_close_callback, lambda *_: request_render())
|
|
346
|
-
#
|
|
347
|
-
#
|
|
348
|
-
hook(glfw.set_window_refresh_callback,
|
|
345
|
+
# Cocoa delivers refreshes inside its modal live-resize loop. Waiting
|
|
346
|
+
# for poll_events to return would stretch the last presented buffer.
|
|
347
|
+
hook(glfw.set_window_refresh_callback, self._on_refresh)
|
|
348
|
+
|
|
349
|
+
def _on_refresh(self):
|
|
350
|
+
request_render()
|
|
351
|
+
from meltygui.core.runtime.app import refresh_surface
|
|
352
|
+
refresh_surface(self)
|
|
349
353
|
|
|
350
354
|
def _on_focus(self, focused):
|
|
351
355
|
request_render()
|
|
@@ -445,10 +449,12 @@ class Surface:
|
|
|
445
449
|
# edits deferred while a picker/slider held the pointer here too.
|
|
446
450
|
from meltygui.core.rendering.parameter_core import flush_deferred_writes
|
|
447
451
|
flush_deferred_writes()
|
|
452
|
+
# Collision bounds belong to the surface, including when the OS
|
|
453
|
+
# supplies its title bar and cannot accept collision-driven moves.
|
|
454
|
+
os_frame.begin_frame()
|
|
448
455
|
if self.chrome:
|
|
449
|
-
os_frame.begin_frame()
|
|
450
456
|
titlebar.poll_os_window_drag()
|
|
451
|
-
|
|
457
|
+
os_frame.solve()
|
|
452
458
|
imgui.set_cursor_screen_pos((0, 0))
|
|
453
459
|
imgui.set_item_allow_overlap()
|
|
454
460
|
draw_list = imgui.get_window_draw_list()
|
|
@@ -466,7 +472,7 @@ class Surface:
|
|
|
466
472
|
imgui.set_cursor_screen_pos((0, top))
|
|
467
473
|
# The root fills the OS MODEL's size (os_frame.content_size): equal
|
|
468
474
|
# to the display except while our own resize is still landing.
|
|
469
|
-
fill_w, fill_h = os_frame.content_size((disp_w, disp_h))
|
|
475
|
+
fill_w, fill_h = os_frame.content_size((disp_w, disp_h))
|
|
470
476
|
Melty.root_fill = (float(fill_w), float(fill_h) - top, float(top)) # (w, h below the chrome, top y)
|
|
471
477
|
Melty.root_fill_used = False
|
|
472
478
|
# The body runs INSIDE the ground, as the studio's windows run inside
|
|
@@ -489,8 +495,7 @@ class Surface:
|
|
|
489
495
|
Melty.end_frame()
|
|
490
496
|
if self.request is not None:
|
|
491
497
|
Melty.finish_surface_root(self.request, self)
|
|
492
|
-
|
|
493
|
-
os_frame.flush()
|
|
498
|
+
os_frame.flush()
|
|
494
499
|
Melty.window_stack.pop()
|
|
495
500
|
draw_list.channels_merge()
|
|
496
501
|
Melty.channels_split = False
|
|
@@ -238,6 +238,35 @@ def backend_supported():
|
|
|
238
238
|
return sys.platform.startswith("linux")
|
|
239
239
|
|
|
240
240
|
|
|
241
|
+
def can_adjust_window_edges(window):
|
|
242
|
+
"""Can collision pushes control this native frame's position AND size?
|
|
243
|
+
|
|
244
|
+
Reading a position or starting a compositor-owned interactive resize is
|
|
245
|
+
insufficient. Only the implemented X11 and Wayland control paths opt in;
|
|
246
|
+
Cocoa and unknown backends keep the observed content bounds fixed.
|
|
247
|
+
"""
|
|
248
|
+
if window is None or not sys.platform.startswith("linux"):
|
|
249
|
+
return False
|
|
250
|
+
try:
|
|
251
|
+
if not glfw.get_window_attrib(window, glfw.RESIZABLE) or _fullscreen(window):
|
|
252
|
+
return False
|
|
253
|
+
if not _on_wayland():
|
|
254
|
+
return (glfw.get_platform() == glfw.PLATFORM_X11
|
|
255
|
+
and not bool(glfw.get_window_attrib(window, glfw.MAXIMIZED)))
|
|
256
|
+
import meltygui.core.windowing.geometry_feed as geometry_feed
|
|
257
|
+
geometry_feed.ensure_started()
|
|
258
|
+
frame = geometry_feed._current_frame()
|
|
259
|
+
if (not geometry_feed._STATE['available'] or not frame
|
|
260
|
+
or frame.get('maximized') or frame.get('fullscreen')):
|
|
261
|
+
return False
|
|
262
|
+
backend = geometry_feed.backend()
|
|
263
|
+
if backend == 'hyprland':
|
|
264
|
+
return bool(frame.get('floating') and frame.get('address'))
|
|
265
|
+
return backend == 'gnome' and wayland_move.offset_available()
|
|
266
|
+
except Exception:
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
|
|
241
270
|
def titlebar_enabled():
|
|
242
271
|
"""X11: Toggles.Melty.enhanced_titlebar (it replaces the WM frame, an
|
|
243
272
|
opt-in). Wayland: whenever the native frame is up — GLFW's fallback frame
|
|
@@ -1022,7 +1051,8 @@ def draw_titlebar(window):
|
|
|
1022
1051
|
# (the drag events themselves are consumed by poll_os_window_drag at
|
|
1023
1052
|
# the frame's START - before the root windows solve - so the OS edge
|
|
1024
1053
|
# moves in the same frame the hand did)
|
|
1025
|
-
|
|
1054
|
+
from meltygui.core.windowing import os_frame
|
|
1055
|
+
if _rdrag is not None and os_frame.available():
|
|
1026
1056
|
mouse_cursor.request(_EDGE_CURSOR[_SIZE_TOPLEFT if _rdrag["top_left"] else _SIZE_BOTTOMRIGHT])
|
|
1027
1057
|
|
|
1028
1058
|
# The buttons themselves are painted earlier in the frame, in the main
|
|
@@ -1356,7 +1386,7 @@ _pending_surface_wait = globals().get("_pending_surface_wait") or 0
|
|
|
1356
1386
|
PENDING_SURFACE_WAIT_FRAMES = 300
|
|
1357
1387
|
|
|
1358
1388
|
|
|
1359
|
-
def request_surface_size(window, width, height, offset=None, fit=False):
|
|
1389
|
+
def request_surface_size(window, width, height, offset=None, fit=False, collision=False):
|
|
1360
1390
|
"""Queue an app-side resize (the right-drag) for the top of the NEXT
|
|
1361
1391
|
frame (apply_pending_surface_size, before process_inputs). Applied
|
|
1362
1392
|
mid-frame it committed a new buffer size and geometry with content laid
|
|
@@ -1369,11 +1399,15 @@ def request_surface_size(window, width, height, offset=None, fit=False):
|
|
|
1369
1399
|
the CONTENT inside the work area at the new size — the compositor
|
|
1370
1400
|
centred the window at its INITIAL size and the box grows anchored at
|
|
1371
1401
|
its top-left, so a restored studio ran off the bottom and right of
|
|
1372
|
-
the screen (and the OS-edge walls then hold it there, 09-09).
|
|
1402
|
+
the screen (and the OS-edge walls then hold it there, 09-09).
|
|
1403
|
+
``collision`` marks a solver request, cancelled if native adjustment
|
|
1404
|
+
becomes unavailable before it is applied. Explicit sizes still work."""
|
|
1373
1405
|
global _pending_surface_size, _pending_surface_offset, _pending_surface_fit
|
|
1406
|
+
from meltygui.core.windowing import os_frame
|
|
1374
1407
|
_pending_surface_size = (int(width), int(height))
|
|
1375
1408
|
_pending_surface_offset = tuple(int(v) for v in offset) if offset else None
|
|
1376
1409
|
_pending_surface_fit = bool(fit)
|
|
1410
|
+
os_frame._STATE['pending_surface_collision'] = bool(collision)
|
|
1377
1411
|
|
|
1378
1412
|
|
|
1379
1413
|
def fit_offset(rect, size, area, inset):
|
|
@@ -1400,6 +1434,17 @@ def apply_pending_surface_size(window):
|
|
|
1400
1434
|
import meltygui.core.windowing.wayland_move as wayland_move
|
|
1401
1435
|
import meltygui.core.windowing.os_frame as os_frame
|
|
1402
1436
|
wayland_move.clear_surface_offset() # last frame's offset is spent
|
|
1437
|
+
collision = os_frame._STATE.get('pending_surface_collision', False)
|
|
1438
|
+
if not can_adjust_window_edges(window):
|
|
1439
|
+
# Discard speculative compensation BEFORE it moves any child. A
|
|
1440
|
+
# readable but uncontrollable frame must never rebase local content.
|
|
1441
|
+
os_frame._set_mode('walls')
|
|
1442
|
+
if collision:
|
|
1443
|
+
_pending_surface_size = _pending_surface_offset = None
|
|
1444
|
+
_pending_surface_fit = False
|
|
1445
|
+
_pending_surface_wait = 0
|
|
1446
|
+
_frame_surface_offset = None
|
|
1447
|
+
os_frame._STATE['pending_surface_collision'] = False
|
|
1403
1448
|
# The roots' passes re-base with the OS near edge's motion lands HERE,
|
|
1404
1449
|
# with the move it compensates (os_frame: the solve booked it).
|
|
1405
1450
|
os_frame.apply_rebase()
|
|
@@ -1440,6 +1485,7 @@ def apply_pending_surface_size(window):
|
|
|
1440
1485
|
_pending_surface_fit = False
|
|
1441
1486
|
_pending_surface_size = None
|
|
1442
1487
|
_pending_surface_offset = None
|
|
1488
|
+
os_frame._STATE['pending_surface_collision'] = False
|
|
1443
1489
|
from meltygui.core.runtime.toggles import Toggles
|
|
1444
1490
|
if Toggles.Melty.push_os_window_edges_trace:
|
|
1445
1491
|
try:
|
|
@@ -59,7 +59,10 @@ class ProgramCompiler:
|
|
|
59
59
|
try:
|
|
60
60
|
vertex_shader = shaders.compileShader(vertex_src, GL.GL_VERTEX_SHADER)
|
|
61
61
|
fragment_shader = shaders.compileShader(fragment_src, GL.GL_FRAGMENT_SHADER)
|
|
62
|
-
|
|
62
|
+
# Validation examines the current VAO/texture bindings, which the
|
|
63
|
+
# executor installs only when drawing. Apple's driver correctly
|
|
64
|
+
# rejects that incomplete state here; compile/link checks still run.
|
|
65
|
+
program_id = shaders.compileProgram(vertex_shader, fragment_shader, validate=False)
|
|
63
66
|
except shaders.ShaderCompilationError as e:
|
|
64
67
|
raise ShaderCompilationError(f"Failed to compile shader '{shader.name}': {e}")
|
|
65
68
|
|
|
@@ -92,8 +95,11 @@ class ProgramCompiler:
|
|
|
92
95
|
parts = [GLSL_VERSION]
|
|
93
96
|
|
|
94
97
|
# Add input declarations
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
# FilterExecutor supplies position at 0 and texcoord at 1. Linkers
|
|
99
|
+
# may assign different locations unless the source pins that layout
|
|
100
|
+
# (Apple otherwise feeds UVs to position, drawing only one quarter).
|
|
101
|
+
for location, (name, gl_type) in enumerate(shader_type.vertex_in.items()):
|
|
102
|
+
parts.append(f"layout(location = {location}) in {gl_type.value} {name};")
|
|
97
103
|
|
|
98
104
|
# Add output declarations (varyings)
|
|
99
105
|
for name, gl_type in shader_type.vertex_out.items():
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: meltygui
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Immediate-mode Python UI, live inspection, HDR and tensor visualization
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
8
9
|
Classifier: Operating System :: Microsoft :: Windows
|
|
9
10
|
Classifier: Operating System :: POSIX :: Linux
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -60,11 +61,13 @@ MeltyGUI is capable of rendering tensors exceeding 64GB in size. The primary cha
|
|
|
60
61
|
MeltyGUI is not yet published to PyPI. For now, install from a checkout using
|
|
61
62
|
[the setup guide](docs/DEVELOPMENT.md#setup).
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
Linux x86-64 uses Python 3.11, 3.12 or 3.13 and OpenGL 4.3. The UI and editor
|
|
65
|
+
also run on Apple Silicon macOS with Python 3.12, OpenGL 4.1 and Retina
|
|
66
|
+
rendering. CUDA tensor rendering requires PyTorch, an
|
|
65
67
|
NVIDIA GPU, a CUDA toolkit, and MeltyGUI's GL-enabled CUDA binding. The native
|
|
66
68
|
bindings install as prebuilt wheels on those targets; elsewhere installers
|
|
67
69
|
compile them, see [when no wheel matches](docs/SUPPORT_WHEELS.md#when-no-wheel-matches).
|
|
70
|
+
CUDA rendering is unavailable on macOS.
|
|
68
71
|
|
|
69
72
|
## Visualize a tensor
|
|
70
73
|
|
|
@@ -67,7 +67,7 @@ meltygui/core/README.md,sha256=_4lJ5-kykF6BBi3ZTfPRlmAIptDfSeygGkT4wtFCDhQ,6158
|
|
|
67
67
|
meltygui/core/__init__.py,sha256=NQnuSYYGxJhquq6jFPaDzG0m66heXxXRbfO8Zoq5BqM,31
|
|
68
68
|
meltygui/core/core_render.py,sha256=Ks_f_RG87moGSDDU0XiwvQsB87-SBb-ffxU1KYjoPh4,360878
|
|
69
69
|
meltygui/core/definition_hotswap.py,sha256=uyeQdmo0Fsc8sxi1YWQ_xbnK2PFgb-TlwLtPG3ftX9c,13163
|
|
70
|
-
meltygui/core/melty.py,sha256=
|
|
70
|
+
meltygui/core/melty.py,sha256=oQ6sEh_3bKBK1qqxnWfTpR4rxhBDFn8mMPbk-YLvr3M,339552
|
|
71
71
|
meltygui/core/module_map.json,sha256=nQpRabbt79yUp383CmHHoriY3BR7Wt-u3iwXRLKKk6k,71038
|
|
72
72
|
meltygui/core/module_names.py,sha256=Y4fFyW8B9Sp68OymFQcYzFNpdDppwFV4TEBTPwOJv0Q,587
|
|
73
73
|
meltygui/core/automation/__init__.py,sha256=qLfR1a21ndcddqigc3kz2QEeefXyAxrUR2Aavgts94M,45
|
|
@@ -88,9 +88,9 @@ meltygui/core/cache/__init__.py,sha256=lqIupeNqK0CgSSyX-oVxiKbl_-6oisCQfj0DJPcOC
|
|
|
88
88
|
meltygui/core/cache/cache_diagnostics.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
89
|
meltygui/core/cache/invalidation_decoration.py,sha256=9OADxQcWK7GESxDXADK_4xvvTBb7pk2egmdlL2yLv3M,8068
|
|
90
90
|
meltygui/core/cache/invalidation_tracker.py,sha256=WkbK-8mgkwxlMiRV1HQ154R5-7akAsJSUBVIudmtfQk,2781
|
|
91
|
-
meltygui/core/cache/mask_batch.py,sha256
|
|
91
|
+
meltygui/core/cache/mask_batch.py,sha256=-3yv2DSrdtKdM0v5Wt94sfwM8ei8MgT6JOKnOzSwSDA,8345
|
|
92
92
|
meltygui/core/cache/parameter_dependencies.py,sha256=xZ9LoTVPqibptYqG-EmSLgueCn0ZMFDxEPcqIZu1jzE,2218
|
|
93
|
-
meltygui/core/cache/tile_cache.py,sha256=
|
|
93
|
+
meltygui/core/cache/tile_cache.py,sha256=ub_jH8ztxCuR5Hb0BO4-jZfrIoMBBZMqM6W8lNmNJb8,306739
|
|
94
94
|
meltygui/core/cache/tile_marks.py,sha256=mF4ZXw-q0QzBMVV2c5mhJ6rLhawbgBzxvYBvvzbq1Lw,5186
|
|
95
95
|
meltygui/core/conversion/__init__.py,sha256=e2s7OoBQMD2xb-odKUzBme7QmQUYZUAmRW6VVyw0c_U,45
|
|
96
96
|
meltygui/core/conversion/bubbling.py,sha256=BmnQoru9ogOaCx5tyHahllW6V0LtAyjWXNEqEySKke8,23281
|
|
@@ -136,9 +136,9 @@ meltygui/core/graphics/cuda_kernel_core.py,sha256=HmiZyDq7_5pbpa01SXO9pkTU0YbbdM
|
|
|
136
136
|
meltygui/core/graphics/framebuffer_recorder.py,sha256=Q5GhivJHmkDlUMH9XkMoTLOmEYlNB70H6IGMxEzuuBg,10877
|
|
137
137
|
meltygui/core/graphics/gl_state.py,sha256=7V85zk2Ryoi17zQjYHSV3fmx1x4W7Il1-g3Q3s0EMcg,29184
|
|
138
138
|
meltygui/core/graphics/lut_core.py,sha256=nw1GZs3tOwcOH9WGNulCm4OJh260Bsz53GwuClZVdX4,2230
|
|
139
|
-
meltygui/core/graphics/overlay_renderer.py,sha256=
|
|
139
|
+
meltygui/core/graphics/overlay_renderer.py,sha256=MUAYDfxl7GJtgxSwLrz0XZV1hDinfXAsFkuBHlbsoPw,50278
|
|
140
140
|
meltygui/core/graphics/scene_target.py,sha256=YEhzvksdnnkYVXubAGsfsGYlj1bsoVG-3jxC5n8ZeHM,7918
|
|
141
|
-
meltygui/core/graphics/screenshot.py,sha256=
|
|
141
|
+
meltygui/core/graphics/screenshot.py,sha256=bmU9yBEootNwiIh6kq2B0kKFJz7Q1QSGksllztl5fp0,17660
|
|
142
142
|
meltygui/core/graphics/shader_func.py,sha256=9N2HsN_pXLJAnV9Vt-J_YnwTeaEXP4y-AXNv02NZyyY,20165
|
|
143
143
|
meltygui/core/graphics/tensor_core.py,sha256=E6OCvyp34szA_5Lg7g2V7dFNDRzMZrdjmqZjFO1kOac,2216
|
|
144
144
|
meltygui/core/graphics/text_texture.py,sha256=Txmlxm606pkrN1fevTAAAmEkCoWPs49_coZJNmfiCSk,14450
|
|
@@ -183,7 +183,7 @@ meltygui/core/rendering/shaped.py,sha256=6jnm33vfb6U27RhNvXPB--ufxgVPi28FKTDkFGF
|
|
|
183
183
|
meltygui/core/rendering/view_identity.py,sha256=QP_U-vY50_2BZmJQC5XdDHEfm3PSJ12fHLU1yAcuwh0,8678
|
|
184
184
|
meltygui/core/rendering/window_decoration.py,sha256=tnwgqGH1qTQ6JAtZuFEn2UbGVDOJ2vEM5CTL4HsSgyo,491
|
|
185
185
|
meltygui/core/runtime/__init__.py,sha256=aU32lxO4bl8_2zNk527p33s8IbbN-aBg-VfFeHLJQk8,42
|
|
186
|
-
meltygui/core/runtime/app.py,sha256=
|
|
186
|
+
meltygui/core/runtime/app.py,sha256=U0n7JZfSo6dCEsyT_w4pdXMDrX50RfQwxqw2rmVIjow,46337
|
|
187
187
|
meltygui/core/runtime/app_session.py,sha256=52MTDyZJAZakgtgYZV9p0Y9IiRY-RbDtKZGCpbcfWVs,6327
|
|
188
188
|
meltygui/core/runtime/app_settings.py,sha256=amcflSd7jhoHioy97_90U4Few3e9IZ4rS-ULTQ40GSw,7790
|
|
189
189
|
meltygui/core/runtime/background.py,sha256=GgsxehbxZXrpSc0WDGzWZEczVs21dbMCK9CY_rxzvqw,24824
|
|
@@ -201,11 +201,11 @@ meltygui/core/services/__init__.py,sha256=BRdDyeLvek-RS-VygUhYck0AG7MZ440zSeEnoO
|
|
|
201
201
|
meltygui/core/services/account_core.py,sha256=-LP4uXEjZn6yhgAGT8oNQTm0ltBzFAGH-rC2gjfKYfQ,299
|
|
202
202
|
meltygui/core/services/chat_core.py,sha256=t9KkzqY4raq6Vi2cJau59gYNcfjKT0t2EZg9T1kr7X0,727
|
|
203
203
|
meltygui/core/services/claude_terminal_core.py,sha256=Wue2qc4IyP-Ax9V2eOoSCnQi-XC0IEbvoZYeoYWquk0,16978
|
|
204
|
-
meltygui/core/services/terminal_core.py,sha256=
|
|
204
|
+
meltygui/core/services/terminal_core.py,sha256=jYxcnlumBy43CZBKhGzgLUwc6JzvnFUJETk8dt6AOQc,21909
|
|
205
205
|
meltygui/core/services/terminal_runtime.py,sha256=AadnkQGvA0ahx-FqpBe8ZNekof9HDxLLdlcvDLzfWp8,2890
|
|
206
206
|
meltygui/core/styling/__init__.py,sha256=cREBOWuRcF5jiDBSkCw8gJSmxNDl146z2BMHbxP_-JY,42
|
|
207
207
|
meltygui/core/styling/color_core.py,sha256=qCtHCkAEXpueguvsXAeMXRPKIG3HOFnlqHYN4cE8MBA,1855
|
|
208
|
-
meltygui/core/styling/fonts.py,sha256=
|
|
208
|
+
meltygui/core/styling/fonts.py,sha256=eKhZVdNesvP9CdaQhDcs-NFSuNCbf-iPMgX37X6pzrU,31991
|
|
209
209
|
meltygui/core/styling/global_style.py,sha256=HBvr9nwb4QEMFCE8oPZ2bYJoS7p-iAPk4--YpK3AvD8,9131
|
|
210
210
|
meltygui/core/styling/style.py,sha256=sDB5KH70lWZeRv_HHoqPvIO6Wouhy49CaFSMS6ERQXc,9071
|
|
211
211
|
meltygui/core/styling/style_core.py,sha256=5c-fFnyr34XkRfbzdH5KJkGUA5ZO88zMmzH6kUDPaI4,22639
|
|
@@ -214,10 +214,10 @@ meltygui/core/windowing/__init__.py,sha256=sxb996QyIPT-sdBwoRORUA_zo2TK2xJ6Uh0-b
|
|
|
214
214
|
meltygui/core/windowing/dock_core.py,sha256=qRg1TqjRxjkgCo1reT20L-flFyX5vtM_mjHOFdPAGdc,8415
|
|
215
215
|
meltygui/core/windowing/frame_geometry.py,sha256=jP9OzjYXhRAAKVIcge7AnW0aPGY72DSZtJSzCYuvtMw,1605
|
|
216
216
|
meltygui/core/windowing/geometry_feed.py,sha256=O39MSf5r1e3MqruToZT6ZBC9ohykNk4lu35rtffMvHM,40289
|
|
217
|
-
meltygui/core/windowing/glfw_utils.py,sha256=
|
|
218
|
-
meltygui/core/windowing/os_frame.py,sha256=
|
|
219
|
-
meltygui/core/windowing/surface.py,sha256=
|
|
220
|
-
meltygui/core/windowing/titlebar.py,sha256=
|
|
217
|
+
meltygui/core/windowing/glfw_utils.py,sha256=RfKAFdm0r3qXlNC-qfS6JBWklxo3lt6UUUzn8DX4Z1U,55586
|
|
218
|
+
meltygui/core/windowing/os_frame.py,sha256=ywRa3afuRZ-dNaCKA1CdXdWHnLHy-kgRySygxoF9KzU,77602
|
|
219
|
+
meltygui/core/windowing/surface.py,sha256=jGiRKXOtVIfEYyEc3kgzkS8su6c1KCqUaYw9P3Ry1ac,36568
|
|
220
|
+
meltygui/core/windowing/titlebar.py,sha256=Cf-jIjTlImP6Qbfkt0E78ylMMlTGdIACE5-RRPL4pwU,79988
|
|
221
221
|
meltygui/core/windowing/titlebar_buttons.py,sha256=sSrDcTg_c5l_ZE6WYm2rF8xg1u1lgzXvUPx7CTqKo9U,10828
|
|
222
222
|
meltygui/core/windowing/wayland_move.py,sha256=LiAndHFsePNwU2I9azBDVZPoRpv9d2s_xZSBTcktoUA,41642
|
|
223
223
|
meltygui/core/windowing/window_api.py,sha256=VcBQPVXvefp9mJhOV1C914R36J-GAz0syIC6QbGARjY,2340
|
|
@@ -284,7 +284,7 @@ meltygui/graphics/filter.py,sha256=uuEnIkrpzmaeR-87TA14J_VKn0fyVR-jx--eHgA9UBo,2
|
|
|
284
284
|
meltygui/graphics/filter.pyi,sha256=wG9CeyTsShji_W95MXA_-1PQhwwaM0rRYniBc9N6FVM,35551
|
|
285
285
|
meltygui/graphics/generate_stubs.py,sha256=-WUJanBHvIBkObgMb4HnoAm9SggStYbK1NQcX5tCd1A,599
|
|
286
286
|
meltygui/graphics/registry.py,sha256=IMnJDopE-1MqIa0Ocopa8ESnfBxEMHx5yuynBG_Aglk,6198
|
|
287
|
-
meltygui/graphics/shader_compiler.py,sha256=
|
|
287
|
+
meltygui/graphics/shader_compiler.py,sha256=aeEtGJgXaXuRlayFMANHZklsXa1NfNGsUOZKJEHxTGY,5888
|
|
288
288
|
meltygui/graphics/shaders.py,sha256=8lysNqIeuGLX9NPiX_VJ5l9gmZJ7zK5By2GMiaN5HQ8,46613
|
|
289
289
|
meltygui/graphics/stub_generator.py,sha256=5i3kBhD0FOCEjydqHLoKmgs7lHRnj5KN6w_HcLyZ2Go,8765
|
|
290
290
|
meltygui/graphics/texture_min_max.py,sha256=PcgSQYT3eonJbmj8I-z4Xk5EJEgXwfyF6UZdWHAAOvc,9941
|
|
@@ -411,7 +411,7 @@ meltygui/view/trace_view.py,sha256=pxcfvdx2evR745hBF72TH1D_5aEqX5GlYn_NBeYk4s8,5
|
|
|
411
411
|
meltygui/view/voxel_cuda_view.py,sha256=KlIfRza6EvaJ-Mm9FVHxF4YCy9Vhb6kGenOdM-pJqAc,44045
|
|
412
412
|
meltygui/view/voxel_view.py,sha256=mhtmUnOpCdXhDwtz3LEvEwQSRQaqXEBMPRDJKBP17VQ,101406
|
|
413
413
|
meltygui/view/window_view.py,sha256=k9RNiWFtyO4jjYX51LeqnIPCt0cF8amY01KUCPkXivY,4982
|
|
414
|
-
meltygui-0.1.
|
|
415
|
-
meltygui-0.1.
|
|
416
|
-
meltygui-0.1.
|
|
417
|
-
meltygui-0.1.
|
|
414
|
+
meltygui-0.1.3.dist-info/METADATA,sha256=EAYgn_nuxkJzmY2xqb0lt3UXznp6U4TAHbozGym82Cg,6095
|
|
415
|
+
meltygui-0.1.3.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
416
|
+
meltygui-0.1.3.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
417
|
+
meltygui-0.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|