Add fullscreen in-app image view, no stretch, sharing the editor's in-app/external toggle

This commit is contained in:
sarah committed 2026-09-26 18:50:38 +02:00
1 parent d588d445b4
commit 117532e017
5 files changed
+281 -2

No files matched your search

+69 -2
View File
@@ -28,11 +28,13 @@ from Core import (
AppConfig, ApplyHeightBounds, ChipButtonStyle, DragGroup, ExportSettings, FormatBatchName, ImportSettings,
NotificationStyle, Palette, ResizeHandle,
)
from Editor import EditorView
from ImageView import ImageView
from Preview import HumanSize, Preview, TextExtensions
from SaraSortdInfo import GetSortRules
from Settings import BuildSettingsDialog
from Tags import TagPanel
from Thumbnails import GetThumbnail, IsThumbnailable
from Thumbnails import GetThumbnail, ImageExtensions, IsThumbnailable
DoubleClickWindow = 0.4 # seconds
@@ -46,6 +48,13 @@ def _IsArchive(EntryPath: Path) -> bool:
return any(NameLower.endswith(Ext) for Ext in ArchiveExtensions)
def _IsInAppViewable(EntryPath: Path) -> bool:
"""Everything Config.OpenFilesInApp can route to (Editor.py's EditorView
for text/config/markup, ImageView.py's ImageView for images)."""
Suffix = EntryPath.suffix.lower()
return Suffix in TextExtensions or Suffix in ImageExtensions
def _EntryDescription(Entry: Path) -> str:
@@ -301,6 +310,9 @@ class Browser(ft.Row):
expand=True,
)
self.EntryList = self._MakeEntryListContainer()
# None: the file list shows. A Path: EditorView fills the file area
# instead (Features/Major: "Different display in middle").
self.OpenFilePath: Optional[Path] = None
# SaraSortd integration (Features/Major): "*.png -> ~/Pictures" style
# lines for the currently-browsed directory. Mutated in place by
# _RefreshSortBanner on every NavigateTo, rather than rebuilt as part
@@ -497,6 +509,27 @@ class Browser(ft.Row):
# bottom toolbar sat right under the last entry. Inside that fixed
# height, the inner Container's bgcolor makes it paint, and so
# hit-test, everywhere, including below the last entry.
if self.OpenFilePath is not None:
# A file is open in-app (Features/Major: "Different display in
# middle") — it takes over the whole file area, SortBanner
# included, same as it takes over the KeyboardListener/entry list
# tree below.
if self.OpenFilePath.suffix.lower() in ImageExtensions:
Viewer: ft.Control = ImageView(self.OpenFilePath, OnClose=self._CloseEditor)
else:
Viewer = EditorView(
self.OpenFilePath,
FontFamily=self.Config.FontFamily,
OnClose=self._CloseEditor,
OnNotify=self._Notify,
)
Items.append(Viewer)
if ShowBar and self.Config.BreadcrumbsPosition == "bottom":
Items += [ft.Divider(height=1, color=Palette.Surface1), self._BuildToolbarBar()]
if ShowTagsAt("bottom"):
Items.append(TagsBar)
return Items
Items.append(self.SortBanner)
Items.append(
ft.Container(
@@ -650,6 +683,10 @@ class Browser(ft.Row):
self.SelectedPaths = set()
self.SelectionAnchor = None
self.MetadataCache = {} # a new directory's entries make the old cache meaningless
if self.OpenFilePath is not None: # navigating away closes whatever's open in-app
self.OpenFilePath = None
self.FileListColumn.controls = self._BuildChrome()
self.FileListColumn.update()
self._Refresh()
if self.Config.ShowPreview:
self.PreviewPane.ShowPath(None)
@@ -779,18 +816,36 @@ class Browser(ft.Row):
return [P for P in self.CurrentEntries if P in self.SelectedPaths]
return [Entry]
def OpenEntry(self, EntryPath: Path) -> None:
def OpenEntry(self, EntryPath: Path, ForceInApp: Optional[bool] = None) -> None:
"""ForceInApp overrides Config.OpenFilesInApp for this one call — used
by the per-entry "Open" menu item, which always does the opposite of
whatever double-click currently does (Features/Major: "Different
display in middle")."""
if EntryPath.is_dir():
self.NavigateTo(EntryPath)
return
if self.OnActivateFile:
self.OnActivateFile(EntryPath)
return
WantInApp = self.Config.OpenFilesInApp if ForceInApp is None else ForceInApp
if WantInApp and _IsInAppViewable(EntryPath):
self._OpenInEditor(EntryPath)
return
try:
subprocess.Popen(["xdg-open", str(EntryPath)])
except OSError as Error:
self._Notify(f"Couldn't open {EntryPath.name}: {Error}")
def _OpenInEditor(self, EntryPath: Path) -> None:
self.OpenFilePath = EntryPath
self.FileListColumn.controls = self._BuildChrome()
self.FileListColumn.update()
def _CloseEditor(self) -> None:
self.OpenFilePath = None
self.FileListColumn.controls = self._BuildChrome()
self.FileListColumn.update()
# --- Rendering --------------------------------------------------------
def Refresh(self) -> None:
@@ -1095,6 +1150,18 @@ class Browser(ft.Row):
selection changes."""
Items = [
ft.PopupMenuItem(content="Rename", on_click=lambda e, p=Entry: self._HandleRename(p)),
]
if not Entry.is_dir():
if _IsInAppViewable(Entry):
# Always the opposite of what double-click currently does —
# see OpenEntry's ForceInApp and Config.OpenFilesInApp.
Label = "Open Externally" if self.Config.OpenFilesInApp else "Open in App"
OpenHandler = lambda e, p=Entry: self.OpenEntry(p, ForceInApp=not self.Config.OpenFilesInApp)
else:
Label = "Open"
OpenHandler = lambda e, p=Entry: self.OpenEntry(p)
Items.insert(0, ft.PopupMenuItem(content=Label, on_click=OpenHandler))
Items += [
ft.PopupMenuItem(
content="Copy", on_click=lambda e, p=Entry: self._SetClipboard(self._TargetSelection(p), "copy")
),