import 'package:flame/components.dart'; class RuntimeScaleMode { const RuntimeScaleMode._(); static const fit = 'fit'; static const fill = 'fill'; static const stretch = 'stretch'; static const none = 'none'; static const all = {fit, fill, stretch, none}; static bool isSupported(String value) => all.contains(value); } class RuntimeViewportConfig { const RuntimeViewportConfig({ required this.designWidth, required this.designHeight, this.scaleMode = RuntimeScaleMode.fit, }); final double designWidth; final double designHeight; final String scaleMode; Vector2 get designSize => Vector2(designWidth, designHeight); } class RuntimeViewportTransform { const RuntimeViewportTransform({ required this.x, required this.y, required this.width, required this.height, required this.scaleX, required this.scaleY, required this.scaleMode, }); final double x; final double y; final double width; final double height; final double scaleX; final double scaleY; final String scaleMode; Map toMap() { return { 'x': x, 'y': y, 'width': width, 'height': height, 'scaleX': scaleX, 'scaleY': scaleY, 'scaleMode': scaleMode, }; } } class RuntimeViewport { const RuntimeViewport._(); static RuntimeViewportTransform compute({ required Vector2 screenSize, required RuntimeViewportConfig config, }) { final designWidth = config.designWidth; final designHeight = config.designHeight; final screenWidth = screenSize.x; final screenHeight = screenSize.y; if (designWidth <= 0 || designHeight <= 0) { throw const FormatException('Runtime viewport design size must be > 0'); } if (!RuntimeScaleMode.isSupported(config.scaleMode)) { throw FormatException( 'Runtime viewport scaleMode is unsupported: ${config.scaleMode}', ); } final safeScreenWidth = screenWidth <= 0 ? designWidth : screenWidth; final safeScreenHeight = screenHeight <= 0 ? designHeight : screenHeight; final scaleX = safeScreenWidth / designWidth; final scaleY = safeScreenHeight / designHeight; return switch (config.scaleMode) { RuntimeScaleMode.fit => _uniform( designWidth: designWidth, designHeight: designHeight, screenWidth: safeScreenWidth, screenHeight: safeScreenHeight, scale: scaleX < scaleY ? scaleX : scaleY, scaleMode: config.scaleMode, ), RuntimeScaleMode.fill => _uniform( designWidth: designWidth, designHeight: designHeight, screenWidth: safeScreenWidth, screenHeight: safeScreenHeight, scale: scaleX > scaleY ? scaleX : scaleY, scaleMode: config.scaleMode, ), RuntimeScaleMode.stretch => RuntimeViewportTransform( x: 0, y: 0, width: safeScreenWidth, height: safeScreenHeight, scaleX: scaleX, scaleY: scaleY, scaleMode: config.scaleMode, ), RuntimeScaleMode.none => RuntimeViewportTransform( x: (safeScreenWidth - designWidth) / 2, y: (safeScreenHeight - designHeight) / 2, width: designWidth, height: designHeight, scaleX: 1, scaleY: 1, scaleMode: config.scaleMode, ), _ => throw FormatException( 'Runtime viewport scaleMode is unsupported: ${config.scaleMode}', ), }; } static void apply( PositionComponent root, RuntimeViewportTransform transform, ) { root ..position = Vector2(transform.x, transform.y) ..scale = Vector2(transform.scaleX, transform.scaleY) ..size = Vector2(transform.width, transform.height); } static RuntimeViewportTransform _uniform({ required double designWidth, required double designHeight, required double screenWidth, required double screenHeight, required double scale, required String scaleMode, }) { final width = designWidth * scale; final height = designHeight * scale; return RuntimeViewportTransform( x: (screenWidth - width) / 2, y: (screenHeight - height) / 2, width: width, height: height, scaleX: scale, scaleY: scale, scaleMode: scaleMode, ); } }