From 9a7480cd683cde3227796330c39186edf6f59119 Mon Sep 17 00:00:00 2001 From: Alhan Date: Sat, 31 Oct 2026 02:10:39 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20window=20size=20presets=20=E2=80=94=204?= =?UTF-8?q?x8=20screen-ratio=20grid=20in=20settings=20(v1.8.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Settings 'Size' section: 8 col x 4 row grid, 31 buttons labeled a:b (a splits H, b splits W); top-left cell empty placeholder (no 1:1 — title bar double click already maximizes) - Click resizes the window to work.width/b x work.height/a of the display's work area (screen_retriever visibleSize, taskbar excluded); position unchanged, no selection state tracking - minimumSize 400x300 -> 160x100 (4:8 cell needs it) - Settings file-properties rows left-aligned (were centered under path) --- CHANGELOG.md | 11 ++++ lib/l10n/app_en.arb | 1 + lib/l10n/app_localizations.dart | 6 +++ lib/l10n/app_localizations_en.dart | 3 ++ lib/l10n/app_localizations_tr.dart | 3 ++ lib/l10n/app_tr.arb | 1 + lib/main.dart | 2 +- lib/screens/viewer_screen.dart | 22 +++++++- lib/services/window_persistence.dart | 30 +++++++++++ lib/widgets/window_size_selector.dart | 72 +++++++++++++++++++++++++++ pubspec.yaml | 2 +- 11 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 lib/widgets/window_size_selector.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d19f1..60bb099 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to imajViewer are documented in this file. ## [Unreleased] +## [1.8.0] - 2026-10-31 + +### Added +- Window size presets: 4×8 screen-ratio grid in settings; clicking a preset + resizes the window to that fraction of the screen work area (taskbar + excluded). Minimum window size lowered to 160×100. + +### Fixed +- Settings file-properties rows were centered under the longest row (path); + now left-aligned. + ## [1.7.2] - 2026-10-25 ### Fixed diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 50d1ffc..e74fd42 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -4,6 +4,7 @@ "sortBy": "Sort by", "sortByTime": "By time", "sortByName": "By name", + "sizeSection": "Size", "appDescription": "Ultra-lightweight and fast image viewer. Open images and use arrows or keyboard keys to navigate.", "shortcuts": "Shortcuts", "shortcutsList": "• Left/Right arrow — previous/next image\n• Ctrl+O — open file\n• Ctrl+S — save visible area (JPEG 95)\n• Ctrl+R — flip horizontally (clears effects, press again to undo)\n• Ctrl+Q — close window\n• Ctrl+Shift+Q — close all windows\n• F11 — toggle fullscreen\n• Esc — exit fullscreen\n• Scroll — zoom (10% per step)\n• Ctrl+scroll — fine zoom (1% per step)\n• Left-drag — pan\n• Double click — toggle fill/fit\n• Right double click — reset effects\n• Right-drag — contrast\n• Shift+right-drag — rotate\n• Ctrl+right-drag — saturation\n• Ctrl+Shift+right-drag — brightness", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 0acde6c..73e256b 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -122,6 +122,12 @@ abstract class AppLocalizations { /// **'By name'** String get sortByName; + /// No description provided for @sizeSection. + /// + /// In en, this message translates to: + /// **'Size'** + String get sizeSection; + /// No description provided for @appDescription. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index cf8499d..b06c0c3 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -20,6 +20,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get sortByName => 'By name'; + @override + String get sizeSection => 'Size'; + @override String get appDescription => 'Ultra-lightweight and fast image viewer. Open images and use arrows or keyboard keys to navigate.'; diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index 09578ec..684cbca 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -20,6 +20,9 @@ class AppLocalizationsTr extends AppLocalizations { @override String get sortByName => 'İsme göre'; + @override + String get sizeSection => 'Boyut'; + @override String get appDescription => 'Ultra hafif ve hızlı resim görüntüleyici. Görselleri açın, gezinti için okları veya klavye tuşlarını kullanın.'; diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index 89c2135..c0b409b 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -4,6 +4,7 @@ "sortBy": "Sıralama", "sortByTime": "Zamana göre", "sortByName": "İsme göre", + "sizeSection": "Boyut", "appDescription": "Ultra hafif ve hızlı resim görüntüleyici. Görselleri açın, gezinti için okları veya klavye tuşlarını kullanın.", "shortcuts": "Kısayollar", "shortcutsList": "• Sol/Sağ ok — önceki/sonraki resim\n• Ctrl+O — dosya aç\n• Ctrl+S — görünen alanı kaydet (JPEG 95)\n• Ctrl+R — yatay aynala (efektleri sıfırlar, tekrar basınca geri alır)\n• Ctrl+Q — pencereyi kapat\n• Ctrl+Shift+Q — tüm pencereleri kapat\n• F11 — tam ekran / normal geçişi\n• Esc — tam ekrandan çık\n• Kaydırma — yakınlaştır (tık başına %10)\n• Ctrl+kaydırma — ince yakınlaştırma (tık başına %1)\n• Sol tık sürükle — kaydır\n• Çift tık — doldur/sığdır geçişi\n• Sağ çift tık — efektleri sıfırla\n• Sağ tık sürükle — kontrast\n• Shift+sağ tık — döndür\n• Ctrl+sağ tık — doygunluk\n• Ctrl+Shift+sağ tık — parlaklık", diff --git a/lib/main.dart b/lib/main.dart index 150d04a..52fe228 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,7 +18,7 @@ void main(List args) async { final windowOptions = WindowOptions( size: saved?.size ?? defaultSize, - minimumSize: const Size(400, 300), + minimumSize: const Size(160, 100), backgroundColor: Colors.transparent, titleBarStyle: TitleBarStyle.hidden, ); diff --git a/lib/screens/viewer_screen.dart b/lib/screens/viewer_screen.dart index ecb7673..507da09 100644 --- a/lib/screens/viewer_screen.dart +++ b/lib/screens/viewer_screen.dart @@ -13,11 +13,12 @@ import '../services/crop_save_service.dart'; import '../widgets/custom_title_bar.dart'; import '../widgets/image_canvas.dart'; import '../widgets/window_resize_zones.dart'; +import '../widgets/window_size_selector.dart'; import '../shortcuts/app_shortcuts.dart'; import '../services/window_persistence.dart'; import '../l10n/app_localizations.dart'; -const appVersion = '1.7.2'; +const appVersion = '1.8.0'; class ViewerScreen extends StatefulWidget { final List initialFiles; @@ -137,6 +138,11 @@ class _ViewerScreenState extends State with WindowListener { }, ), const Divider(color: Colors.white24, height: 24), + WindowSizeSelector( + workArea: _workArea, + onPick: _applyWindowSize, + ), + const Divider(color: Colors.white24, height: 24), if (_hasImages) ...[ Text(AppLocalizations.of(context)!.fileProperties, style: const TextStyle(color: Colors.white70, fontSize: 13)), @@ -146,6 +152,7 @@ class _ViewerScreenState extends State with WindowListener { builder: (context, snap) { if (!snap.hasData) return const SizedBox(height: 20); return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: snap.data!.map((r) => Padding( padding: const EdgeInsets.only(bottom: 2), child: Text.rich( @@ -259,6 +266,19 @@ class _ViewerScreenState extends State with WindowListener { void _flipHorizontal() => _canvasKey.currentState?.toggleFlipH(); + Future _workArea() async { + final pos = await windowManager.getPosition(); + return WindowPersistence.workAreaSize(windowPosition: pos); + } + + Future _applyWindowSize(int rows, int cols, Size work) async { + try { + await windowManager.setSize(Size(work.width / cols, work.height / rows)); + } catch (_) { + // Boyutlandırma başarısız olursa sessizce geç (buton saf eylem). + } + } + Future _cropSave() async { final path = ImageManager.instance.currentImagePath; final state = _canvasKey.currentState; diff --git a/lib/services/window_persistence.dart b/lib/services/window_persistence.dart index bbfa81e..d2bf59b 100644 --- a/lib/services/window_persistence.dart +++ b/lib/services/window_persistence.dart @@ -84,6 +84,36 @@ class WindowPersistence { } } + /// Work area (taskbar excluded) of the display containing the window, + /// in logical px. Returns null if screen info is unavailable. + static Future workAreaSize({ + required Offset windowPosition, + }) async { + try { + final displays = await screenRetriever.getAllDisplays(); + + // Pencerenin bulunduğu monitörü bul (çoklu monitör desteği) + Display display = await screenRetriever.getPrimaryDisplay(); + for (final d in displays) { + final pos = d.visiblePosition; + final size = d.visibleSize ?? d.size; + if (pos != null && + windowPosition.dx >= pos.dx && + windowPosition.dx < pos.dx + size.width && + windowPosition.dy >= pos.dy && + windowPosition.dy < pos.dy + size.height) { + display = d; + break; + } + } + + return display.visibleSize ?? display.size; + } catch (_) { + // Fallback: screen info unavailable → caller silently no-ops. + 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. diff --git a/lib/widgets/window_size_selector.dart b/lib/widgets/window_size_selector.dart new file mode 100644 index 0000000..ca1acf6 --- /dev/null +++ b/lib/widgets/window_size_selector.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; + +import '../l10n/app_localizations.dart'; + +/// 8 sütun × 4 satır ekran oranı buton ızgarası. +/// +/// Buton (sütun `b`, satır `a`) etiketi `"$a:$b"` → hedef boyut +/// `W/b × H/a` (a dikeyi, b yatayı böler). Sol-üst hücre (1:1) boş +/// placeholder — 1:1 yok (title bar çift tık zaten tam ekran). +class WindowSizeSelector extends StatelessWidget { + final Future Function() workArea; + final void Function(int rows, int cols, Size work) onPick; + + const WindowSizeSelector({ + super.key, + required this.workArea, + required this.onPick, + }); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(AppLocalizations.of(context)!.sizeSection, + style: const TextStyle(color: Colors.white70, fontSize: 13)), + const SizedBox(height: 4), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (var a = 1; a <= 4; a++) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + for (var b = 1; b <= 8; b++) + Padding( + padding: const EdgeInsets.only(right: 4, bottom: 4), + child: a == 1 && b == 1 + ? const SizedBox(width: 46, height: 26) + : GestureDetector( + onTap: () async { + final work = await workArea(); + if (work != null) onPick(a, b, work); + }, + child: Container( + width: 46, + height: 26, + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(4), + ), + alignment: Alignment.center, + child: Text( + '$a:$b', + style: const TextStyle( + color: Colors.white, fontSize: 11), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 77b2f9d..383aebf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: imajviewer description: "Ultra-lightweight image viewer for Linux and Windows." publish_to: 'none' -version: 1.7.2+18 +version: 1.8.0+19 msix_config: display_name: ImajViewer