def resize(self, available: Size): """ Sets the view's frame size, taking into account content size, padding, and borders. """ available = self.env.constrain(available) max_w = 0 max_h = 0 inside = Size( max(0, available.w - self.env.padding.width - self.env.border.width), max(0, available.h - self.env.padding.height - self.env.border.height), ) for view in self.subviews: view.resize(inside) max_w = max(max_w, view.frame.width) max_h = max(max_h, view.frame.height) size = self.content_size(inside) max_w = max(max_w, size.w) max_h = max(max_h, size.h) self.frame.size = Size( max_w + self.env.padding.width + self.env.border.width, max_h + self.env.padding.height + self.env.border.height, ) return Size(max_w, max_h)
def test_rect_insets(self): r1 = Rect(origin=(100, 100), size=(150, 50)) r2 = r1 + Insets(10) self.assertEqual(r2.origin, Point(90, 90)) self.assertEqual(r2.size, Size(170, 70)) r3 = r1 - Insets(10) self.assertEqual(r3.origin, Point(110, 110)) self.assertEqual(r3.size, Size(130, 30))
def constrain(self, available=None): if available is None: available = self.image_size box = self.env.constrain(available, self.image_size, clamped=self.clamped) if self.stretch: return box pct_w = box.w / self.image_size.w pct_h = box.h / self.image_size.h if pct_w < pct_h: return Size(box.w, int(self.image_size.h * pct_w)) else: return Size(int(self.image_size.w * pct_h), box.h)
def load_surface(self): if self._surface is None: if self.path: self._surface = IMG_Load(self.path.encode("utf-8")) else: self._surface = IMG_Load_RW(self.rw, 0) self._size = Size(self._surface.contents.w, self._surface.contents.h)
def size(self, width=None, height=None): if isinstance(width, int): width = self.env.scaled(width) if isinstance(height, int): height = self.env.scaled(height) self.env.size = Size(width or self.env.size.w, height or self.env.size.h) return self
def constrain(self, available, value=None, clamped=True): final = list(value or available) for a in Axis: v = self.size[a] if v: final[a] = v if isinstance(v, int) else int(v * available[a]) if clamped: final[a] = clamp(final[a], 0, available[a]) return Size(*final)
def content_size(self, available): if self.env.lines > 0: h = self._font.line_height * self.env.lines else: size = self._font.measure(self.text.value, width=available.w) h = max(size.h, available.h) # TODO: be smarter about when to invalidate this self._line_cache = None return Size(available.w, h)
def knob_size(self): return Size( max(self.track_size, int(self.frame.width * self.frame.width / self.scroll_size[Axis.HORIZONTAL])) if self.scroll_size[Axis.HORIZONTAL] > self.frame.width else 0, max(self.track_size, int(self.frame.height * self.frame.height / self.scroll_size[Axis.VERTICAL])) if self.scroll_size[Axis.VERTICAL] > self.frame.height else 0, )
def __init__(self, *contents, axis=Axis.VERTICAL, **options): super().__init__(*contents, **options) self.axis = Axis(axis) if axis is not None else None self.scroll_size = Size() self.scroll_interval = self.env.scaled(20) self.tracking = None self.offset = 0 self.track_size = self.env.scaled(15) # Local state, not overwritten when re-using the view. self._position = Point()
def minimum_size(self): # Not sure a minimum size calculation is useful for this without knowing what's available. return Size() # Minimum size for a Grid is a single row/column (based on Axis). main = self.spacing[self.axis] * (len(self.subviews) - 1) cross = 0 for view in self.subviews: min_size = view.minimum_size() main += min_size[self.axis] + view.env.padding[ self.axis] + view.env.border[self.axis] cross = max( cross, min_size[self.cross] + view.env.padding[self.cross] + view.env.border[self.cross]) return self.axis.size(main, cross)
def minimum_size(self): """ Returns the minimum size in each dimension of this view's content, not including any padding or borders. """ min_w = 0 min_h = 0 for view in self.subviews: m = view.minimum_size() min_w = max( m.w, min_w + view.env.padding[Axis.HORIZONTAL] + view.env.border[Axis.HORIZONTAL]) min_h = max( m.h, min_h + view.env.padding[Axis.VERTICAL] + view.env.border[Axis.VERTICAL]) return Size(min_w, min_h)
def resize(self, available: Size): available = self.env.constrain(available) # Need to account for scrollbars when resizing subviews. h = self.track_size if self.axis in (Axis.HORIZONTAL, None) else 0 w = self.track_size if self.axis in (Axis.VERTICAL, None) else 0 inside = Size( max(0, available.w - self.env.padding.width - self.env.border.width - w), max(0, available.h - self.env.padding.height - self.env.border.height - h), ) self.scroll_size = super().resize(inside) self.frame.size = available # Clamp scroll positions when resizing. for axis in Axis: self.set_position(axis) return self.scroll_size
def load(self, class_name): for key, value in self.theme.env(class_name.lower()).items(): if key in ("padding", "border"): value = Insets(*value).scaled(self.scale) elif key in ("color", "background", "border_color", "text_shadow"): value = sdl2.SDL_Color(*value) elif key == "priority": value = Priority[value.upper()] elif key == "position": value = Position[value.upper()] elif key == "alignment": value = Alignment[value.upper()] elif key == "spacing": value = self.scaled(value) elif key == "size": value = Size(self.scaled(value[0]), self.scaled(value[1])) elif key == "opacity": value = clamp(float(value), 0.0, 1.0) setattr(self, key, value)
class Environment: theme = Env(inherit=True, default=Theme(os.path.join(BASE_DIR, "themes/dark/config.json"))) scale = Env(inherit=True, default=1.0) font = Env(inherit=True, default="default") font_size = Env(inherit=True) text_shadow = Env(inherit=True) color = Env(default=sdl2.SDL_Color(230, 230, 230), inherit=True) background = Env() radius = Env(default=0) padding = Env(default=Insets(0)) border = Env(default=Insets(0)) border_color = Env() priority = Env(default=Priority.NORMAL) position = Env(default=Position.CENTER) alignment = Env(default=Alignment.CENTER) spacing = Env(default=0) size = Env(default=Size()) opacity = Env(default=1.0, inherit=True) animation = Env(inherit=True) lines = Env(default=1) alpha = property(lambda self: round(self.opacity * 255)) blended_color = property( lambda self: sdl2.SDL_Color(self.color.r, self.color.g, self.color.b, round((self.color.a * self.alpha) / 255))) def __init__(self, class_name=None, **overrides): self.parent = None if class_name: self.load(class_name) for key, value in overrides.items(): setattr(self, key, value) def inherit(self, parent): self.parent = parent def scaled(self, value): if value is None: return None return value.__class__(value * self.scale) def constrain(self, available, value=None, clamped=True): final = list(value or available) for a in Axis: v = self.size[a] if v: final[a] = v if isinstance(v, int) else int(v * available[a]) if clamped: final[a] = clamp(final[a], 0, available[a]) return Size(*final) def load(self, class_name): for key, value in self.theme.env(class_name.lower()).items(): if key in ("padding", "border"): value = Insets(*value).scaled(self.scale) elif key in ("color", "background", "border_color", "text_shadow"): value = sdl2.SDL_Color(*value) elif key == "priority": value = Priority[value.upper()] elif key == "position": value = Position[value.upper()] elif key == "alignment": value = Alignment[value.upper()] elif key == "spacing": value = self.scaled(value) elif key == "size": value = Size(self.scaled(value[0]), self.scaled(value[1])) elif key == "opacity": value = clamp(float(value), 0.0, 1.0) setattr(self, key, value) def draw(self, renderer, asset_name, rect, alpha=None): # Mostly lives here because it's where I have access to the theme and the scale, not because it's a good place # for it to live. asset = self.theme.load_asset(asset_name) if alpha is None: alpha = self.alpha asset.render(renderer, rect, alpha=alpha, scale=self.scale)
def minimum_size(self): return Size(self.track_size * 2, self.track_size * 2)
def content_size(self, available: Size): return self.env.constrain(Size(self.d, self.d))
def minimum_size(self): return Size(self.env.scaled(40), self.env.scaled(20))
def content_size(self, available): return Size(available.w, self.env.scaled(20))
def content_size(self, available: Size): return Size(available.w, 0) if self.parent.axis == Axis.HORIZONTAL else Size( 0, available.h)
def minimum_size(self): size = self._font.measure(self.placeholder) return Size(size.w, size.h * max(1, self.env.lines))