Milestone ultima alpha
This commit is contained in:
@@ -76,6 +76,129 @@ def _work_area_bounds(window: tk.Misc) -> tuple[int, int, int, int]:
|
||||
return 0, 0, int(window.winfo_screenwidth()), int(window.winfo_screenheight())
|
||||
|
||||
|
||||
def _taskbar_thickness(window: tk.Misc) -> int:
|
||||
"""Return the Windows taskbar thickness when it can be determined."""
|
||||
|
||||
try:
|
||||
screen_h = int(window.winfo_screenheight())
|
||||
_left, _top, _right, work_bottom = _work_area_bounds(window)
|
||||
inferred = max(0, screen_h - int(work_bottom))
|
||||
if inferred > 0:
|
||||
return inferred
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if hasattr(ctypes, "windll"):
|
||||
class RECT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("left", ctypes.c_long),
|
||||
("top", ctypes.c_long),
|
||||
("right", ctypes.c_long),
|
||||
("bottom", ctypes.c_long),
|
||||
]
|
||||
|
||||
class APPBARDATA(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("cbSize", ctypes.c_uint),
|
||||
("hWnd", ctypes.c_void_p),
|
||||
("uCallbackMessage", ctypes.c_uint),
|
||||
("uEdge", ctypes.c_uint),
|
||||
("rc", RECT),
|
||||
("lParam", ctypes.c_long),
|
||||
]
|
||||
|
||||
ABM_GETTASKBARPOS = 0x00000005
|
||||
abd = APPBARDATA()
|
||||
abd.cbSize = ctypes.sizeof(APPBARDATA)
|
||||
if ctypes.windll.shell32.SHAppBarMessage(ABM_GETTASKBARPOS, ctypes.byref(abd)):
|
||||
rect = abd.rc
|
||||
width = max(0, int(rect.right) - int(rect.left))
|
||||
height = max(0, int(rect.bottom) - int(rect.top))
|
||||
return max(width, height)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _window_nonclient_extra(window: tk.Misc) -> tuple[int, int]:
|
||||
"""Return extra outer frame size added by Windows around the client area."""
|
||||
|
||||
try:
|
||||
if not hasattr(ctypes, "windll"):
|
||||
return 0, 0
|
||||
|
||||
class RECT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("left", ctypes.c_long),
|
||||
("top", ctypes.c_long),
|
||||
("right", ctypes.c_long),
|
||||
("bottom", ctypes.c_long),
|
||||
]
|
||||
|
||||
class POINT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("x", ctypes.c_long),
|
||||
("y", ctypes.c_long),
|
||||
]
|
||||
|
||||
user32 = ctypes.windll.user32
|
||||
hwnd = int(window.winfo_id())
|
||||
outer = RECT()
|
||||
client = RECT()
|
||||
origin = POINT()
|
||||
if not user32.GetWindowRect(hwnd, ctypes.byref(outer)):
|
||||
return 0, 0
|
||||
if not user32.GetClientRect(hwnd, ctypes.byref(client)):
|
||||
return 0, 0
|
||||
if not user32.ClientToScreen(hwnd, ctypes.byref(origin)):
|
||||
return 0, 0
|
||||
|
||||
outer_w = max(0, int(outer.right) - int(outer.left))
|
||||
outer_h = max(0, int(outer.bottom) - int(outer.top))
|
||||
client_w = max(0, int(client.right) - int(client.left))
|
||||
client_h = max(0, int(client.bottom) - int(client.top))
|
||||
extra_w = max(0, outer_w - client_w)
|
||||
extra_h = max(0, outer_h - client_h)
|
||||
return extra_w, extra_h
|
||||
except Exception:
|
||||
return 0, 0
|
||||
|
||||
|
||||
def _window_outer_bounds(window: tk.Misc) -> tuple[int, int, int, int] | None:
|
||||
"""Return the actual outer window rect in screen coordinates."""
|
||||
|
||||
try:
|
||||
if not hasattr(ctypes, "windll"):
|
||||
return None
|
||||
|
||||
class RECT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("left", ctypes.c_long),
|
||||
("top", ctypes.c_long),
|
||||
("right", ctypes.c_long),
|
||||
("bottom", ctypes.c_long),
|
||||
]
|
||||
|
||||
rect = RECT()
|
||||
hwnd = int(window.winfo_id())
|
||||
if not ctypes.windll.user32.GetWindowRect(hwnd, ctypes.byref(rect)):
|
||||
return None
|
||||
return int(rect.left), int(rect.top), int(rect.right), int(rect.bottom)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _set_window_alpha(window: tk.Misc, alpha: float) -> None:
|
||||
"""Best-effort helper to change a window opacity."""
|
||||
|
||||
try:
|
||||
window.attributes("-alpha", float(alpha))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _set_window_bounds(child: tk.Misc, x: int, y: int, width: int | None = None, height: int | None = None) -> None:
|
||||
"""Move a toplevel to the requested bounds, resizing it when dimensions are provided."""
|
||||
|
||||
@@ -146,6 +269,21 @@ def _set_window_position(child: tk.Misc, x: int, y: int) -> None:
|
||||
_set_window_bounds(child, x, y, None, None)
|
||||
|
||||
|
||||
def _ensure_window_position(child: tk.Misc, x: int, y: int, *, tolerance: int = 2) -> None:
|
||||
"""Only correct the window position when it really drifted from the target."""
|
||||
|
||||
try:
|
||||
current_x, current_y = _safe_xy(child)
|
||||
if current_x is None or current_y is None:
|
||||
_set_window_position(child, x, y)
|
||||
return
|
||||
if abs(current_x - int(x)) <= tolerance and abs(current_y - int(y)) <= tolerance:
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
_set_window_position(child, x, y)
|
||||
|
||||
|
||||
def _batch_set_window_positions(windows: list[tk.Misc], positions: list[tuple[int, int]]) -> bool:
|
||||
"""Try to reposition multiple windows in one Win32 batch to reduce flicker."""
|
||||
|
||||
@@ -156,6 +294,11 @@ def _batch_set_window_positions(windows: list[tk.Misc], positions: list[tuple[in
|
||||
|
||||
try:
|
||||
user32 = ctypes.windll.user32
|
||||
WM_SETREDRAW = 0x000B
|
||||
RDW_INVALIDATE = 0x0001
|
||||
RDW_ALLCHILDREN = 0x0080
|
||||
RDW_FRAME = 0x0400
|
||||
redraw_hwnds: list[int] = []
|
||||
hdwp = user32.BeginDeferWindowPos(len(windows))
|
||||
if not hdwp:
|
||||
return False
|
||||
@@ -163,7 +306,9 @@ def _batch_set_window_positions(windows: list[tk.Misc], positions: list[tuple[in
|
||||
SWP_NOSIZE = 0x0001
|
||||
SWP_NOZORDER = 0x0004
|
||||
SWP_NOACTIVATE = 0x0010
|
||||
flags = SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE
|
||||
SWP_NOREDRAW = 0x0008
|
||||
SWP_DEFERERASE = 0x2000
|
||||
flags = SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_DEFERERASE
|
||||
|
||||
for child, (x, y) in zip(windows, positions):
|
||||
try:
|
||||
@@ -175,15 +320,43 @@ def _batch_set_window_positions(windows: list[tk.Misc], positions: list[tuple[in
|
||||
except Exception:
|
||||
pass
|
||||
hwnd = int(child.winfo_id())
|
||||
redraw_hwnds.append(hwnd)
|
||||
try:
|
||||
user32.SendMessageW(hwnd, WM_SETREDRAW, 0, 0)
|
||||
except Exception:
|
||||
pass
|
||||
hdwp = user32.DeferWindowPos(hdwp, hwnd, 0, int(x), int(y), 0, 0, flags)
|
||||
if not hdwp:
|
||||
for redraw_hwnd in redraw_hwnds:
|
||||
try:
|
||||
user32.SendMessageW(redraw_hwnd, WM_SETREDRAW, 1, 0)
|
||||
user32.RedrawWindow(redraw_hwnd, None, None, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_FRAME)
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
return bool(user32.EndDeferWindowPos(hdwp))
|
||||
ok = bool(user32.EndDeferWindowPos(hdwp))
|
||||
for redraw_hwnd in redraw_hwnds:
|
||||
try:
|
||||
user32.SendMessageW(redraw_hwnd, WM_SETREDRAW, 1, 0)
|
||||
user32.RedrawWindow(redraw_hwnd, None, None, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_FRAME)
|
||||
except Exception:
|
||||
pass
|
||||
return ok
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _restack_windows(windows: list[tk.Misc]) -> None:
|
||||
"""Lift windows from back to front without mixing movement and z-order updates."""
|
||||
|
||||
for child in windows:
|
||||
try:
|
||||
child.lift()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def place_window_below_parent(parent: tk.Misc, child: tk.Misc, *, x_offset: int = 0, y_gap: int = 0):
|
||||
"""Place ``child`` so its outer top edge sits just below ``parent``.
|
||||
|
||||
@@ -220,20 +393,95 @@ def place_window_fullsize_below_parent(parent: tk.Misc, child: tk.Misc, *, x_off
|
||||
parent.update_idletasks()
|
||||
child.update_idletasks()
|
||||
work_left, _work_top, work_right, work_bottom = _work_area_bounds(parent)
|
||||
screen_h = int(parent.winfo_screenheight())
|
||||
x = parent.winfo_x() + int(x_offset)
|
||||
y = parent.winfo_rooty() + parent.winfo_height() + int(y_gap)
|
||||
width = max(320, int(work_right) - int(x))
|
||||
height = max(240, int(work_bottom) - int(y))
|
||||
taskbar_h = _taskbar_thickness(parent)
|
||||
usable_bottom = int(work_bottom)
|
||||
if taskbar_h > 0:
|
||||
usable_bottom = min(usable_bottom, screen_h - int(taskbar_h))
|
||||
extra_w, extra_h = _window_nonclient_extra(child)
|
||||
width = max(320, int(work_right) - int(x) - int(extra_w))
|
||||
height = max(240, int(usable_bottom) - int(y) - int(extra_h))
|
||||
_MODULE_LOGGER.debug(
|
||||
"fullsize.calc window=%s x=%s y=%s work_right=%s usable_bottom=%s taskbar=%s extra=(%s,%s) final=(%s,%s)",
|
||||
_window_label(child),
|
||||
x,
|
||||
y,
|
||||
work_right,
|
||||
usable_bottom,
|
||||
taskbar_h,
|
||||
extra_w,
|
||||
extra_h,
|
||||
width,
|
||||
height,
|
||||
)
|
||||
_set_window_bounds(child, x, y, width, height)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _fit_window_to_work_area(parent: tk.Misc, child: tk.Misc, *, x_offset: int = 0, y_gap: int = 0) -> None:
|
||||
"""Trim a rendered child window so its outer frame stays above the taskbar."""
|
||||
|
||||
try:
|
||||
if not getattr(child, "winfo_exists", lambda: False)():
|
||||
return
|
||||
parent.update_idletasks()
|
||||
child.update_idletasks()
|
||||
_work_left, _work_top, _work_right, work_bottom = _work_area_bounds(parent)
|
||||
screen_h = int(parent.winfo_screenheight())
|
||||
taskbar_h = _taskbar_thickness(parent)
|
||||
usable_bottom = int(work_bottom)
|
||||
if taskbar_h > 0:
|
||||
usable_bottom = min(usable_bottom, screen_h - int(taskbar_h))
|
||||
outer = _window_outer_bounds(child)
|
||||
if outer is None:
|
||||
return
|
||||
left, top, right, bottom = outer
|
||||
overflow = int(bottom) - int(usable_bottom)
|
||||
_MODULE_LOGGER.debug(
|
||||
"fit_window.check window=%s outer=(%s,%s,%s,%s) usable_bottom=%s overflow=%s",
|
||||
_window_label(child),
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
usable_bottom,
|
||||
overflow,
|
||||
)
|
||||
if overflow <= 0:
|
||||
return
|
||||
current_w, current_h = _safe_wh(child)
|
||||
if current_w is None or current_h is None:
|
||||
return
|
||||
new_h = max(240, int(current_h) - int(overflow) - 2)
|
||||
x = parent.winfo_x() + int(x_offset)
|
||||
y = parent.winfo_rooty() + parent.winfo_height() + int(y_gap)
|
||||
_MODULE_LOGGER.debug(
|
||||
"fit_window.apply window=%s current=(%s,%s) new_h=%s",
|
||||
_window_label(child),
|
||||
current_w,
|
||||
current_h,
|
||||
new_h,
|
||||
)
|
||||
_set_window_bounds(child, x, y, int(current_w), int(new_h))
|
||||
except Exception:
|
||||
_MODULE_LOGGER.exception("fit_window.error")
|
||||
|
||||
|
||||
def place_window_fullsize_below_parent_later(parent: tk.Misc, child: tk.Misc, *, x_offset: int = 0, y_gap: int = 0):
|
||||
"""Schedule full-size placement below the launcher after geometry settles."""
|
||||
|
||||
try:
|
||||
try:
|
||||
_set_window_alpha(child, 0.0)
|
||||
except Exception:
|
||||
pass
|
||||
child.after(0, lambda: place_window_fullsize_below_parent(parent, child, x_offset=x_offset, y_gap=y_gap))
|
||||
child.after(120, lambda: _fit_window_to_work_area(parent, child, x_offset=x_offset, y_gap=y_gap))
|
||||
child.after(260, lambda: _fit_window_to_work_area(parent, child, x_offset=x_offset, y_gap=y_gap))
|
||||
child.after(300, lambda: _set_window_alpha(child, 1.0) if getattr(child, "winfo_exists", lambda: False)() else None)
|
||||
except Exception:
|
||||
place_window_fullsize_below_parent(parent, child, x_offset=x_offset, y_gap=y_gap)
|
||||
|
||||
@@ -324,11 +572,20 @@ def cascade_children_below_parent(
|
||||
try:
|
||||
if not batched:
|
||||
_set_window_position(child, x, y)
|
||||
child.after(110, lambda w=child, px=x, py=y: _set_window_position(w, px, py) if getattr(w, "winfo_exists", lambda: False)() else None)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
parent.after(10, lambda wins=list(windows): _restack_windows([w for w in wins if getattr(w, "winfo_exists", lambda: False)()]))
|
||||
except Exception:
|
||||
_restack_windows(windows)
|
||||
for child, (x, y) in zip(windows, positions):
|
||||
try:
|
||||
child.lift()
|
||||
child.after(
|
||||
110,
|
||||
lambda w=child, px=x, py=y: _ensure_window_position(w, px, py)
|
||||
if getattr(w, "winfo_exists", lambda: False)()
|
||||
else None,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
|
||||
Reference in New Issue
Block a user