Popout was always reopening as floating even when it had been tiled at close. closeEvent already persisted geometry + fullscreen, but nothing captured the Hyprland floating/tiled bit, so the windowrule's `float = yes` rule always won on reopen. Now closeEvent records `_saved_tiled` from hyprctl, popout_controller persists it as `slideshow_tiled`, and FullscreenPreview's restore path calls the new `hyprland.settiled` helper shortly after show() to push the window back into the layout. Saved geometry is ignored for tiled reopens since the tile extent is the layout's concern. behavior change: popout reopens tiled if it was tiled at close.
213 lines
8.8 KiB
Python
213 lines
8.8 KiB
Python
"""Popout (fullscreen preview) lifecycle, state sync, and geometry persistence."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from .main_window import BooruApp
|
|
|
|
log = logging.getLogger("booru")
|
|
|
|
|
|
# -- Pure functions (tested in tests/gui/test_popout_controller.py) --
|
|
|
|
|
|
def build_video_sync_dict(
|
|
volume: int,
|
|
mute: bool,
|
|
autoplay: bool,
|
|
loop_state: int,
|
|
position_ms: int,
|
|
) -> dict:
|
|
"""Build the video-state transfer dict used on popout open/close."""
|
|
return {
|
|
"volume": volume,
|
|
"mute": mute,
|
|
"autoplay": autoplay,
|
|
"loop_state": loop_state,
|
|
"position_ms": position_ms,
|
|
}
|
|
|
|
|
|
# -- Controller --
|
|
|
|
|
|
class PopoutController:
|
|
"""Owns popout lifecycle, state sync, and geometry persistence."""
|
|
|
|
def __init__(self, app: BooruApp) -> None:
|
|
self._app = app
|
|
self._fullscreen_window = None
|
|
self._popout_active = False
|
|
self._info_was_visible = False
|
|
self._right_splitter_sizes: list[int] = []
|
|
|
|
@property
|
|
def window(self):
|
|
return self._fullscreen_window
|
|
|
|
@property
|
|
def is_active(self) -> bool:
|
|
return self._popout_active
|
|
|
|
# -- Open --
|
|
|
|
def open(self) -> None:
|
|
path = self._app._preview._current_path
|
|
if not path:
|
|
return
|
|
info = self._app._preview._info_label.text()
|
|
video_pos = 0
|
|
if self._app._preview._stack.currentIndex() == 1:
|
|
video_pos = self._app._preview._video_player.get_position_ms()
|
|
self._popout_active = True
|
|
self._info_was_visible = self._app._info_panel.isVisible()
|
|
self._right_splitter_sizes = self._app._right_splitter.sizes()
|
|
self._app._preview.clear()
|
|
self._app._preview.hide()
|
|
self._app._info_panel.show()
|
|
self._app._right_splitter.setSizes([0, 0, 1000])
|
|
self._app._preview._current_path = path
|
|
idx = self._app._grid.selected_index
|
|
if 0 <= idx < len(self._app._posts):
|
|
self._app._info_panel.set_post(self._app._posts[idx])
|
|
from .popout.window import FullscreenPreview
|
|
saved_geo = self._app._db.get_setting("slideshow_geometry")
|
|
saved_fs = self._app._db.get_setting_bool("slideshow_fullscreen")
|
|
saved_tiled = self._app._db.get_setting_bool("slideshow_tiled")
|
|
if saved_geo:
|
|
parts = saved_geo.split(",")
|
|
if len(parts) == 4:
|
|
from PySide6.QtCore import QRect
|
|
FullscreenPreview._saved_geometry = QRect(*[int(p) for p in parts])
|
|
FullscreenPreview._saved_fullscreen = saved_fs
|
|
FullscreenPreview._saved_tiled = saved_tiled
|
|
else:
|
|
FullscreenPreview._saved_geometry = None
|
|
FullscreenPreview._saved_fullscreen = True
|
|
FullscreenPreview._saved_tiled = False
|
|
else:
|
|
FullscreenPreview._saved_fullscreen = True
|
|
FullscreenPreview._saved_tiled = saved_tiled
|
|
cols = self._app._grid._flow.columns
|
|
show_actions = self._app._stack.currentIndex() != 2
|
|
monitor = self._app._db.get_setting("slideshow_monitor")
|
|
anchor = self._app._db.get_setting("popout_anchor") or "center"
|
|
self._fullscreen_window = FullscreenPreview(grid_cols=cols, show_actions=show_actions, monitor=monitor, anchor=anchor, parent=self._app)
|
|
self._fullscreen_window.navigate.connect(self.navigate)
|
|
self._fullscreen_window.play_next_requested.connect(self._app._on_video_end_next)
|
|
from ..core.config import library_folders
|
|
self._fullscreen_window.set_folders_callback(library_folders)
|
|
self._fullscreen_window.save_to_folder.connect(self._app._post_actions.save_from_preview)
|
|
self._fullscreen_window.unsave_requested.connect(self._app._post_actions.unsave_from_preview)
|
|
self._fullscreen_window.toggle_save_requested.connect(self._app._post_actions.toggle_save_from_preview)
|
|
if show_actions:
|
|
self._fullscreen_window.bookmark_requested.connect(self._app._post_actions.bookmark_from_preview)
|
|
self._fullscreen_window.set_bookmark_folders_callback(self._app._db.get_folders)
|
|
self._fullscreen_window.bookmark_to_folder.connect(self._app._post_actions.bookmark_to_folder_from_preview)
|
|
self._fullscreen_window.blacklist_tag_requested.connect(self._app._post_actions.blacklist_tag_from_popout)
|
|
self._fullscreen_window.blacklist_post_requested.connect(self._app._post_actions.blacklist_post_from_popout)
|
|
self._fullscreen_window.open_in_default.connect(self._app._open_preview_in_default)
|
|
self._fullscreen_window.open_in_browser.connect(self._app._open_preview_in_browser)
|
|
self._fullscreen_window.closed.connect(self.on_closed)
|
|
self._fullscreen_window.privacy_requested.connect(self._app._privacy.toggle)
|
|
post = self._app._preview._current_post
|
|
if post:
|
|
self._fullscreen_window.set_post_tags(post.tag_categories, post.tag_list)
|
|
pv = self._app._preview._video_player
|
|
self._fullscreen_window.sync_video_state(
|
|
volume=pv.volume,
|
|
mute=pv.is_muted,
|
|
autoplay=pv.autoplay,
|
|
loop_state=pv.loop_state,
|
|
)
|
|
if video_pos > 0:
|
|
self._fullscreen_window.connect_media_ready_once(
|
|
lambda: self._fullscreen_window.seek_video_to(video_pos)
|
|
)
|
|
pre_w = post.width if post else 0
|
|
pre_h = post.height if post else 0
|
|
self._fullscreen_window.set_media(path, info, width=pre_w, height=pre_h)
|
|
self.update_state()
|
|
|
|
# -- Close --
|
|
|
|
def on_closed(self) -> None:
|
|
if self._fullscreen_window:
|
|
from .popout.window import FullscreenPreview
|
|
fs = FullscreenPreview._saved_fullscreen
|
|
geo = FullscreenPreview._saved_geometry
|
|
tiled = FullscreenPreview._saved_tiled
|
|
self._app._db.set_setting("slideshow_fullscreen", "1" if fs else "0")
|
|
self._app._db.set_setting("slideshow_tiled", "1" if tiled else "0")
|
|
if geo:
|
|
self._app._db.set_setting("slideshow_geometry", f"{geo.x()},{geo.y()},{geo.width()},{geo.height()}")
|
|
self._app._preview.show()
|
|
if not self._info_was_visible:
|
|
self._app._info_panel.hide()
|
|
if self._right_splitter_sizes:
|
|
self._app._right_splitter.setSizes(self._right_splitter_sizes)
|
|
self._popout_active = False
|
|
video_pos = 0
|
|
if self._fullscreen_window:
|
|
vstate = self._fullscreen_window.get_video_state()
|
|
pv = self._app._preview._video_player
|
|
pv.volume = vstate["volume"]
|
|
pv.is_muted = vstate["mute"]
|
|
pv.autoplay = vstate["autoplay"]
|
|
pv.loop_state = vstate["loop_state"]
|
|
video_pos = vstate["position_ms"]
|
|
path = self._app._preview._current_path
|
|
info = self._app._preview._info_label.text()
|
|
self._fullscreen_window = None
|
|
if path:
|
|
if video_pos > 0:
|
|
def _seek_preview():
|
|
self._app._preview._video_player.seek_to_ms(video_pos)
|
|
try:
|
|
self._app._preview._video_player.media_ready.disconnect(_seek_preview)
|
|
except RuntimeError:
|
|
pass
|
|
self._app._preview._video_player.media_ready.connect(_seek_preview)
|
|
self._app._preview.set_media(path, info)
|
|
|
|
# -- Navigation --
|
|
|
|
def navigate(self, direction: int) -> None:
|
|
self._app._navigate_preview(direction)
|
|
|
|
# -- State sync --
|
|
|
|
def update_media(self, path: str, info: str) -> None:
|
|
"""Sync the popout with new media from browse/bookmark/library."""
|
|
if self._fullscreen_window and self._fullscreen_window.isVisible():
|
|
self._app._preview._video_player.stop()
|
|
cp = self._app._preview._current_post
|
|
w = cp.width if cp else 0
|
|
h = cp.height if cp else 0
|
|
self._fullscreen_window.set_media(path, info, width=w, height=h)
|
|
show_full = self._app._stack.currentIndex() != 2
|
|
self._fullscreen_window.set_toolbar_visibility(
|
|
bookmark=show_full,
|
|
save=True,
|
|
bl_tag=show_full,
|
|
bl_post=show_full,
|
|
)
|
|
self.update_state()
|
|
|
|
def update_state(self) -> None:
|
|
"""Update popout button states by mirroring the embedded preview."""
|
|
if not self._fullscreen_window:
|
|
return
|
|
self._fullscreen_window.update_state(
|
|
self._app._preview._is_bookmarked,
|
|
self._app._preview._is_saved,
|
|
)
|
|
post = self._app._preview._current_post
|
|
if post is not None:
|
|
self._fullscreen_window.set_post_tags(
|
|
post.tag_categories or {}, post.tag_list
|
|
)
|