""" Android Graphics Drawable Shapes 组件 — Shape, ArcShape, OvalShape, PathShape, RectShape, RoundRectShape 方法签名完全对齐 Android SDK (android.graphics.drawable.shapes 包) """ from __future__ import annotations import math from typing import Any, Callable, Dict, List, Optional, Set, Tuple from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException # ═══════════════════════════════════════════════════════════════ # Shape # ═══════════════════════════════════════════════════════════════ class Shape(Stub): """Shape — 形状基类""" def __init__(self): super().__init__() self._width: float = 0.0 self._height: float = 0.0 self._alpha: int = 255 self._visible: bool = True self._dirty: bool = True self._bounds: Tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.0) def getWidth(self) -> float: return self._width def getHeight(self) -> float: return self._height def getAlpha(self) -> int: return self._alpha def setAlpha(self, alpha: int): self._alpha = max(0, min(255, alpha)) def isVisible(self) -> bool: return self._visible def setVisible(self, visible: bool): self._visible = visible def resize(self, width: float, height: float): """调整大小""" self._width = width self._height = height self._bounds = (0.0, 0.0, width, height) self._dirty = True self.onResize(width, height) def onResize(self, width: float, height: float): """大小变化回调""" pass def getBounds(self) -> Tuple[float, float, float, float]: return self._bounds def setBounds(self, left: float, top: float, right: float, bottom: float): self._bounds = (left, top, right, bottom) self._width = right - left self._height = bottom - top def draw(self, canvas: Any, paint: Any): """绘制形状 (由子类实现)""" pass def hasAlpha(self) -> bool: """检查是否有透明度""" return self._alpha < 255 def clone(self) -> Shape: """克隆形状""" new_shape = self.__class__.__new__(self.__class__) new_shape._width = self._width new_shape._height = self._height new_shape._alpha = self._alpha new_shape._visible = self._visible new_shape._bounds = self._bounds return new_shape def isDirty(self) -> bool: return self._dirty def setDirty(self, dirty: bool): self._dirty = dirty def invalidate(self): self._dirty = True def toString(self) -> str: return f"Shape(w={self._width:.1f}, h={self._height:.1f})" # ═══════════════════════════════════════════════════════════════ # RectShape # ═══════════════════════════════════════════════════════════════ class RectShape(Shape): """RectShape — 矩形形状""" def __init__(self): super().__init__() self._corner_radius: Tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.0) self._fill_color: int = 0xFF000000 self._stroke_color: int = 0x00000000 self._stroke_width: float = 0.0 def draw(self, canvas: Any, paint: Any): """绘制矩形""" pass def getCornerRadii(self) -> Tuple[float, float, float, float]: return self._corner_radius def setCornerRadii(self, radii: Tuple[float, float, float, float]): self._corner_radius = radii def getFillColor(self) -> int: return self._fill_color def setFillColor(self, color: int): self._fill_color = color def getStrokeColor(self) -> int: return self._stroke_color def setStrokeColor(self, color: int): self._stroke_color = color def getStrokeWidth(self) -> float: return self._stroke_width def setStrokeWidth(self, width: float): self._stroke_width = width def getCenterX(self) -> float: return self._width / 2.0 def getCenterY(self) -> float: return self._height / 2.0 def containsPoint(self, x: float, y: float) -> bool: """检查是否包含点""" return 0.0 <= x <= self._width and 0.0 <= y <= self._height def clone(self) -> RectShape: r = RectShape() r._width = self._width r._height = self._height r._alpha = self._alpha r._corner_radius = self._corner_radius r._fill_color = self._fill_color r._stroke_color = self._stroke_color r._stroke_width = self._stroke_width r._bounds = self._bounds return r def toString(self) -> str: return f"RectShape(w={self._width:.1f}, h={self._height:.1f})" # ═══════════════════════════════════════════════════════════════ # OvalShape # ═══════════════════════════════════════════════════════════════ class OvalShape(Shape): """OvalShape — 椭圆形状""" def __init__(self): super().__init__() self._fill_color: int = 0xFF000000 self._stroke_color: int = 0x00000000 self._stroke_width: float = 0.0 self._start_angle: float = 0.0 self._sweep_angle: float = 360.0 def draw(self, canvas: Any, paint: Any): """绘制椭圆""" pass def getFillColor(self) -> int: return self._fill_color def setFillColor(self, color: int): self._fill_color = color def getStrokeColor(self) -> int: return self._stroke_color def setStrokeColor(self, color: int): self._stroke_color = color def getStrokeWidth(self) -> float: return self._stroke_width def setStrokeWidth(self, width: float): self._stroke_width = width def getCenterX(self) -> float: return self._width / 2.0 def getCenterY(self) -> float: return self._height / 2.0 def getRadiusX(self) -> float: return self._width / 2.0 def getRadiusY(self) -> float: return self._height / 2.0 def getStartAngle(self) -> float: return self._start_angle def setStartAngle(self, angle: float): self._start_angle = angle def getSweepAngle(self) -> float: return self._sweep_angle def setSweepAngle(self, angle: float): self._sweep_angle = angle def isCircle(self) -> bool: """检查是否为圆形""" return abs(self._width - self._height) < 0.001 def containsPoint(self, x: float, y: float) -> bool: """检查是否包含点""" cx = self.getCenterX() cy = self.getCenterY() rx = self.getRadiusX() ry = self.getRadiusY() if rx <= 0 or ry <= 0: return False dx = (x - cx) / rx dy = (y - cy) / ry return (dx * dx + dy * dy) <= 1.0 def clone(self) -> OvalShape: o = OvalShape() o._width = self._width o._height = self._height o._alpha = self._alpha o._fill_color = self._fill_color o._stroke_color = self._stroke_color o._stroke_width = self._stroke_width o._start_angle = self._start_angle o._sweep_angle = self._sweep_angle o._bounds = self._bounds return o def toString(self) -> str: return f"OvalShape(w={self._width:.1f}, h={self._height:.1f})" # ═══════════════════════════════════════════════════════════════ # ArcShape # ═══════════════════════════════════════════════════════════════ class ArcShape(OvalShape): """ArcShape — 圆弧形状""" def __init__(self, start_angle: float = 0.0, sweep_angle: float = 90.0): super().__init__() self._start_angle = start_angle self._sweep_angle = sweep_angle self._use_center: bool = False def draw(self, canvas: Any, paint: Any): """绘制圆弧""" pass def getStartAngle(self) -> float: return self._start_angle def setStartAngle(self, angle: float): self._start_angle = angle def getSweepAngle(self) -> float: return self._sweep_angle def setSweepAngle(self, angle: float): self._sweep_angle = angle def isUseCenter(self) -> bool: return self._use_center def setUseCenter(self, use_center: bool): self._use_center = use_center def getArcEndX(self) -> float: cx = self.getCenterX() cy = self.getCenterY() rx = self.getRadiusX() ry = self.getRadiusY() angle_rad = math.radians(self._start_angle + self._sweep_angle) return cx + rx * math.cos(angle_rad) def getArcEndY(self) -> float: cx = self.getCenterX() cy = self.getCenterY() rx = self.getRadiusX() ry = self.getRadiusY() angle_rad = math.radians(self._start_angle + self._sweep_angle) return cy + ry * math.sin(angle_rad) def isFullCircle(self) -> bool: return abs(self._sweep_angle) >= 360.0 def clone(self) -> ArcShape: a = ArcShape(self._start_angle, self._sweep_angle) a._width = self._width a._height = self._height a._alpha = self._alpha a._fill_color = self._fill_color a._stroke_color = self._stroke_color a._stroke_width = self._stroke_width a._use_center = self._use_center a._bounds = self._bounds return a def toString(self) -> str: return f"ArcShape(start={self._start_angle:.1f}, sweep={self._sweep_angle:.1f})" # ═══════════════════════════════════════════════════════════════ # PathShape # ═══════════════════════════════════════════════════════════════ class PathShape(Shape): """PathShape — 路径形状""" def __init__(self, path: Any = None, std_width: float = 1.0, std_height: float = 1.0): super().__init__() self._path = path self._std_width = std_width self._std_height = std_height self._fill_color: int = 0xFF000000 self._stroke_color: int = 0x00000000 self._stroke_width: float = 0.0 self._scale_x: float = 1.0 self._scale_y: float = 1.0 def draw(self, canvas: Any, paint: Any): """绘制路径""" pass def getPath(self) -> Any: return self._path def setPath(self, path: Any): self._path = path def getStdWidth(self) -> float: return self._std_width def getStdHeight(self) -> float: return self._std_height def getFillColor(self) -> int: return self._fill_color def setFillColor(self, color: int): self._fill_color = color def getStrokeColor(self) -> int: return self._stroke_color def setStrokeColor(self, color: int): self._stroke_color = color def getStrokeWidth(self) -> float: return self._stroke_width def setStrokeWidth(self, width: float): self._stroke_width = width def onResize(self, width: float, height: float): if self._std_width > 0 and self._std_height > 0: self._scale_x = width / self._std_width self._scale_y = height / self._std_height def getScaleX(self) -> float: return self._scale_x def getScaleY(self) -> float: return self._scale_y def containsPoint(self, x: float, y: float) -> bool: """检查是否包含点""" if self._path is None: return False # 使用简单矩形包含检查 return 0.0 <= x <= self._width and 0.0 <= y <= self._height def clone(self) -> PathShape: p = PathShape(self._path, self._std_width, self._std_height) p._width = self._width p._height = self._height p._alpha = self._alpha p._fill_color = self._fill_color p._stroke_color = self._stroke_color p._stroke_width = self._stroke_width p._scale_x = self._scale_x p._scale_y = self._scale_y p._bounds = self._bounds return p def toString(self) -> str: return f"PathShape(std={self._std_width:.1f}x{self._std_height:.1f})" # ═══════════════════════════════════════════════════════════════ # RoundRectShape # ═══════════════════════════════════════════════════════════════ class RoundRectShape(RectShape): """RoundRectShape — 圆角矩形形状""" def __init__(self, outer_radii: Optional[Tuple[float, float, float, float]] = None, inner_radii: Optional[Tuple[float, float, float, float]] = None, inset: Optional[Tuple[float, float, float, float]] = None): super().__init__() self._outer_radii = outer_radii or (0.0, 0.0, 0.0, 0.0) self._inner_radii = inner_radii or (0.0, 0.0, 0.0, 0.0) self._inset = inset or (0.0, 0.0, 0.0, 0.0) def draw(self, canvas: Any, paint: Any): """绘制圆角矩形""" pass def getOuterRadii(self) -> Tuple[float, float, float, float]: return self._outer_radii def setOuterRadii(self, radii: Tuple[float, float, float, float]): self._outer_radii = radii def getInnerRadii(self) -> Tuple[float, float, float, float]: return self._inner_radii def setInnerRadii(self, radii: Tuple[float, float, float, float]): self._inner_radii = radii def getInset(self) -> Tuple[float, float, float, float]: return self._inset def setInset(self, inset: Tuple[float, float, float, float]): self._inset = inset def getOuterRect(self) -> Tuple[float, float, float, float]: left, top, right, bottom = self._bounds return (left + self._inset[0], top + self._inset[1], right - self._inset[2], bottom - self._inset[3]) def getInnerRect(self) -> Tuple[float, float, float, float]: outer = self.getOuterRect() r = max(self._outer_radii) return (outer[0] + r, outer[1] + r, outer[2] - r, outer[3] - r) def hasInnerShape(self) -> bool: """检查是否有内部形状""" return any(r > 0 for r in self._inner_radii) def getCornerRadius(self) -> float: """获取统一圆角半径 (所有角相同)""" if all(abs(r - self._outer_radii[0]) < 0.001 for r in self._outer_radii): return self._outer_radii[0] return -1.0 def setCornerRadius(self, radius: float): self._outer_radii = (radius, radius, radius, radius) def isRect(self) -> bool: """检查是否为普通矩形""" return all(r == 0.0 for r in self._outer_radii) def clone(self) -> RoundRectShape: rr = RoundRectShape(self._outer_radii, self._inner_radii, self._inset) rr._width = self._width rr._height = self._height rr._alpha = self._alpha rr._fill_color = self._fill_color rr._stroke_color = self._stroke_color rr._stroke_width = self._stroke_width rr._bounds = self._bounds return rr def toString(self) -> str: return f"RoundRectShape(radius={self._outer_radii[0]:.1f})" # ═══════════════════════════════════════════════════════════════ # ShapeUtils # ═══════════════════════════════════════════════════════════════ class ShapeUtils(Stub): """ShapeUtils — 形状工具类""" @staticmethod def createRect(width: float, height: float) -> RectShape: r = RectShape() r.resize(width, height) return r @staticmethod def createOval(width: float, height: float) -> OvalShape: o = OvalShape() o.resize(width, height) return o @staticmethod def createCircle(radius: float) -> OvalShape: o = OvalShape() o.resize(radius * 2, radius * 2) return o @staticmethod def createArc(start_angle: float, sweep_angle: float, width: float, height: float) -> ArcShape: a = ArcShape(start_angle, sweep_angle) a.resize(width, height) return a @staticmethod def createRoundRect(width: float, height: float, corner_radius: float) -> RoundRectShape: rr = RoundRectShape((corner_radius, corner_radius, corner_radius, corner_radius)) rr.resize(width, height) return rr @staticmethod def createPathShape(path: Any, std_width: float, std_height: float) -> PathShape: return PathShape(path, std_width, std_height) @staticmethod def createSquare(size: float) -> RectShape: r = RectShape() r.resize(size, size) return r @staticmethod def applyFill(shape: Shape, color: int): if isinstance(shape, RectShape): shape.setFillColor(color) elif isinstance(shape, OvalShape): shape.setFillColor(color) elif isinstance(shape, PathShape): shape.setFillColor(color) @staticmethod def applyStroke(shape: Shape, color: int, width: float): if isinstance(shape, RectShape): shape.setStrokeColor(color) shape.setStrokeWidth(width) elif isinstance(shape, OvalShape): shape.setStrokeColor(color) shape.setStrokeWidth(width) elif isinstance(shape, PathShape): shape.setStrokeColor(color) shape.setStrokeWidth(width) @staticmethod def getShapeCenter(shape: Shape) -> Tuple[float, float]: return (shape.getWidth() / 2.0, shape.getHeight() / 2.0) @staticmethod def shapeEquals(a: Shape, b: Shape) -> bool: if type(a) is not type(b): return False return (abs(a.getWidth() - b.getWidth()) < 0.001 and abs(a.getHeight() - b.getHeight()) < 0.001 and a.getAlpha() == b.getAlpha()) @staticmethod def scaleShape(shape: Shape, scale_x: float, scale_y: float): shape.resize(shape.getWidth() * scale_x, shape.getHeight() * scale_y) @staticmethod def translateShape(shape: Shape, dx: float, dy: float): b = shape.getBounds() shape.setBounds(b[0] + dx, b[1] + dy, b[2] + dx, b[3] + dy)