feat: zoom percentage badge at bottom center (v1.7.1)
- Percentage shown between the prev/next buttons, together with the hover controls, on a translucent background. - Value is the on-screen scale relative to the image's real pixel size (100% = 1:1); updated on wheel zoom, fill/fit toggle, image change and window resize. - Pixel-size header parsing moved to lib/services/image_header.dart, shared with the settings file-properties panel. - dist: 1.7.1 .deb replaces the tracked 1.7.0 package.
This commit is contained in:
1 parent
abbde04e50
commit
32fb12c210
7 files changed
+184
-70
No files matched your search
+1
-1
@@ -49,7 +49,7 @@ app.*.map.json
|
|||||||
*.deb
|
*.deb
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.exe
|
*.exe
|
||||||
!dist/imajviewer_1.7.0_amd64.deb
|
!dist/imajviewer_1.7.1_amd64.deb
|
||||||
!dist/imajviewer_setup_1.1.0.exe
|
!dist/imajviewer_setup_1.1.0.exe
|
||||||
deb/usr/lib/imajviewer/
|
deb/usr/lib/imajviewer/
|
||||||
release/
|
release/
|
||||||
|
|||||||
@@ -4,6 +4,19 @@ All notable changes to imajViewer are documented in this file.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.7.1] - 2026-10-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Zoom percentage badge at the bottom center, between the previous/next buttons: shown
|
||||||
|
together with the hover controls, translucent background, live while wheeling
|
||||||
|
(100% = one image pixel per screen pixel), recomputed on fill/fit toggle, image
|
||||||
|
change and window resize.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Image header parsing (pixel size without decoding) moved from the viewer screen into
|
||||||
|
`lib/services/image_header.dart`; the settings file-properties panel and the zoom badge
|
||||||
|
use the same code.
|
||||||
|
|
||||||
## [1.7.0] - 2026-10-20
|
## [1.7.0] - 2026-10-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
Binary file not shown.
@@ -1,13 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:image/image.dart' as img;
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:desktop_drop/desktop_drop.dart';
|
import 'package:desktop_drop/desktop_drop.dart';
|
||||||
import 'package:pdf/pdf.dart';
|
import 'package:pdf/pdf.dart';
|
||||||
import 'package:pdf/widgets.dart' as pw;
|
import 'package:pdf/widgets.dart' as pw;
|
||||||
import 'package:printing/printing.dart';
|
import 'package:printing/printing.dart';
|
||||||
|
import '../services/image_header.dart';
|
||||||
import '../services/image_manager.dart';
|
import '../services/image_manager.dart';
|
||||||
import '../services/file_handler.dart';
|
import '../services/file_handler.dart';
|
||||||
import '../services/crop_save_service.dart';
|
import '../services/crop_save_service.dart';
|
||||||
@@ -18,7 +17,7 @@ import '../shortcuts/app_shortcuts.dart';
|
|||||||
import '../services/window_persistence.dart';
|
import '../services/window_persistence.dart';
|
||||||
import '../l10n/app_localizations.dart';
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
const appVersion = '1.7.0';
|
const appVersion = '1.7.1';
|
||||||
|
|
||||||
class ViewerScreen extends StatefulWidget {
|
class ViewerScreen extends StatefulWidget {
|
||||||
final List<String> initialFiles;
|
final List<String> initialFiles;
|
||||||
@@ -33,6 +32,7 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
|
|||||||
bool _controlsVisible = false;
|
bool _controlsVisible = false;
|
||||||
Timer? _saveDebounce;
|
Timer? _saveDebounce;
|
||||||
Rect _lastBounds = Rect.zero;
|
Rect _lastBounds = Rect.zero;
|
||||||
|
final ValueNotifier<double> _zoomPercent = ValueNotifier<double>(100);
|
||||||
final GlobalKey<ImageCanvasState> _canvasKey = GlobalKey<ImageCanvasState>();
|
final GlobalKey<ImageCanvasState> _canvasKey = GlobalKey<ImageCanvasState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -53,6 +53,7 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
|
|||||||
windowManager.removeListener(this);
|
windowManager.removeListener(this);
|
||||||
ImageManager.instance.removeListener(_onImageManagerChanged);
|
ImageManager.instance.removeListener(_onImageManagerChanged);
|
||||||
_saveDebounce?.cancel();
|
_saveDebounce?.cancel();
|
||||||
|
_zoomPercent.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,16 +223,8 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
|
|||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final file = File(path);
|
final file = File(path);
|
||||||
final rows = <(String, String)>[];
|
final rows = <(String, String)>[];
|
||||||
Size? size;
|
final Size? size = pixelSizeOfFile(path);
|
||||||
try {
|
rows.add((l10n.propResolution, size == null ? '—' : '${size.width.toInt()} × ${size.height.toInt()}'));
|
||||||
final stat = file.statSync();
|
|
||||||
const maxHead = 512 * 1024;
|
|
||||||
final raf = file.openSync();
|
|
||||||
final header = raf.readSync(stat.size < maxHead ? stat.size : maxHead);
|
|
||||||
raf.closeSync();
|
|
||||||
size = _headerPixelSize(header);
|
|
||||||
} catch (_) {}
|
|
||||||
rows.add((l10n.propResolution, size == null ? '—' : '${size!.width.toInt()} × ${size!.height.toInt()}'));
|
|
||||||
try {
|
try {
|
||||||
rows.add((l10n.propFileSize, _fmtSize(file.statSync().size)));
|
rows.add((l10n.propFileSize, _fmtSize(file.statSync().size)));
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
@@ -247,60 +240,6 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
|
|||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// İlk 512 KB'dan çözünürlük (piksel decode yok).
|
|
||||||
/// PNG/GIF/BMP/WebP: boyut ilk 33 baytta → elle parse.
|
|
||||||
/// JPEG: SOF büyük APP/EXIF segmentlerinin ardından gelebilir → package:image startDecode.
|
|
||||||
Size? _headerPixelSize(Uint8List b) {
|
|
||||||
try {
|
|
||||||
if (b.length >= 24 && b[0] == 0x89 && b[1] == 0x50) {
|
|
||||||
// PNG
|
|
||||||
final w = (b[16] << 24) | (b[17] << 16) | (b[18] << 8) | b[19];
|
|
||||||
final h = (b[20] << 24) | (b[21] << 16) | (b[22] << 8) | b[23];
|
|
||||||
return Size(w.toDouble(), h.toDouble());
|
|
||||||
}
|
|
||||||
if (b.length >= 10 && b[0] == 0x47 && b[1] == 0x49 && b[2] == 0x46) {
|
|
||||||
// GIF (WBMP da GIF tabanlı olabilir)
|
|
||||||
final w = b[6] | (b[7] << 8);
|
|
||||||
final h = b[8] | (b[9] << 8);
|
|
||||||
return Size(w.toDouble(), h.toDouble());
|
|
||||||
}
|
|
||||||
if (b.length >= 26 && b[0] == 0x42 && b[1] == 0x4D) {
|
|
||||||
// BMP
|
|
||||||
final w = b[18] | (b[19] << 8) | (b[20] << 16) | (b[21] << 24);
|
|
||||||
final h = b[22] | (b[23] << 8) | (b[24] << 16) | (b[25] << 24);
|
|
||||||
return Size(w.toDouble(), h.abs().toDouble());
|
|
||||||
}
|
|
||||||
if (b.length >= 30 && b[0] == 0x52 && b[1] == 0x49 && b[2] == 0x46 && b[3] == 0x46 &&
|
|
||||||
b[8] == 0x57 && b[9] == 0x45 && b[10] == 0x42 && b[11] == 0x50) {
|
|
||||||
// WebP
|
|
||||||
if (b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x58) {
|
|
||||||
// VP8X
|
|
||||||
final w = b[24] | (b[25] << 8) | (b[26] << 16);
|
|
||||||
final h = b[27] | (b[28] << 8) | (b[29] << 16);
|
|
||||||
return Size((w + 1).toDouble(), (h + 1).toDouble());
|
|
||||||
}
|
|
||||||
if (b.length >= 25 && b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x4C && b[20] == 0x2F) {
|
|
||||||
// VP8L
|
|
||||||
final w = (b[21] & 0x3F) | ((b[22] & 0x0F) << 6);
|
|
||||||
final h = (b[23] & 0x3F) | ((b[24] & 0x0F) << 6);
|
|
||||||
return Size((w + 1).toDouble(), (h + 1).toDouble());
|
|
||||||
}
|
|
||||||
if (b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x20) {
|
|
||||||
// VP8 (lossy)
|
|
||||||
final w = (b[26] | (b[27] << 8)) & 0x3FFF;
|
|
||||||
final h = (b[28] | (b[29] << 8)) & 0x3FFF;
|
|
||||||
return Size(w.toDouble(), h.toDouble());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
try {
|
|
||||||
// JPEG
|
|
||||||
final info = img.JpegDecoder().startDecode(b);
|
|
||||||
if (info != null) return Size(info.width.toDouble(), info.height.toDouble());
|
|
||||||
} catch (_) {}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _fmtSize(int bytes) {
|
String _fmtSize(int bytes) {
|
||||||
if (bytes < 1024) return '$bytes B';
|
if (bytes < 1024) return '$bytes B';
|
||||||
const units = ['KB', 'MB', 'GB', 'TB'];
|
const units = ['KB', 'MB', 'GB', 'TB'];
|
||||||
@@ -402,6 +341,7 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
|
|||||||
? ImageCanvas(
|
? ImageCanvas(
|
||||||
key: _canvasKey,
|
key: _canvasKey,
|
||||||
filePath: ImageManager.instance.currentImagePath,
|
filePath: ImageManager.instance.currentImagePath,
|
||||||
|
zoomPercent: _zoomPercent,
|
||||||
)
|
)
|
||||||
: Center(
|
: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -440,6 +380,36 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
|
|||||||
onTap: () => ImageManager.instance.nextImage(),
|
onTap: () => ImageManager.instance.nextImage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 12,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Center(
|
||||||
|
child: ValueListenableBuilder<double>(
|
||||||
|
valueListenable: _zoomPercent,
|
||||||
|
builder: (context, zoom, _) {
|
||||||
|
final pct = zoom.round();
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withValues(alpha: 0.35),
|
||||||
|
borderRadius: BorderRadius.circular(13),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
pct < 1 ? '<1%' : '$pct%',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
if (_isDragging)
|
if (_isDragging)
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart' show Size;
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
|
||||||
|
/// Görüntü dosyasının gerçek piksel boyutu — tam decode yapmadan.
|
||||||
|
///
|
||||||
|
/// PNG/GIF/BMP/WebP: boyut ilk 33 baytta → elle parse.
|
||||||
|
/// JPEG: SOF büyük APP/EXIF segmentlerinin ardından gelebilir → package:image startDecode.
|
||||||
|
Size? pixelSizeOfFile(String path) {
|
||||||
|
try {
|
||||||
|
final file = File(path);
|
||||||
|
final stat = file.statSync();
|
||||||
|
const maxHead = 512 * 1024;
|
||||||
|
final raf = file.openSync();
|
||||||
|
final header = raf.readSync(stat.size < maxHead ? stat.size : maxHead);
|
||||||
|
raf.closeSync();
|
||||||
|
return _headerPixelSize(header);
|
||||||
|
} catch (_) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size? _headerPixelSize(Uint8List b) {
|
||||||
|
try {
|
||||||
|
if (b.length >= 24 && b[0] == 0x89 && b[1] == 0x50) {
|
||||||
|
// PNG
|
||||||
|
final w = (b[16] << 24) | (b[17] << 16) | (b[18] << 8) | b[19];
|
||||||
|
final h = (b[20] << 24) | (b[21] << 16) | (b[22] << 8) | b[23];
|
||||||
|
return Size(w.toDouble(), h.toDouble());
|
||||||
|
}
|
||||||
|
if (b.length >= 10 && b[0] == 0x47 && b[1] == 0x49 && b[2] == 0x46) {
|
||||||
|
// GIF (WBMP da GIF tabanlı olabilir)
|
||||||
|
final w = b[6] | (b[7] << 8);
|
||||||
|
final h = b[8] | (b[9] << 8);
|
||||||
|
return Size(w.toDouble(), h.toDouble());
|
||||||
|
}
|
||||||
|
if (b.length >= 26 && b[0] == 0x42 && b[1] == 0x4D) {
|
||||||
|
// BMP
|
||||||
|
final w = b[18] | (b[19] << 8) | (b[20] << 16) | (b[21] << 24);
|
||||||
|
final h = b[22] | (b[23] << 8) | (b[24] << 16) | (b[25] << 24);
|
||||||
|
return Size(w.toDouble(), h.abs().toDouble());
|
||||||
|
}
|
||||||
|
if (b.length >= 30 &&
|
||||||
|
b[0] == 0x52 &&
|
||||||
|
b[1] == 0x49 &&
|
||||||
|
b[2] == 0x46 &&
|
||||||
|
b[3] == 0x46 &&
|
||||||
|
b[8] == 0x57 &&
|
||||||
|
b[9] == 0x45 &&
|
||||||
|
b[10] == 0x42 &&
|
||||||
|
b[11] == 0x50) {
|
||||||
|
// WebP
|
||||||
|
if (b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x58) {
|
||||||
|
// VP8X
|
||||||
|
final w = b[24] | (b[25] << 8) | (b[26] << 16);
|
||||||
|
final h = b[27] | (b[28] << 8) | (b[29] << 16);
|
||||||
|
return Size((w + 1).toDouble(), (h + 1).toDouble());
|
||||||
|
}
|
||||||
|
if (b.length >= 25 && b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x4C && b[20] == 0x2F) {
|
||||||
|
// VP8L
|
||||||
|
final w = (b[21] & 0x3F) | ((b[22] & 0x0F) << 6);
|
||||||
|
final h = (b[23] & 0x3F) | ((b[24] & 0x0F) << 6);
|
||||||
|
return Size((w + 1).toDouble(), (h + 1).toDouble());
|
||||||
|
}
|
||||||
|
if (b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x20) {
|
||||||
|
// VP8 (lossy)
|
||||||
|
final w = (b[26] | (b[27] << 8)) & 0x3FFF;
|
||||||
|
final h = (b[28] | (b[29] << 8)) & 0x3FFF;
|
||||||
|
return Size(w.toDouble(), h.toDouble());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
try {
|
||||||
|
// JPEG
|
||||||
|
final info = img.JpegDecoder().startDecode(b);
|
||||||
|
if (info != null) return Size(info.width.toDouble(), info.height.toDouble());
|
||||||
|
} catch (_) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -6,11 +6,15 @@ import 'package:flutter/scheduler.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import '../l10n/app_localizations.dart';
|
import '../l10n/app_localizations.dart';
|
||||||
import '../services/crop_save_service.dart';
|
import '../services/crop_save_service.dart';
|
||||||
|
import '../services/image_header.dart';
|
||||||
import '../services/window_persistence.dart';
|
import '../services/window_persistence.dart';
|
||||||
|
|
||||||
class ImageCanvas extends StatefulWidget {
|
class ImageCanvas extends StatefulWidget {
|
||||||
final String filePath;
|
final String filePath;
|
||||||
const ImageCanvas({super.key, required this.filePath});
|
|
||||||
|
/// Görüntünün gerçek piksel boyutuna göre ölçek yüzdesi (100% = 1:1).
|
||||||
|
final ValueNotifier<double>? zoomPercent;
|
||||||
|
const ImageCanvas({super.key, required this.filePath, this.zoomPercent});
|
||||||
@override
|
@override
|
||||||
State<ImageCanvas> createState() => ImageCanvasState();
|
State<ImageCanvas> createState() => ImageCanvasState();
|
||||||
}
|
}
|
||||||
@@ -47,6 +51,44 @@ class ImageCanvasState extends State<ImageCanvas> {
|
|||||||
Size _imageSize = Size.zero;
|
Size _imageSize = Size.zero;
|
||||||
static const int _downsampleTarget = 2048;
|
static const int _downsampleTarget = 2048;
|
||||||
|
|
||||||
|
// ── Gerçek piksel boyutu + zoom yüzdesi ──
|
||||||
|
Size? _origSize;
|
||||||
|
bool _zoomSyncScheduled = false;
|
||||||
|
|
||||||
|
/// Yüzdeyi bir sonraki kare sonunda yayınla (build sırasında setState tetiklememek için).
|
||||||
|
void _syncZoom() {
|
||||||
|
if (_zoomSyncScheduled) return;
|
||||||
|
_zoomSyncScheduled = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_zoomSyncScheduled = false;
|
||||||
|
if (!mounted) return;
|
||||||
|
final n = widget.zoomPercent;
|
||||||
|
if (n == null) return;
|
||||||
|
final v = _zoomPercentValue();
|
||||||
|
if (n.value != v) n.value = v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ekrandaki ölçeğin görüntünün gerçek piksel boyutuna oranı (100% = 1:1).
|
||||||
|
double _zoomPercentValue() {
|
||||||
|
final o = _origSize;
|
||||||
|
if (o == null || o.isEmpty || _vpSize.isEmpty) return _sc * 100;
|
||||||
|
final double ratio;
|
||||||
|
if (_isFilled) {
|
||||||
|
// cover: kutu viewport, boya ölçeği max oran
|
||||||
|
ratio = math.max(_vpSize.width / o.width, _vpSize.height / o.height);
|
||||||
|
} else {
|
||||||
|
// contain: yerleşmiş görüntü kutusu (downsample sınırı dahil) → gerçek ölçek
|
||||||
|
final box = _imageKey.currentContext?.findRenderObject() as RenderBox?;
|
||||||
|
if (box != null && box.hasSize && box.size.width > 0) {
|
||||||
|
ratio = box.size.width / o.width;
|
||||||
|
} else {
|
||||||
|
ratio = math.min(_vpSize.width / o.width, _vpSize.height / o.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _sc * ratio * 100;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Kare hızına eşitlenmiş render tick ──
|
// ── Kare hızına eşitlenmiş render tick ──
|
||||||
final ValueNotifier<int> _renderTick = ValueNotifier(0);
|
final ValueNotifier<int> _renderTick = ValueNotifier(0);
|
||||||
bool _frameScheduled = false;
|
bool _frameScheduled = false;
|
||||||
@@ -128,8 +170,10 @@ class ImageCanvasState extends State<ImageCanvas> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_origSize = pixelSizeOfFile(widget.filePath);
|
||||||
HardwareKeyboard.instance.addHandler(_onHardwareKey);
|
HardwareKeyboard.instance.addHandler(_onHardwareKey);
|
||||||
_startWarmup();
|
_startWarmup();
|
||||||
|
_syncZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// İlk kullanım shader derlemesini açılışa çek: gizli katmanda sırayla
|
/// İlk kullanım shader derlemesini açılışa çek: gizli katmanda sırayla
|
||||||
@@ -189,6 +233,7 @@ class ImageCanvasState extends State<ImageCanvas> {
|
|||||||
void didUpdateWidget(ImageCanvas old) {
|
void didUpdateWidget(ImageCanvas old) {
|
||||||
super.didUpdateWidget(old);
|
super.didUpdateWidget(old);
|
||||||
if (old.filePath != widget.filePath) {
|
if (old.filePath != widget.filePath) {
|
||||||
|
_origSize = pixelSizeOfFile(widget.filePath);
|
||||||
_cachedFileImage?.evict();
|
_cachedFileImage?.evict();
|
||||||
_cachedProvider?.evict();
|
_cachedProvider?.evict();
|
||||||
_cachedFileImage = null;
|
_cachedFileImage = null;
|
||||||
@@ -207,6 +252,7 @@ class ImageCanvasState extends State<ImageCanvas> {
|
|||||||
_usingFullRes = false;
|
_usingFullRes = false;
|
||||||
_imageSize = Size.zero;
|
_imageSize = Size.zero;
|
||||||
_markRenderDirty();
|
_markRenderDirty();
|
||||||
|
_syncZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _toggleFillFit() {
|
void _toggleFillFit() {
|
||||||
@@ -215,6 +261,7 @@ class ImageCanvasState extends State<ImageCanvas> {
|
|||||||
_flipH = false;
|
_flipH = false;
|
||||||
WindowPersistence.saveFilled(_isFilled);
|
WindowPersistence.saveFilled(_isFilled);
|
||||||
_markRenderDirty();
|
_markRenderDirty();
|
||||||
|
_syncZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ctrl+R: sağ çift tık gibi efektleri sıfırlar ve yatay aynalar; tekrar basınca geri alır.
|
/// Ctrl+R: sağ çift tık gibi efektleri sıfırlar ve yatay aynalar; tekrar basınca geri alır.
|
||||||
@@ -250,6 +297,7 @@ class ImageCanvasState extends State<ImageCanvas> {
|
|||||||
_checkFullResSwap();
|
_checkFullResSwap();
|
||||||
_clamp();
|
_clamp();
|
||||||
_markRenderDirty();
|
_markRenderDirty();
|
||||||
|
_syncZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPanUpdate(DragUpdateDetails d) {
|
void _onPanUpdate(DragUpdateDetails d) {
|
||||||
@@ -502,7 +550,10 @@ class ImageCanvasState extends State<ImageCanvas> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
|
if (constraints.biggest != _vpSize) {
|
||||||
_vpSize = constraints.biggest;
|
_vpSize = constraints.biggest;
|
||||||
|
_syncZoom();
|
||||||
|
}
|
||||||
_clamp();
|
_clamp();
|
||||||
return RepaintBoundary(
|
return RepaintBoundary(
|
||||||
child: ValueListenableBuilder<int>(
|
child: ValueListenableBuilder<int>(
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
name: imajviewer
|
name: imajviewer
|
||||||
description: "Ultra-lightweight image viewer for Linux and Windows."
|
description: "Ultra-lightweight image viewer for Linux and Windows."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.7.0+16
|
version: 1.7.1+17
|
||||||
|
|
||||||
msix_config:
|
msix_config:
|
||||||
display_name: ImajViewer
|
display_name: ImajViewer
|
||||||
|
|||||||
Reference in new issue
Block a user