def minimum_size(self, component: Label) -> Dimension: (width, height) = self.extents(component).tuple (top, right, bottom, left) = self.padding(component) return Dimension(width + left + right, height + top + bottom)
def test_bounds_size(self): self.assertEqual(Dimension(100, 200), Bounds(10, 20, 100, 200).size)
def _reduce_size(self, s1: Dimension, s2: Dimension) -> Dimension: return Dimension(max(s1.width, s2.width), s1.height + s2.height)
def test_with_padding(padding: Insets): with Label(self.context) as label: calculated = [] label.set_insets(StyleKeys.Padding, padding) label.validate() pw = padding.left + padding.right ph = padding.top + padding.bottom rv.observe(label.preferred_size).subscribe(calculated.append) self.assertEqual(Nothing, label.preferred_size_override) self.assertEqual(Dimension(pw, ph), label.preferred_size) self.assertEqual([Dimension(pw, ph)], calculated) label.text = "Test" label.validate() self.assertEqual(2, len(calculated)) self.assertEqual(Nothing, label.preferred_size_override) self.assertAlmostEqual(20.02 + pw, label.preferred_size.width, delta=TextTolerance) self.assertAlmostEqual(7.227 + ph, label.preferred_size.height, delta=TextTolerance) self.assertEqual(2, len(calculated)) self.assertAlmostEqual(20.02 + pw, calculated[1].width, delta=TextTolerance) self.assertAlmostEqual(7.227 + ph, calculated[1].height, delta=TextTolerance) label.text_size = 15 label.validate() self.assertEqual(3, len(calculated)) self.assertEqual(Nothing, label.preferred_size_override) self.assertAlmostEqual(30.03 + pw, label.preferred_size.width, delta=TextTolerance) self.assertAlmostEqual(10.840 + ph, label.preferred_size.height, delta=TextTolerance) self.assertEqual(3, len(calculated)) self.assertAlmostEqual(30.03 + pw, calculated[2].width, delta=TextTolerance) self.assertAlmostEqual(10.840 + ph, calculated[2].height, delta=TextTolerance) label.preferred_size_override = Some(Dimension(80, 50)) label.validate() self.assertEqual(Some(Dimension(80, 50)), label.preferred_size_override) self.assertEqual(Dimension(80, 50), label.preferred_size) self.assertEqual(4, len(calculated)) self.assertEqual(Dimension(80, 50), calculated[3]) label.preferred_size_override = Some(Dimension(10, 10)) label.validate() self.assertEqual(calculated[2], label.preferred_size) self.assertEqual(5, len(calculated)) self.assertEqual(calculated[2], calculated[4]) label.minimum_size_override = Some(Dimension(400, 360)) label.validate() self.assertEqual(Dimension(400, 360), label.preferred_size) self.assertEqual(6, len(calculated)) self.assertEqual(Dimension(400, 360), calculated[5])
def _to_size(self, value: float) -> Dimension: return Dimension(0, value)
class Component(Drawable, StyleResolver, MouseEventHandler, EventDispatcher, ContextAware, ReactiveObject): visible: RP[bool] = rv.new_property() parent: RP[Maybe[Container]] = rv.from_value(Nothing) offset: RV[Point] = parent.as_view().map(lambda _, parent: parent.map( lambda p: rx.combine_latest(p.observe("offset"), p.observe("location")) .pipe(ops.map(lambda v: v[0] + v[1]))).or_else_call(lambda: rx.of( Point(0, 0)))).pipe(lambda _: (ops.exclusive(), )) _minimum_size: RP[Dimension] = rv.from_value(Dimension(0, 0)) _preferred_size: RP[Dimension] = rv.from_value(Dimension(0, 0)) minimum_size_override: RP[Maybe[Dimension]] = rv.from_value(Nothing) minimum_size: RV[Dimension] = rv.combine_latest( _minimum_size, minimum_size_override)(ops.pipe(ops.map(lambda v: v[1].value_or(v[0])), ops.distinct_until_changed())) preferred_size_override: RP[Maybe[Dimension]] = rv.from_value( Nothing).pipe(lambda o: (ops.combine_latest(o.observe("minimum_size")), ops.map(lambda t: t[0].map(lambda v: t[1].copy( width=max(v.width, t[1].width), height=max(v.height, t[1].height)))), ops.distinct_until_changed())) preferred_size: RV[Dimension] = rv.combine_latest( _preferred_size, preferred_size_override, minimum_size)(ops.pipe( ops.map(lambda v: (v[1].value_or(v[0]), v[2])), ops.map(lambda v: v[0].copy(width=max(v[0].width, v[1].width), height=max(v[0].height, v[1].height))), ops.distinct_until_changed())) bounds: RP[Bounds] = Bounded.bounds.pipe(lambda o: ( ops.combine_latest(o.observe("minimum_size")), ops.map(lambda v: v[0].copy(width=max(v[0].width, v[1].width), height=max(v[0].height, v[1].height))), ops.start_with(o.preferred_size))) def __init__(self, context: Context, visible: bool = True) -> None: if context is None: raise ValueError("Argument 'context' is required.") # noinspection PyTypeChecker self.visible = visible self._context = context self._valid = False self._ui = self.create_ui() assert self._ui is not None super().__init__() self.validate() self.ui \ .on_invalidate(self) \ .pipe(ops.take_until(self.on_dispose)) \ .subscribe(lambda _: self.invalidate(), on_error=self.error_handler) @property def context(self) -> Context: return self._context @property def ui(self) -> ComponentUI: return self._ui @property def look_and_feel(self) -> LookAndFeel: return self.context.look_and_feel def create_ui(self) -> ComponentUI: return self.context.look_and_feel.create_ui(self) def show(self) -> None: # noinspection PyTypeChecker self.visible = True def hide(self) -> None: # noinspection PyTypeChecker self.visible = False @property def valid(self) -> bool: return self._valid # noinspection PyTypeChecker def validate(self, force: bool = False) -> None: if self.visible and (not self.valid or force): self._minimum_size = self.ui.minimum_size(self) self._preferred_size = self.ui.preferred_size(self) self._valid = True self.parent.map(lambda p: p.request_layout()) def invalidate(self) -> None: self._valid = False self.parent.map(lambda p: p.invalidate()) def draw(self, g: Graphics) -> None: if self.visible: g.save() (dx, dy) = self.parent.map(lambda p: p.location).value_or(Point(0, 0)) (cx, cy, cw, ch) = self.ui.clip_bounds(self).tuple g.translate(dx, dy) g.rectangle(cx, cy, cw, ch) g.clip() try: self.draw_component(g) except BaseException as e: self.error_handler(e) g.restore() def draw_component(self, g: Graphics) -> None: self.ui.draw(g, self) def position_of(self, event: PositionalEvent) -> Point: if event is None: raise ValueError("Argument 'event' is required.") return event.position - self.offset @property def inputs(self) -> Mapping[str, Input]: return self.context.inputs @property def parent_dispatcher(self) -> Maybe[EventDispatcher]: # noinspection PyTypeChecker return self.parent def __repr__(self) -> Any: return str({"id": id(self), "type": type(self).__name__})
def minimum_size(self, component: T) -> Dimension: return Dimension(0, 0)
def merge(s1: Dimension, s2: Dimension): return Dimension(max(s1.width, s2.width), max(s1.height, s2.height))
def preferred_size(self, component: Canvas) -> Dimension: (width, height) = component.image.map(lambda i: i.size.tuple).value_or( (0, 0)) (top, right, bottom, left) = self.padding(component).tuple return Dimension(width + left + right, height + top + bottom)