1# Copyright 2023 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from __future__ import annotations 6 7import enum 8from argparse import ArgumentTypeError 9from typing import Any, Tuple 10 11from crossbench import compat 12 13 14@enum.unique 15class ViewportMode(compat.StrEnum): 16 SIZE = "size" 17 MAXIMIZED = "maximized" 18 FULLSCREEN = "fullscreen" 19 HEADLESS = "headless" 20 21 22class Viewport: 23 DEFAULT: Viewport 24 MAXIMIZED: Viewport 25 FULLSCREEN: Viewport 26 HEADLESS: Viewport 27 28 @classmethod 29 def parse_sized(cls, value: Any) -> Viewport: 30 if isinstance(value, Viewport): 31 viewport = value 32 elif isinstance(value, str): 33 viewport = cls.parse(value) 34 else: 35 raise ArgumentTypeError(f"Expected str, but got '{type(value)}': {value}") 36 if not viewport.has_size: 37 raise ArgumentTypeError("Expected viewport with explicit size, " 38 f"but got {viewport}") 39 return viewport 40 41 @classmethod 42 def parse(cls, value: str) -> Viewport: 43 if not value: 44 return cls.DEFAULT 45 if value in ("m", "max", "maximised", ViewportMode.MAXIMIZED): 46 return cls.MAXIMIZED 47 if value in ("f", "full", ViewportMode.FULLSCREEN): 48 return cls.FULLSCREEN 49 if value == ViewportMode.HEADLESS: 50 return cls.HEADLESS 51 size, _, position = value.partition(",") 52 width, _, height = size.partition("x") 53 if not height: 54 raise ArgumentTypeError(f"Missing viewport height in input: {value}") 55 x = str(cls.DEFAULT.x) 56 y = str(cls.DEFAULT.y) 57 if position: 58 x, _, y = position.partition("x") 59 if not y: 60 raise ArgumentTypeError( 61 f"Missing viewport y position in input: {value}") 62 return Viewport(int(width), int(height), int(x), int(y)) 63 64 def __init__(self, 65 width: int = 1500, 66 height: int = 1000, 67 x: int = 10, 68 y: int = 50, 69 mode: ViewportMode = ViewportMode.SIZE): 70 self._width = width 71 self._height = height 72 self._x = x 73 self._y = y 74 self._mode = mode 75 self._validate() 76 77 def _validate(self) -> None: 78 if self._mode == ViewportMode.SIZE: 79 if self._width <= 0: 80 raise ArgumentTypeError(f"width must be > 0, but got {self._width}") 81 if self._height <= 0: 82 raise ArgumentTypeError(f"height must be > 0, but got {self._height}") 83 if self._x < 0: 84 raise ArgumentTypeError(f"x must be >= 0, but got {self._x}") 85 if self._y < 0: 86 raise ArgumentTypeError(f"y must be >= 0, but got {self._y}") 87 else: 88 if self._width != 0: 89 raise ArgumentTypeError( 90 "Non-zero width only allowed with ViewportMode.SIZE") 91 if self._height != 0: 92 raise ArgumentTypeError( 93 "Non-zero height only allowed with ViewportMode.SIZE") 94 if self._x != 0: 95 raise ArgumentTypeError( 96 "Non-zero x only allowed with ViewportMode.SIZE") 97 if self._y != 0: 98 raise ArgumentTypeError( 99 "Non-zero y only allowed with ViewportMode.SIZE") 100 101 @property 102 def is_default(self) -> bool: 103 return self is Viewport.DEFAULT 104 105 @property 106 def is_maximized(self) -> bool: 107 return self._mode == ViewportMode.MAXIMIZED 108 109 @property 110 def is_fullscreen(self) -> bool: 111 return self._mode == ViewportMode.FULLSCREEN 112 113 @property 114 def is_headless(self) -> bool: 115 return self._mode == ViewportMode.HEADLESS 116 117 @property 118 def has_size(self) -> bool: 119 return self._mode == ViewportMode.SIZE 120 121 @property 122 def position(self) -> Tuple[int, int]: 123 assert self.has_size, f"Viewport has no explicit size: {self._mode}" 124 return (self._x, self._y) 125 126 @property 127 def size(self) -> Tuple[int, int]: 128 assert self.has_size, f"Viewport has no explicit size: {self._mode}" 129 return (self._width, self._height) 130 131 @property 132 def width(self) -> int: 133 assert self.has_size, f"Viewport has no explicit size: {self._mode}" 134 return self._width 135 136 @property 137 def height(self) -> int: 138 assert self.has_size, f"Viewport has no explicit size: {self._mode}" 139 return self._height 140 141 @property 142 def x(self) -> int: 143 assert self.has_size, f"Viewport has no explicit size: {self._mode}" 144 return self._x 145 146 @property 147 def y(self) -> int: 148 assert self.has_size, f"Viewport has no explicit size: {self._mode}" 149 return self._y 150 151 @property 152 def mode(self) -> ViewportMode: 153 return self._mode 154 155 @property 156 def key(self) -> Tuple[Tuple, ...]: 157 return ( 158 ("mode", str(self.mode)), 159 ("x", self._x), 160 ("y", self._y), 161 ("width", self._width), 162 ("height", self._height), 163 ) 164 165 def __str__(self) -> str: 166 if self.has_size: 167 return f"Viewport({self.width}x{self.height},{self.x}x{self.y})" 168 return f"Viewport({self.mode})" 169 170 def __eq__(self, other) -> bool: 171 if not isinstance(other, Viewport): 172 return False 173 if self is other: 174 return True 175 return self.key == other.key 176 177 178Viewport.DEFAULT = Viewport() 179Viewport.MAXIMIZED = Viewport(0, 0, 0, 0, ViewportMode.MAXIMIZED) 180Viewport.FULLSCREEN = Viewport(0, 0, 0, 0, ViewportMode.FULLSCREEN) 181Viewport.HEADLESS = Viewport(0, 0, 0, 0, ViewportMode.HEADLESS) 182