fixed scintillating thumbnail borders & selection overlay layout

This commit is contained in:
Thibault Deckers 2022-01-07 18:27:14 +09:00
parent c8c2537996
commit 14385eeadd
9 changed files with 58 additions and 40 deletions

View file

@ -25,7 +25,7 @@ class EntryListDetails extends StatelessWidget {
return Container( return Container(
padding: EntryListDetailsTheme.contentPadding, padding: EntryListDetailsTheme.contentPadding,
foregroundDecoration: BoxDecoration( foregroundDecoration: BoxDecoration(
border: Border(top: AvesBorder.side), border: Border(top: AvesBorder.straightSide),
), ),
margin: EntryListDetailsTheme.contentMargin, margin: EntryListDetailsTheme.contentMargin,
child: IconTheme.merge( child: IconTheme.merge(

View file

@ -6,12 +6,22 @@ class AvesBorder {
static const borderColor = Colors.white30; static const borderColor = Colors.white30;
// directly uses `devicePixelRatio` as it never changes, to avoid visiting ancestors via `MediaQuery` // directly uses `devicePixelRatio` as it never changes, to avoid visiting ancestors via `MediaQuery`
static double get borderWidth => window.devicePixelRatio > 2 ? 0.5 : 1.0;
static BorderSide get side => BorderSide( // 1 device pixel for straight lines is fine
static double get straightBorderWidth => 1 / window.devicePixelRatio;
// 1 device pixel for curves is too thin
static double get curvedBorderWidth => window.devicePixelRatio > 2 ? 0.5 : 1.0;
static BorderSide get straightSide => BorderSide(
color: borderColor, color: borderColor,
width: borderWidth, width: straightBorderWidth,
); );
static Border get border => Border.fromBorderSide(side); static BorderSide get curvedSide => BorderSide(
color: borderColor,
width: curvedBorderWidth,
);
static Border get border => Border.fromBorderSide(curvedSide);
} }

View file

@ -30,8 +30,9 @@ class GridItemSelectionOverlay<T> extends StatelessWidget {
? OverlayIcon( ? OverlayIcon(
key: ValueKey(isSelected), key: ValueKey(isSelected),
icon: isSelected ? AIcons.selected : AIcons.unselected, icon: isSelected ? AIcons.selected : AIcons.unselected,
margin: EdgeInsets.zero,
) )
: const SizedBox.shrink(); : const SizedBox();
child = AnimatedSwitcher( child = AnimatedSwitcher(
duration: duration, duration: duration,
switchInCurve: Curves.easeOutBack, switchInCurve: Curves.easeOutBack,

View file

@ -50,8 +50,8 @@ class AnimatedImageIcon extends StatelessWidget {
} }
} }
class GeotiffIcon extends StatelessWidget { class GeoTiffIcon extends StatelessWidget {
const GeotiffIcon({Key? key}) : super(key: key); const GeoTiffIcon({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -181,12 +181,15 @@ class OverlayIcon extends StatelessWidget {
final IconData icon; final IconData icon;
final String? text; final String? text;
final double iconScale; final double iconScale;
final EdgeInsets margin;
const OverlayIcon({ const OverlayIcon({
Key? key, Key? key,
required this.icon, required this.icon,
this.iconScale = 1, this.iconScale = 1,
this.text, this.text,
// default margin for multiple icons in a `Column`
this.margin = const EdgeInsets.only(left: 1, right: 1, bottom: 1),
}) : super(key: key); }) : super(key: key);
@override @override
@ -211,7 +214,7 @@ class OverlayIcon extends StatelessWidget {
); );
return Container( return Container(
margin: const EdgeInsets.only(left: 1, right: 1, bottom: 1), margin: margin,
padding: text != null ? EdgeInsets.only(right: size / 4) : null, padding: text != null ? EdgeInsets.only(right: size / 4) : null,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xBB000000), color: const Color(0xBB000000),

View file

@ -13,7 +13,7 @@ class DecoratedThumbnail extends StatelessWidget {
final Object? Function()? heroTagger; final Object? Function()? heroTagger;
static final Color borderColor = Colors.grey.shade700; static final Color borderColor = Colors.grey.shade700;
static final double borderWidth = AvesBorder.borderWidth; static final double borderWidth = AvesBorder.straightBorderWidth;
const DecoratedThumbnail({ const DecoratedThumbnail({
Key? key, Key? key,
@ -27,12 +27,10 @@ class DecoratedThumbnail extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final imageExtent = tileExtent - borderWidth * 2;
final isSvg = entry.isSvg; final isSvg = entry.isSvg;
Widget child = ThumbnailImage( Widget child = ThumbnailImage(
entry: entry, entry: entry,
extent: imageExtent, extent: tileExtent,
cancellableNotifier: cancellableNotifier, cancellableNotifier: cancellableNotifier,
heroTag: heroTagger?.call(), heroTag: heroTagger?.call(),
); );
@ -42,13 +40,19 @@ class DecoratedThumbnail extends StatelessWidget {
children: [ children: [
child, child,
if (!isSvg) ThumbnailEntryOverlay(entry: entry), if (!isSvg) ThumbnailEntryOverlay(entry: entry),
if (selectable) GridItemSelectionOverlay(item: entry), if (selectable)
GridItemSelectionOverlay<AvesEntry>(
item: entry,
padding: const EdgeInsets.all(2),
),
if (highlightable) ThumbnailHighlightOverlay(entry: entry), if (highlightable) ThumbnailHighlightOverlay(entry: entry),
], ],
); );
return Container( return Container(
decoration: BoxDecoration( // `decoration` with sub logical pixel width yields scintillating borders
// so we use `foregroundDecoration` instead
foregroundDecoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide( border: Border.fromBorderSide(BorderSide(
color: borderColor, color: borderColor,
width: borderWidth, width: borderWidth,

View file

@ -28,7 +28,7 @@ class ThumbnailEntryOverlay extends StatelessWidget {
const AnimatedImageIcon() const AnimatedImageIcon()
else ...[ else ...[
if (entry.isRaw && context.select<GridThemeData, bool>((t) => t.showRaw)) const RawIcon(), if (entry.isRaw && context.select<GridThemeData, bool>((t) => t.showRaw)) const RawIcon(),
if (entry.isGeotiff) const GeotiffIcon(), if (entry.isGeotiff) const GeoTiffIcon(),
if (entry.is360) const SphericalImageIcon(), if (entry.is360) const SphericalImageIcon(),
], ],
if (entry.isMultiPage) ...[ if (entry.isMultiPage) ...[
@ -36,7 +36,7 @@ class ThumbnailEntryOverlay extends StatelessWidget {
if (!entry.isMotionPhoto) MultiPageIcon(entry: entry), if (!entry.isMotionPhoto) MultiPageIcon(entry: entry),
], ],
]; ];
if (children.isEmpty) return const SizedBox.shrink(); if (children.isEmpty) return const SizedBox();
if (children.length == 1) return children.first; if (children.length == 1) return children.first;
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View file

@ -37,7 +37,7 @@ class FilterListDetails<T extends CollectionFilter> extends StatelessWidget {
return Container( return Container(
padding: FilterListDetailsTheme.contentPadding, padding: FilterListDetailsTheme.contentPadding,
foregroundDecoration: BoxDecoration( foregroundDecoration: BoxDecoration(
border: Border(top: AvesBorder.side), border: Border(top: AvesBorder.straightSide),
), ),
margin: FilterListDetailsTheme.contentMargin, margin: FilterListDetailsTheme.contentMargin,
child: Column( child: Column(

View file

@ -38,7 +38,7 @@ class OverlayButton extends StatelessWidget {
} }
// icon (24) + icon padding (8) + button padding (16) + border (1 or 2) // icon (24) + icon padding (8) + button padding (16) + border (1 or 2)
static double getSize(BuildContext context) => 48.0 + AvesBorder.borderWidth * 2; static double getSize(BuildContext context) => 48.0 + AvesBorder.curvedBorderWidth * 2;
} }
class OverlayTextButton extends StatelessWidget { class OverlayTextButton extends StatelessWidget {
@ -71,7 +71,7 @@ class OverlayTextButton extends StatelessWidget {
foregroundColor: MaterialStateProperty.all<Color>(Colors.white), foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
overlayColor: MaterialStateProperty.all<Color>(Colors.white.withOpacity(0.12)), overlayColor: MaterialStateProperty.all<Color>(Colors.white.withOpacity(0.12)),
minimumSize: _minSize, minimumSize: _minSize,
side: MaterialStateProperty.all<BorderSide>(AvesBorder.side), side: MaterialStateProperty.all<BorderSide>(AvesBorder.curvedSide),
shape: MaterialStateProperty.all<OutlinedBorder>(const RoundedRectangleBorder( shape: MaterialStateProperty.all<OutlinedBorder>(const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(_borderRadius)), borderRadius: BorderRadius.all(Radius.circular(_borderRadius)),
)), )),

View file

@ -119,14 +119,14 @@ packages:
name: connectivity_plus name: connectivity_plus
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.2.0"
connectivity_plus_linux: connectivity_plus_linux:
dependency: transitive dependency: transitive
description: description:
name: connectivity_plus_linux name: connectivity_plus_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.1" version: "1.2.0"
connectivity_plus_macos: connectivity_plus_macos:
dependency: transitive dependency: transitive
description: description:
@ -140,14 +140,14 @@ packages:
name: connectivity_plus_platform_interface name: connectivity_plus_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.1" version: "1.2.0"
connectivity_plus_web: connectivity_plus_web:
dependency: transitive dependency: transitive
description: description:
name: connectivity_plus_web name: connectivity_plus_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0+1" version: "1.2.0"
connectivity_plus_windows: connectivity_plus_windows:
dependency: transitive dependency: transitive
description: description:
@ -210,21 +210,21 @@ packages:
name: device_info_plus name: device_info_plus
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.2.0" version: "3.2.1"
device_info_plus_linux: device_info_plus_linux:
dependency: transitive dependency: transitive
description: description:
name: device_info_plus_linux name: device_info_plus_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
device_info_plus_macos: device_info_plus_macos:
dependency: transitive dependency: transitive
description: description:
name: device_info_plus_macos name: device_info_plus_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.0" version: "2.2.1"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -245,7 +245,7 @@ packages:
name: device_info_plus_windows name: device_info_plus_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@ -447,7 +447,7 @@ packages:
name: github name: github
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.3.0" version: "8.5.0"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@ -475,7 +475,7 @@ packages:
name: google_maps_flutter_platform_interface name: google_maps_flutter_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.3" version: "2.1.4"
highlight: highlight:
dependency: transitive dependency: transitive
description: description:
@ -734,7 +734,7 @@ packages:
name: path_provider_platform_interface name: path_provider_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.2"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
@ -797,7 +797,7 @@ packages:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.1.0"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -839,7 +839,7 @@ packages:
name: provider name: provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.1" version: "6.0.2"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@ -867,28 +867,28 @@ packages:
name: screen_brightness name: screen_brightness
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.2+2" version: "0.1.3"
screen_brightness_android: screen_brightness_android:
dependency: transitive dependency: transitive
description: description:
name: screen_brightness_android name: screen_brightness_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1" version: "0.0.3"
screen_brightness_ios: screen_brightness_ios:
dependency: transitive dependency: transitive
description: description:
name: screen_brightness_ios name: screen_brightness_ios
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.2" version: "0.0.4"
screen_brightness_platform_interface: screen_brightness_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: screen_brightness_platform_interface name: screen_brightness_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.2" version: "0.0.3"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1012,7 +1012,7 @@ packages:
name: sqflite_common name: sqflite_common
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.2.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -1161,7 +1161,7 @@ packages:
name: url_launcher_platform_interface name: url_launcher_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.4" version: "2.0.5"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
@ -1231,7 +1231,7 @@ packages:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.1" version: "2.3.3"
wkt_parser: wkt_parser:
dependency: transitive dependency: transitive
description: description: