diff --git a/README.md b/README.md index 17c3c9c..c1f0a1a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Ultra hafif görüntü izleyici. Flutter ile yazıldı — Linux & Windows deste ## Özellikler -- **Dosya formatları:** PNG, JPEG, WebP, BMP, GIF +- **Dosya formatları:** PNG, JPEG, JFIF, WebP, BMP, GIF, WBMP - **Pan:** Sol tık + sürükle - **Zoom:** Fare tekerleği (imleç merkezli, rotate modunda viewport merkezli) - **Fill/Fit geçişi:** Sol çift tık @@ -15,6 +15,7 @@ Ultra hafif görüntü izleyici. Flutter ile yazıldı — Linux & Windows deste - Sürükle-bırak ile resim açma - Pencere boyutu/konumu hatırlama (JSON persistence) - Varsayılan görüntü izleyici olarak ayarlanabilir +- Yeni pencere öncekinin yanında açılır (cascade — taşarsa alt satır) - Taskbar gruplama (Linux: `StartupWMClass`, otomatik) ## Linux Kurulum diff --git a/deb/usr/share/applications/imajviewer.desktop b/deb/usr/share/applications/imajviewer.desktop index 8c8e3f8..9e94813 100644 --- a/deb/usr/share/applications/imajviewer.desktop +++ b/deb/usr/share/applications/imajviewer.desktop @@ -6,5 +6,6 @@ Exec=/usr/bin/imajviewer %F Icon=imajviewer Terminal=false Categories=Graphics;Viewer;RasterGraphics; -MimeType=image/png;image/jpeg;image/webp;image/bmp;image/gif;image/jpg; +MimeType=image/png;image/jpeg;image/webp;image/bmp;image/gif;image/jpg;image/vnd.wap.wbmp; StartupNotify=false +StartupWMClass=com.example.imajviewer diff --git a/dist/imajviewer_1.0.0_amd64.deb b/dist/imajviewer_1.0.0_amd64.deb index 93f580c..e922770 100644 Binary files a/dist/imajviewer_1.0.0_amd64.deb and b/dist/imajviewer_1.0.0_amd64.deb differ diff --git a/dist/rebuild-deb.sh b/dist/rebuild-deb.sh index 892eb65..2e4fa11 100644 --- a/dist/rebuild-deb.sh +++ b/dist/rebuild-deb.sh @@ -23,11 +23,9 @@ cp -r "$RELEASE_DIR"/* "$TMPDIR/pkg/usr/lib/${APP}/" echo "• Symlink güncelleniyor…" ln -sf "/usr/lib/${APP}/${APP}" "$TMPDIR/pkg/usr/bin/${APP}" -echo "• .desktop dosyasına StartupWMClass ekleniyor…" +echo "• .desktop dosyası kaynaktan kopyalanıyor…" DESKTOP_FILE="$TMPDIR/pkg/usr/share/applications/${APP}.desktop" -if ! grep -q 'StartupWMClass' "$DESKTOP_FILE" 2>/dev/null; then - sed -i '/^StartupNotify=false$/a StartupWMClass=com.example.imajviewer' "$DESKTOP_FILE" -fi +cp "${PROJECT_DIR}/deb/usr/share/applications/${APP}.desktop" "$DESKTOP_FILE" echo "• Yeni .deb paketi oluşturuluyor…" fakeroot dpkg-deb --build "$TMPDIR/pkg" "${DEB}.tmp" >/dev/null diff --git a/docs/format-destegi-ek-formatlar.md b/docs/format-destegi-ek-formatlar.md new file mode 100644 index 0000000..d922d04 --- /dev/null +++ b/docs/format-destegi-ek-formatlar.md @@ -0,0 +1,59 @@ +# Ek Görüntü Formatları Destek Planı + +> Tarih: 2026-08-03 · Değerlendirme + implementasyon planı +> Not: TIFF değerlendirildi ve **vazgeçildi** (kullanıcı kararı). Bu plan yalnızca mevcut mimariye sıfır maliyetle eklenebilen formatları kapsar. + +## 1. Hedef + +imajViewer'ın desteklediği format listesini, **ek decode katmanı gerektirmeden** (Flutter/Skia'nın zaten açabildiği) formatlarla genişletmek. + +## 2. Mevcut Durum + +### Desteklenen uzantılar (kod içi, 3 yerde) +| Dosya | Satır | Liste | +|---|---|---| +| `lib/services/file_handler.dart` | 9 | png, jpg, jpeg, webp, bmp, gif | +| `lib/services/image_manager.dart` | 24 | png, jpg, jpeg, webp, bmp, gif | +| `imajviewer.desktop` + `deb/.../imajviewer.desktop` | 9 | image/png;image/jpeg;image/webp;image/bmp;image/gif;image/jpg | +| `install.sh` | 77 | aynı MimeType listesi | + +### Flutter'ın yerleşik codec'leri (Skia) +Resmi dokümantasyona göre Flutter açabilir: **JPEG, PNG, GIF, WebP, BMP, WBMP** + +Yani Skia zaten açabiliyor ama listemizde olmayanlar: +- `.jfif` — JPEG/JFIF içerik (aslında JPEG, magic-byte aynı) +- `.jpe` — JPEG alternatif uzantısı +- `.wbmp` — Skia'da WBMP codec var +- `.avif` — **YOK**: Flutter issue #61229 açık, native plugin (`flutter_avif`/libavif) gerekir → kapsam dışı +- `.tif/.tiff` — **YOK**: image paketi gerekir → vazgeçildi + +## 3. Eklenecekler + +| Uzantı | MIME | Neden açılır | Maliyet | +|---|---|---|---| +| `.jfif` | image/jpeg | JPEG codec, aynı magic byte | sadece filtre | +| `.jpe` | image/jpeg | JPEG alternatif uzantısı | sadece filtre | +| `.wbmp` | image/vnd.wap.wbmp | Skia WBMP codec | sadece filtre | + +> `.avif` bilinçli olarak eklenmiyor: Flutter'ın yerleşik decoder'ı yok; native libavif bağımlılığı "ultra hafif" felsefeyi bozar. İstenirse ileride ayrı plan yapılır. + +## 4. Yapılacaklar (sıralı) + +1. [ ] `file_handler.dart` → allowedExtensions: `'jfif', 'jpe', 'wbmp'` ekle +2. [ ] `image_manager.dart` → supported: `'jfif', 'jpe', 'wbmp'` ekle +3. [ ] `imajviewer.desktop` → MimeType'a `image/jpeg` zaten var (jfif/jpe kapsar), `image/vnd.wap.wbmp;` ekle +4. [ ] `deb/usr/share/applications/imajviewer.desktop` → aynı +5. [ ] `install.sh` → aynı MimeType güncellemesi +6. [ ] Windows installer (`.iss`) dosya ilişkilendirme listesi kontrolü — varsa `image/jpeg` jfif/jpe'yi kapsar + +## 5. Riskler / Açık Sorular + +- **JFIF varyantı:** Bazı .jfif dosyaları CMYK-JPEG olabilir; Skia çoğunu açıyor ama tamamını değil → açılmayanlar `errorBuilder` ekranı gösterir (mevcut davranış, çökme yok) +- **WBMP nadir:** Çoğu kullanıcı hiç karşılaşmaz; eklemesi zararsız +- **Windows ilişkilendirme:** Inno Setup `.iss` dosyası `image/jpeg` kayıtlıysa .jfif/.jpe otomatik kapsanır; `.wbmp` için ayrı registry gerekebilir (opsiyonel) + +## 6. Kabul Kriterleri + +- [ ] `.jfif`, `.jpe`, `.wbmp` dosyaları dosya seçicide görünür +- [ ] JFIF dosyası pan/zoom/filtre ile görüntülenir +- [ ] Mevcut PNG/JPEG/WebP/GIF/BMP davranışı değişmez diff --git a/docs/index.md b/docs/index.md index 1fc7c18..771bb70 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,6 +2,9 @@ | Tarih | Dosya | Konu | |---|---|---| +| 2026-08-03 | [session-2026-08-03.md](session-2026-08-03.md) | **Session özeti** — jfif/jpe/wbmp, pencere cascade, konum kaydı düzeltmesi | +| 2026-08-03 | [format-destegi-ek-formatlar.md](format-destegi-ek-formatlar.md) | **Plan** — jfif/jpe/wbmp desteği (TIFF vazgeçildi) | +| 2026-08-03 | [pencere-cascade-yerlesim.md](pencere-cascade-yerlesim.md) | **Plan** — yeni pencereyi yan yana / alt satıra açma (screen_retriever) | | 2026-07-27 | [session-windows-ci.md](session-windows-ci.md) | **Windows setup.exe yeniden derlendi, Gitea CI eklendi** | | 2026-07-27 | [session-2026-07-27.md](session-2026-07-27.md) | **Session özeti** — rotate aktif, StartupWMClass ile taskbar gruplama | | 2026-07-27 | [image-canvas-interactions.md](image-canvas-interactions.md) | Detaylı analiz: contrast, saturation, rotate implementasyon planı | diff --git a/docs/pencere-cascade-yerlesim.md b/docs/pencere-cascade-yerlesim.md new file mode 100644 index 0000000..59e556c --- /dev/null +++ b/docs/pencere-cascade-yerlesim.md @@ -0,0 +1,111 @@ +# Pencere Cascade Yerleşimi Planı + +> Tarih: 2026-08-03 · Değerlendirme + implementasyon planı (henüz kod değişikliği yapılmadı) + +## 1. Sorun + +imajViewer'a yeni bir resim açıldığında pencere **hep aynı koordinat ve boyutta** açılıyor (önceki pencerenin üstüne biniyor). İstenen davranış: + +- Yeni pencere, önceki pencerenin **x + width** kadar sağına açılsın (yan yana) +- Ekrandan **taşacaksa** alt satıra, satır başından (x baştan) açılsın + +Bu, Windows'un klasik "cascade windows" davranışının basit hali. + +## 2. Mevcut Durum Analizi + +### Pencere açılış akışı (`lib/main.dart`) +``` +main(args) + → WindowPersistence.restore() // window.json'dan son konum/boyut + → windowOptions = WindowOptions(size: saved ?? 1280x800) + → waitUntilReadyToShow: + saved varsa → setPosition(saved.position) + setSize(saved.size) + saved yoksa → center() + → runApp(ImajViewerApp(initialFiles: args)) +``` + +### Konum kaydetme akışı (`lib/screens/viewer_screen.dart` + `lib/services/window_persistence.dart`) +- `_startPolling()`: 2 saniyede bir `windowManager.getBounds()` → `WindowPersistence.saveSync()` → `~/.config/imajviewer/window.json` (Linux) / `%APPDATA%/imajviewer/window.json` (Windows) +- `onWindowEvent` (move/resize/close) → debounce'lu `_doSave()` + +### Sorunun kökü +- Her yeni pencere `window.json`'daki **son kaydedilen** konumu okuyor → aynı yere açılıyor +- Cascade mantığı yok; ekran genişliği bilgisi hiç sorgulanmıyor + +## 3. Teknik Araştırma (doğrulandı) + +### window_manager 0.4.3 (mevcut bağımlılık) +- `setPosition`, `setSize`, `getBounds`, `getPosition`, `getSize`, `center()` → var +- **Ekran/display API'si → YOK** (getScreen/getPrimaryDisplay yok) + +### window_manager 0.5.2 (güncel) +- Ekran API'si yine yok — `screen_retriever`'ı bağımlılık olarak içeriyor ama expose etmiyor + +### screen_retriever 0.2.2 (pub.dev, window_manager'ın zaten bağımlısı) +- `ScreenRetriever.instance.getPrimaryDisplay()` → `Display` +- `getAllDisplays()` → `List` +- `Display` alanları: + - `size` (ekran tam boyutu, logical px) + - `visiblePosition` (Offset — çalışma alanı başlangıcı) + - `visibleSize` (Size — görev çubuğu hariç çalışma alanı) + - `scaleFactor` +- Linux (X11) + Windows desteği var + +**Karar: `screen_retriever` doğrudan bağımlılık olarak eklenir** (window_manager sürümü değişmez). + +## 4. Önerilen Algoritma + +`main.dart`'ta, `windowOptions` oluşturulmadan önce: + +``` +1. saved = WindowPersistence.restore() +2. display = await ScreenRetriever.instance.getPrimaryDisplay() + workArea = display.visiblePosition + display.visibleSize (yoksa display.size) +3. Eğer saved != null: + newX = saved.x + saved.width // önceki pencerenin sağına + newY = saved.y + + // Yatay taşma → alt satıra, satır başına + if (newX + pencereGenişliği > workArea.right): + newX = workArea.left + newY = saved.y + saved.height + + // Dikey taşma da varsa → en başa sar (0,0) + if (newY + pencereYüksekliği > workArea.bottom): + newX = workArea.left + newY = workArea.top + + position = Offset(newX, newY) +4. saved yoksa → center() (mevcut davranış) +``` + +### Örnek +- Ekran 1920×1080, pencere 1280×800, önceki pencere (100, 50) +- 1. yeni: (1380, 50) — yan yana ✓ +- 2. yeni: (100+1280*2 = hesaplanır) → 1380+1280=2660 > 1920 → (0, 850) — alt satır başı ✓ + +> Not: `saved` her 2 saniyede bir güncellendiği için "önceki pencere" = en son kaydedilmiş konum. Bu, en son açılan pencerenin konumu olur — istenen davranışla uyumlu. + +## 5. Yapılacaklar (sıralı) + +1. [ ] `pubspec.yaml` → `screen_retriever: ^0.2.2` ekle, `flutter pub get` +2. [ ] `lib/main.dart` → cascade hesabı ekle (yeni bir servis fonksiyonuna taşınabilir) +3. [ ] `lib/services/window_persistence.dart` → `restore()` dışında, cascade hesabı için yardımcı fonksiyon: + - `static Future cascadePosition({required Size windowSize})` (opsiyonel, temizlik için) +4. [ ] Ekran boyutu alınamazsa (hata / çoklu monitör) → mevcut davranışa düş (saved veya center) +5. [ ] Test: ardışık 3-4 resim aç → yan yana dizilim + taşınca alt satır + +## 6. Riskler / Açık Sorular + +- **Çoklu monitör:** `getPrimaryDisplay` birincil ekranı verir. İmleç hangi ekrandaysa oraya açılsın mı? (başlangıç: primary) +- **Pencere kapatılıp yenisi açılırsa:** `window.json` son kapanan pencerenin konumunu tutar; yeni pencere onun yanına açılır — boşluk oluşabilir, kabul edilebilir +- **Tam ekran / maximize durumu:** saved boyut maximize ise cascade hesabı yine de eski boyutla yapılır; `window.json` sadece normal durumda yazıldığı için risk düşük +- **Pencere ekrandan tamamen dışarıdaysa:** `restore()` zaten `x < -1000 || y < -1000` guard'ı var (window_persistence.dart:44) — yine de sağlamlaştırılabilir + +## 7. Kabul Kriterleri + +- [ ] Ardışık 2 pencere: ikincisi birincinin hemen sağında +- [ ] Yatay taşma: yeni pencere alt satıra, satır başından başlar +- [ ] İlk açılışta (kayıt yok) pencere yine ortalanır +- [ ] Pencere boyutu/konumu kaydetme davranışı bozulmaz +- [ ] Linux ve Windows'ta çalışır (screen_retriever her ikisini destekliyor) diff --git a/docs/session-2026-08-03.md b/docs/session-2026-08-03.md new file mode 100644 index 0000000..fb9c300 --- /dev/null +++ b/docs/session-2026-08-03.md @@ -0,0 +1,33 @@ +# Session Özeti — 2026-08-03 + +## Yapılanlar + +### 1. Ek Görüntü Formatları (jfif / jpe / wbmp) +- TIFF değerlendirildi, **vazgeçildi** (kullanıcı kararı — image paketi/decode katmanı istenmedi) +- AVIF değerlendirildi, **eklenmedi** (Flutter'da yerleşik decoder yok — native libavif gerekir, "ultra hafif" felsefeye aykırı) +- Eklendi: `.jfif`, `.jpe` (JPEG içerikli, Skia zaten açıyor), `.wbmp` (Skia WBMP codec) +- Değişen yerler: file_handler (seçici), image_manager (filtre), 2× .desktop + install.sh (MimeType) + +### 2. Pencere Cascade Yerleşimi +- Yeni pencere artık öncekinin **x + width** kadar sağında açılıyor +- Ekrandan taşarsa → alt satıra, satır başından +- O da taşarsa → ekran sol-üst köşesine +- Ekran bilgisi: `screen_retriever` paketi (window_manager'ın zaten transitive bağımlısıydı, doğrudan bağımlılık yapıldı) +- Yeni: `WindowPersistence.cascadePosition()` — getPrimaryDisplay → visibleSize/visiblePosition + +### 3. Pencere Konum Kaydı Düzeltmesi (kök neden) +- **Sorun:** Konum kaydedilemiyordu → çözüm olarak 5 yere kayıt koyulmuştu (initState + 2sn polling + move/resize debounce + close + main). Birden fazla pencere açıkken polling konum yarışı yaratıyordu → yeni pencere eski konuma göre hesaplanıp üstüne düşüyordu. +- **Çözüm:** Polling kaldırıldı. Tek akış: + 1. `main.dart` — pencere açılışında konum anında saveSync (sıradaki pencere güncel referans okur) + 2. `viewer_screen` move/resize — son bilinen bounds hemen `_lastBounds`'a, 300ms debounce ile saveSync + 3. `viewer_screen` close — `_lastBounds` **senkron** saveSync (async çağrı kapanışta tamamlanmıyordu) + +## Doğrulama +- `flutter analyze` — 18 info uyarı (mevcut kod stil/deprecated), hata yok +- `flutter build linux --release` ✓ +- Kurulum: release bundle `~/.local/lib/imajviewer/` (install.sh debug bundle'ı önceliklendirdiği için elle kopyalandı) +- Kullanıcı manuel test: WebP açılıyor ✓, cascade düzgün çalışıyor ✓ + +## Not +- `docs/session-windows-ci.md` bozuk encoding (mojibake) — içerik git log'dan biliniyor, düzeltilmedi +- Windows setup.exe **yeniden derlenmedi** — sadece Linux build + .deb güncellendi (Windows CI zaten Gitea Actions'ta) diff --git a/imajviewer.desktop b/imajviewer.desktop index 1d529c8..10074ce 100644 --- a/imajviewer.desktop +++ b/imajviewer.desktop @@ -6,5 +6,5 @@ Exec=imajviewer %F Icon=imajviewer Terminal=false Categories=Graphics;Viewer;RasterGraphics; -MimeType=image/png;image/jpeg;image/webp;image/bmp;image/gif;image/jpg; +MimeType=image/png;image/jpeg;image/webp;image/bmp;image/gif;image/jpg;image/vnd.wap.wbmp; StartupNotify=false diff --git a/install.sh b/install.sh index bf74364..fe8a949 100755 --- a/install.sh +++ b/install.sh @@ -74,7 +74,7 @@ Exec=${BIN_LINK} %F Icon=${APP} Terminal=false Categories=Graphics;Viewer;RasterGraphics; -MimeType=image/png;image/jpeg;image/webp;image/bmp;image/gif;image/jpg; +MimeType=image/png;image/jpeg;image/webp;image/bmp;image/gif;image/jpg;image/vnd.wap.wbmp; StartupNotify=false StartupWMClass=com.example.imajviewer EOF diff --git a/lib/main.dart b/lib/main.dart index 39189ba..150d04a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,7 +25,11 @@ void main(List args) async { await windowManager.waitUntilReadyToShow(windowOptions, () async { if (saved != null) { - await windowManager.setPosition(saved.position); + final position = await WindowPersistence.cascadePosition( + previousPosition: saved.position, + previousSize: saved.size, + ); + await windowManager.setPosition(position); await windowManager.setSize(saved.size); } else { await windowManager.center(); @@ -33,6 +37,11 @@ void main(List args) async { await windowManager.setResizable(true); await windowManager.show(); await windowManager.focus(); + + // Açılış konumunu hemen kaydet — sıradaki pencere güncel referans okusun + final pos = await windowManager.getPosition(); + final size = await windowManager.getSize(); + WindowPersistence.saveSync(position: pos, size: size); }); // Extract file paths from command-line arguments diff --git a/lib/screens/viewer_screen.dart b/lib/screens/viewer_screen.dart index 5a4a861..4fca2a4 100644 --- a/lib/screens/viewer_screen.dart +++ b/lib/screens/viewer_screen.dart @@ -21,7 +21,6 @@ class _ViewerScreenState extends State with WindowListener { bool _hasImages = false; bool _isDragging = false; Timer? _saveDebounce; - Timer? _pollTimer; Rect _lastBounds = Rect.zero; @override @@ -35,29 +34,6 @@ class _ViewerScreenState extends State with WindowListener { FileHandler.loadImagesFromPaths(widget.initialFiles); }); } - - // İlk kayıt + periyodik polling başlat - WidgetsBinding.instance.addPostFrameCallback((_) async { - await Future.delayed(const Duration(milliseconds: 200)); - await _doSave(); - _startPolling(); - }); - } - - void _startPolling() { - _pollTimer?.cancel(); - _pollTimer = Timer.periodic(const Duration(seconds: 2), (_) async { - try { - final bounds = await windowManager.getBounds(); - if (bounds != _lastBounds) { - _lastBounds = bounds; - WindowPersistence.saveSync( - position: bounds.topLeft, - size: bounds.size, - ); - } - } catch (_) {} - }); } @override @@ -65,7 +41,6 @@ class _ViewerScreenState extends State with WindowListener { windowManager.removeListener(this); ImageManager.instance.removeListener(_onImageManagerChanged); _saveDebounce?.cancel(); - _pollTimer?.cancel(); super.dispose(); } @@ -74,10 +49,18 @@ class _ViewerScreenState extends State with WindowListener { void onWindowEvent(String eventName) { switch (eventName) { case 'close': - _doSave(); + // Senkron kayıt — async çağrı kapanışta tamamlanmayabilir + if (_lastBounds != Rect.zero) { + WindowPersistence.saveSync( + position: _lastBounds.topLeft, + size: _lastBounds.size, + ); + } break; case 'resize': case 'move': + // Son bilinen konumu hemen güncelle (kapanış senkron kaydı için) + windowManager.getBounds().then((b) => _lastBounds = b).catchError((_) {}); _saveDebounce?.cancel(); _saveDebounce = Timer(const Duration(milliseconds: 300), () { _doSave(); diff --git a/lib/services/file_handler.dart b/lib/services/file_handler.dart index 6b155da..e90fd8c 100644 --- a/lib/services/file_handler.dart +++ b/lib/services/file_handler.dart @@ -6,7 +6,7 @@ class FileHandler { static Future openFileDialog() async { final result = await FilePicker.platform.pickFiles( type: FileType.custom, - allowedExtensions: ['png', 'jpg', 'jpeg', 'webp', 'bmp', 'gif'], + allowedExtensions: ['png', 'jpg', 'jpeg', 'jfif', 'jpe', 'webp', 'bmp', 'gif', 'wbmp'], allowMultiple: true, ); diff --git a/lib/services/image_manager.dart b/lib/services/image_manager.dart index bada35e..f35ef2e 100644 --- a/lib/services/image_manager.dart +++ b/lib/services/image_manager.dart @@ -21,7 +21,7 @@ class ImageManager extends ChangeNotifier { void addImages(List paths) { final validPaths = paths.where((p) { final ext = p.toLowerCase().split('.').last; - const supported = ['png', 'jpg', 'jpeg', 'webp', 'bmp', 'gif']; + const supported = ['png', 'jpg', 'jpeg', 'jfif', 'jpe', 'webp', 'bmp', 'gif', 'wbmp']; return supported.contains(ext) && File(p).existsSync(); }).toList(); diff --git a/lib/services/window_persistence.dart b/lib/services/window_persistence.dart index 43deb1c..dcfa028 100644 --- a/lib/services/window_persistence.dart +++ b/lib/services/window_persistence.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:screen_retriever/screen_retriever.dart'; /// Persists and restores window position and size. class WindowPersistence { @@ -51,4 +52,38 @@ class WindowPersistence { return null; } } + + /// Cascade: place new window to the right of the previous one. + /// Falls to the next row (row start) when it would overflow the screen, + /// and wraps to the top-left if the next row also overflows. + static Future cascadePosition({ + required Offset previousPosition, + required Size previousSize, + }) async { + try { + final display = await screenRetriever.getPrimaryDisplay(); + final workLeft = display.visiblePosition?.dx ?? 0; + final workTop = display.visiblePosition?.dy ?? 0; + final workWidth = display.visibleSize?.width ?? display.size.width; + final workHeight = display.visibleSize?.height ?? display.size.height; + + var x = previousPosition.dx + previousSize.width; + var y = previousPosition.dy; + + if (x + previousSize.width > workLeft + workWidth) { + x = workLeft; + y = previousPosition.dy + previousSize.height; + } + + if (y + previousSize.height > workTop + workHeight) { + x = workLeft; + y = workTop; + } + + return Offset(x, y); + } catch (_) { + // Fallback: keep previous position if screen info is unavailable. + return previousPosition; + } + } } diff --git a/pubspec.lock b/pubspec.lock index a7146d5..314757d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -281,7 +281,7 @@ packages: source: hosted version: "2.2.0" screen_retriever: - dependency: transitive + dependency: "direct main" description: name: screen_retriever sha256: ace919117a7520c13a50a6259e60c4a0d4cbe98809468792a91b5c5adada2aa6 diff --git a/pubspec.yaml b/pubspec.yaml index 8002e4c..e1804dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: flutter: sdk: flutter window_manager: ^0.4.3 + screen_retriever: ^0.2.2 file_picker: ^8.0.0 path: ^1.9.0 desktop_drop: ^0.7.1