def test_align(self): window = Frame(self.context) window.bounds = Bounds(0, 0, 100, 100) label = Label(self.context) label.text = "AlleyCat" label.text_size = 18 label.bounds = Bounds(0, 0, 100, 100) window.add(label) self.context.process() self.assertImage("align_default", self.context, tolerance=Tolerance) for align in TextAlign: for vertical_align in TextAlign: label.text_align = align label.text_vertical_align = vertical_align test_name = f"align_{align}_{vertical_align}".replace( "TextAlign.", "") self.context.process() self.assertImage(test_name, self.context, tolerance=Tolerance)
def test_component_at_with_layers(self): parent = Container(self.context) parent.bounds = Bounds(0, 0, 200, 200) bottom = Container(self.context) bottom.bounds = Bounds(0, 0, 100, 100) middle = Container(self.context) middle.bounds = Bounds(100, 100, 100, 100) top = Container(self.context) top.bounds = Bounds(50, 50, 100, 100) parent.add(bottom) parent.add(middle) parent.add(top) self.assertEqual(Some(parent), parent.component_at(Point(200, 0))) self.assertEqual(Some(parent), parent.component_at(Point(0, 200))) self.assertEqual(Some(bottom), parent.component_at(Point(0, 0))) self.assertEqual(Some(bottom), parent.component_at(Point(100, 0))) self.assertEqual(Some(bottom), parent.component_at(Point(0, 100))) self.assertEqual(Some(middle), parent.component_at(Point(200, 100))) self.assertEqual(Some(middle), parent.component_at(Point(200, 200))) self.assertEqual(Some(middle), parent.component_at(Point(100, 200))) self.assertEqual(Some(top), parent.component_at(Point(100, 100))) self.assertEqual(Some(top), parent.component_at(Point(150, 150))) self.assertEqual(Some(top), parent.component_at(Point(150, 50))) self.assertEqual(Some(top), parent.component_at(Point(50, 150)))
def test_drag_overlapping(self): bottom = Frame(self.context) bottom.draggable = True bottom.bounds = Bounds(10, 10, 50, 50) bottom.set_color(StyleKeys.Background, RGBA(1, 0, 0, 1)) top = Frame(self.context) top.draggable = True top.bounds = Bounds(20, 20, 50, 50) top.set_color(StyleKeys.Background, RGBA(0, 0, 1, 1)) self.mouse.move_to(Point(30, 30)) self.mouse.press(MouseButton.LEFT) self.mouse.move_to(Point(50, 50)) self.mouse.release(MouseButton.LEFT) self.context.process() self.assertImage("drag_overlapping_top", self.context) self.mouse.move_to(Point(20, 20)) self.mouse.press(MouseButton.LEFT) self.mouse.move_to(Point(40, 40)) self.mouse.release(MouseButton.LEFT) self.context.process() self.assertImage("drag_overlapping_bottom", self.context)
def test_layout_with_insets(self): layout = StackLayout() container = Frame(self.context, layout) container.bounds = Bounds(0, 0, 100, 100) child1 = Panel(self.context) child1.bounds = Bounds(10, 10, 20, 20) child1.set_color(StyleKeys.Background, RGBA(1, 0, 0, 1)) child2 = Panel(self.context) child2.set_color(StyleKeys.Background, RGBA(0, 0, 1, 1)) child2.preferred_size_override = Some(Dimension(80, 60)) container.add(child1) container.add(child2, fill=False) def test(padding: Insets): layout.padding = padding self.assertEqual(True, container.layout_pending) self.context.process() self.assertEqual(False, container.layout_pending) name = f"stack-{padding.top},{padding.right},{padding.bottom},{padding.left}" self.assertImage(name, self.context) for p in [ Insets(10, 10, 10, 10), Insets(15, 0, 15, 0), Insets(0, 10, 20, 0) ]: test(p)
def test_align(self): window = Window(self.context) window.bounds = Bounds(0, 0, 100, 100) button = LabelButton(self.context) button.text = "AlleyCat" button.text_size = 16 button.bounds = Bounds(0, 0, 100, 100) window.add(button) self.context.process() self.assertImage("align_default", self.context, tolerance=Tolerance) for align in TextAlign: for vertical_align in TextAlign: button.text_align = align button.text_vertical_align = vertical_align test_name = f"align_{align}_{vertical_align}".replace( "TextAlign.", "") self.context.process() self.assertImage(test_name, self.context, tolerance=Tolerance)
def test_drag(self): window = Frame(self.context) window.draggable = True window.resizable = True window.bounds = Bounds(10, 10, 50, 50) self.mouse.move_to(Point(30, 30)) self.mouse.press(MouseButton.RIGHT) self.mouse.move_to(Point(40, 40)) self.mouse.release(MouseButton.RIGHT) self.context.process() self.assertImage("drag_with_right_button", self.context) window.bounds = Bounds(10, 10, 50, 50) self.mouse.move_to(Point(20, 20)) self.mouse.press(MouseButton.LEFT) self.mouse.move_to(Point(30, 40)) self.mouse.release(MouseButton.LEFT) self.context.process() self.assertImage("drag_with_left_button", self.context) window.bounds = Bounds(10, 10, 50, 50) self.mouse.move_to(Point(10, 10)) self.mouse.press(MouseButton.LEFT) self.mouse.press(MouseButton.MIDDLE) self.mouse.move_to(Point(40, 30)) self.mouse.release(MouseButton.MIDDLE) self.mouse.move_to(Point(20, 50)) self.context.process() self.assertImage("drag_with_2_buttons", self.context) self.mouse.release(MouseButton.LEFT) window.bounds = Bounds(10, 10, 50, 50) window.draggable = False self.mouse.move_to(Point(30, 30)) self.mouse.press(MouseButton.LEFT) self.mouse.move_to(Point(50, 30)) self.mouse.release(MouseButton.LEFT) self.context.process() self.assertImage("drag_non_draggable", self.context)
def test_layout(self): container = Frame(self.context, AbsoluteLayout()) container.bounds = Bounds(30, 30, 200, 200) child1 = Panel(self.context) child1.bounds = Bounds(10, 10, 20, 20) child2 = Panel(self.context) child2.bounds = Bounds(50, 60, 20, 20) container.add(child1) container.add(child2) self.assertEqual(False, container.valid) self.context.process() self.assertEqual(True, container.valid) self.assertEqual(Bounds(10, 10, 20, 20), child1.bounds) self.assertEqual(Bounds(50, 60, 20, 20), child2.bounds) self.assertEqual(Dimension(0, 0), container.minimum_size) self.assertEqual(Dimension(0, 0), container.preferred_size) container.bounds = Bounds(20, 20, 100, 100) child1.minimum_size_override = Some(Dimension(400, 400)) child2.bounds = Bounds(-30, -40, 50, 50) self.assertEqual(False, container.valid) self.context.process() self.assertEqual(True, container.valid) self.assertEqual(Bounds(10, 10, 400, 400), child1.bounds) self.assertEqual(Bounds(-30, -40, 50, 50), child2.bounds) self.assertEqual(Dimension(0, 0), container.minimum_size)
def test_draw(self): window1 = Frame(self.context) window1.bounds = Bounds(10, 20, 80, 60) window2 = Frame(self.context) window2.bounds = Bounds(50, 40, 50, 50) window2.set_color(StyleKeys.Background, RGBA(1, 0, 0, 1)) self.context.process() self.assertImage("draw", self.context)
def test_resize_with_min_size(self): window = Frame(self.context) window.draggable = True window.resizable = True window.minimum_size_override = Some(Dimension(30, 30)) def resize(drag_from: Point, drag_to: Point, expected: Bounds) -> None: window.bounds = Bounds(20, 20, 60, 60) self.mouse.move_to(drag_from) self.mouse.press(MouseButton.LEFT) self.mouse.move_to(drag_to) self.mouse.release(MouseButton.LEFT) self.context.process() self.assertEqual(expected, window.bounds) resize(Point(40, 25), Point(40, 95), Bounds(20, 50, 60, 30)) resize(Point(75, 25), Point(5, 95), Bounds(20, 50, 30, 30)) resize(Point(75, 40), Point(5, 40), Bounds(20, 20, 30, 60)) resize(Point(75, 75), Point(5, 5), Bounds(20, 20, 30, 30)) resize(Point(40, 75), Point(40, 5), Bounds(20, 20, 60, 30)) resize(Point(25, 75), Point(95, 5), Bounds(50, 20, 30, 30)) resize(Point(25, 40), Point(95, 40), Bounds(50, 20, 30, 60)) resize(Point(25, 25), Point(95, 95), Bounds(50, 50, 30, 30))
def test_resize_to_collapse(self): window = Frame(self.context) window.draggable = True window.resizable = True def resize(drag_from: Point, drag_to: Point, expected: Bounds) -> None: window.bounds = Bounds(20, 20, 60, 60) self.mouse.move_to(drag_from) self.mouse.press(MouseButton.LEFT) self.mouse.move_to(drag_to) self.mouse.release(MouseButton.LEFT) self.context.process() self.assertEqual(expected, window.bounds) resize(Point(40, 25), Point(40, 95), Bounds(20, 80, 60, 0)) resize(Point(75, 25), Point(5, 95), Bounds(20, 80, 0, 0)) resize(Point(75, 40), Point(5, 40), Bounds(20, 20, 0, 60)) resize(Point(75, 75), Point(5, 5), Bounds(20, 20, 0, 0)) resize(Point(40, 75), Point(40, 5), Bounds(20, 20, 60, 0)) resize(Point(25, 75), Point(95, 5), Bounds(80, 20, 0, 0)) resize(Point(25, 40), Point(95, 40), Bounds(80, 20, 0, 60)) resize(Point(25, 25), Point(95, 95), Bounds(80, 80, 0, 0))
def _calculate_bounds(self, size: float, offset: float, preferred: Dimension, area: Bounds) -> Bounds: align = self.align if align == BoxAlign.Begin: return Bounds(area.x, area.y + offset, preferred.width, size) elif align == BoxAlign.End: return Bounds(area.x + area.width - preferred.width, area.y + offset, preferred.width, size) elif align == BoxAlign.Stretch: return Bounds(area.x, area.y + offset, area.width, size) return Bounds(area.x + (area.width - preferred.width) / 2., area.y + offset, preferred.width, size)
def _bounds_for_state(state: Frame._ResizeState, location: Point) -> Bounds: (handle, anchor, init_bounds, min_size) = state (mw, mh) = min_size.tuple delta = location - anchor (x, y, w, h) = init_bounds.tuple if handle in (Direction.North, Direction.Northeast, Direction.Northwest): d = min(delta.y, max(h - mh, 0)) y += d h -= d elif handle in (Direction.South, Direction.Southeast, Direction.Southwest): h += max(delta.y, min(-h + mh, 0)) if handle in (Direction.East, Direction.Northeast, Direction.Southeast): w += max(delta.x, min(-w + mw, 0)) elif handle in (Direction.West, Direction.Northwest, Direction.Southwest): d = min(delta.x, max(w - mw, 0)) x += d w -= d return Bounds(x, y, w, h)
def test_bounds_unpack(self): (x, y, width, height) = Bounds(10, -30, 150, 200) self.assertEqual(10, x) self.assertEqual(-30, y) self.assertEqual(150, width) self.assertEqual(200, height)
def _calculate_bounds(self, size: float, offset: float, preferred: Dimension, area: Bounds) -> Bounds: align = self.align if align == BoxAlign.Begin: return Bounds(area.x + offset, area.y, size, preferred.height) elif align == BoxAlign.End: return Bounds(area.x + offset, area.y + area.height - preferred.height, size, preferred.height) elif align == BoxAlign.Stretch: return Bounds(area.x + offset, area.y, size, area.height) return Bounds(area.x + offset, area.y + (area.height - preferred.height) / 2., size, preferred.height)
def draw_component(self, g: Graphics, component: Canvas) -> None: super().draw_component(g, component) if component.image == Nothing: return image = component.image.unwrap() padding = component.resolve_insets(StyleKeys.Padding).value_or( Insets(0, 0, 0, 0)) + component.padding (x, y, w, h) = component.bounds.tuple bounds = Bounds(x + padding.left, y + padding.top, max(w - padding.left - padding.right, 0), max(h - padding.top - padding.bottom, 0)) (iw, ih) = image.size.tuple (w, h) = bounds.size.tuple if iw == 0 or ih == 0 or w == 0 or h == 0: return (x, y) = bounds.location.tuple sw = w / iw sh = h / ih sx = x / sw sy = y / sh g.scale(sw, sh) g.set_source_surface(image.surface, sx, sy) g.paint()
def test_draw_with_padding(self): image = self.context.toolkit.images[FixturePath] window = Frame(self.context) window.bounds = Bounds(0, 0, 100, 100) canvas = Canvas(self.context, image) canvas.set_color(StyleKeys.Background, RGBA(1, 0, 0, 1)) window.add(canvas) def assert_padding(size: Dimension, padding: Insets): (w, h) = size.tuple (top, right, bottom, left) = padding.tuple canvas.bounds = Bounds(0, 0, w, h) canvas.padding = padding self.context.process() self.assertImage( f"draw_with_padding-{top},{right},{bottom},{left}-{w}x{h}", self.context, tolerance=Tolerance) assert_padding(Dimension(100, 100), Insets(0, 0, 0, 0)) assert_padding(Dimension(100, 100), Insets(10, 5, 3, 15)) assert_padding(Dimension(64, 64), Insets(0, 0, 0, 0)) assert_padding(Dimension(64, 64), Insets(10, 5, 3, 15))
def bounds(self) -> Bounds: if self.component == Nothing: return Bounds(0, 0, 0, 0) component = self.component.unwrap() bounds = component.bounds return (bounds + self.padding).copy(x=bounds.x, y=bounds.y)
def test_bounds_copy(self): self.assertEqual(Bounds(-20, 20, 30, 30), Bounds(-20, 10, 80, 30).copy(y=20, width=30)) self.assertEqual(Bounds(10, 10, 80, 100), Bounds(-20, 10, 80, 30).copy(x=10, height=100)) self.assertEqual( Bounds(0, 0, 20, 40), Bounds(-20, 10, 80, 30).copy(x=0, y=0, width=20, height=40)) self.assertEqual(Bounds(-20, 10, 80, 30), Bounds(-20, 10, 80, 30).copy())
def setUp(self) -> None: super().setUp() self.container = Frame(self.context, AnchorLayout()) self.container.bounds = Bounds(0, 0, 100, 100) self.child = Panel(self.context) self.child.preferred_size_override = Some(Dimension(40, 30)) self.child.set_color(StyleKeys.Background, RGBA(1, 0, 0, 1))
def test_hbox_layout(self): layout = HBoxLayout() container = Frame(self.context, layout) container.bounds = Bounds(5, 5, 90, 90) child1 = Panel(self.context) child1.preferred_size_override = Some(Dimension(20, 50)) child1.set_color(StyleKeys.Background, RGBA(1, 0, 0, 1)) container.add(child1) child2 = Panel(self.context) child2.preferred_size_override = Some(Dimension(15, 60)) child2.minimum_size_override = Some(Dimension(15, 60)) child2.set_color(StyleKeys.Background, RGBA(0, 1, 0, 1)) container.add(child2) child3 = Panel(self.context) child3.preferred_size_override = Some(Dimension(30, 40)) child3.minimum_size_override = Some(Dimension(10, 20)) child3.set_color(StyleKeys.Background, RGBA(0, 0, 1, 1)) container.add(child3) def test(direction: BoxDirection, spacing: float, padding: Insets, align: BoxAlign): container.bounds = Bounds(5, 5, 90, 90) layout.spacing = spacing layout.padding = padding layout.align = align self.assertEqual(True, container.layout_pending) self.context.process() self.assertEqual(False, container.layout_pending) (top, right, bottom, left) = padding.tuple prefix = f"hbox-{direction.name}-{spacing}-{top},{right},{bottom},{left}-{align.name}-" self.assertImage(prefix + "full-size", self.context) container.bounds = Bounds(5, 5, 45, 45) self.assertEqual(True, container.layout_pending) self.context.process() self.assertEqual(False, container.layout_pending) self.assertImage(prefix + "half-size", self.context) for d in BoxDirection: for s in [0, 10]: for p in [Insets(0, 0, 0, 0), Insets(15, 20, 10, 5)]: for a in BoxAlign: test(d, s, p, a)
def perform(self, bounds: Bounds) -> None: s = self._from_size # noinspection PyTypeChecker children: Sequence[Component] = tuple( filter(lambda c: c.visible, map(lambda c: c.component, self.children))) area = bounds.copy(x=0, y=0) - self.padding spacing = self.spacing space_between = max(len(children) - 1, 0) * spacing space_available = s(area.size) - space_between space_needed = sum(map(lambda c: s(c.preferred_size), children)) space_to_reduce = max(space_needed - space_available, 0) reduced_size = dict.fromkeys(children, 0.) def calculate_sizes_to_reduce(comps: Sequence[Component], remaining: float = space_to_reduce): if remaining <= 0: return target = remaining / len(comps) next_comps = [] next_remaining = remaining for c in comps: available = s(c.preferred_size - c.minimum_size) if available > target: next_comps.append(c) reduced = min(target, available) reduced_size[c] += reduced next_remaining -= reduced if len(next_comps) > 0: calculate_sizes_to_reduce(next_comps, next_remaining) calculate_sizes_to_reduce(children) offset = 0 if self.direction == BoxDirection.Forward else s(area.size) for child in children: preferred = child.preferred_size size = max(s(preferred) - reduced_size[child], 0) if self.direction != BoxDirection.Forward: offset -= size child.bounds = self._calculate_bounds(size, offset, preferred, area) offset += size + spacing if self.direction == BoxDirection.Forward else -spacing
def start(self, args: dict): from alleycat.ui.blender import UI self.context = UI().create_context() window = Frame(self.context, BorderLayout()) window.bounds = Bounds(160, 70, 280, 200) panel = Panel(self.context, HBoxLayout()) panel.set_color(StyleKeys.Background, RGBA(0.3, 0.3, 0.3, 0.8)) window.add(panel, padding=Insets(10, 10, 10, 10)) icon = Canvas(self.context, self.context.toolkit.images["cat.png"]) icon.minimum_size_override = Some(Dimension(64, 64)) panel.add(icon) label = Label(self.context, text_size=18) label.set_color(StyleKeys.Text, RGBA(1, 1, 1, 1)) panel.add(label) button1 = LabelButton(self.context, text_size=16, text="Button 1") button2 = LabelButton(self.context, text_size=16, text="Button 2") buttons = Panel(self.context, HBoxLayout(spacing=10, direction=BoxDirection.Reverse)) buttons.add(button2) buttons.add(button1) window.add(buttons, Border.Bottom, Insets(0, 10, 10, 10)) def handle_button(button: str): if len(button) > 0: label.text = f"{button} is pressed" panel.set_color(StyleKeys.Background, RGBA(1, 0, 0, 1)) else: label.text = "" panel.set_color(StyleKeys.Background, RGBA(0.1, 0.1, 0.1, 0.8)) button1_active = button1.observe("active").pipe( ops.map(lambda v: "Button 1" if v else "")) button2_active = button2.observe("active").pipe( ops.map(lambda v: "Button 2" if v else "")) button_active = rx.combine_latest(button1_active, button2_active).pipe( ops.map(lambda v: v[0] + v[1])) button_active.subscribe(handle_button, on_error=self.context.error_handler) window.draggable = True window.resizable = True
def test_draw(self): self.context.look_and_feel.set_color("Panel.background", RGBA(0, 0, 1, 0.5)) window = Window(self.context) window.bounds = Bounds(0, 0, 100, 100) panel1 = Panel(self.context) panel1.bounds = Bounds(20, 20, 40, 60) panel2 = Panel(self.context) panel2.bounds = Bounds(50, 40, 40, 40) panel2.set_color(StyleKeys.Background, RGBA(1, 0, 0, 1)) window.add(panel1) window.add(panel2) self.context.process() self.assertImage("draw", self.context)
def resize(name: str, drag_from: Point, drag_to: Point) -> None: window.bounds = Bounds(20, 20, 60, 60) self.mouse.move_to(drag_from) self.mouse.press(MouseButton.LEFT) self.mouse.move_to(drag_to) self.mouse.release(MouseButton.LEFT) self.context.process() self.assertImage(name, self.context)
def resize(drag_from: Point, drag_to: Point, expected: Bounds) -> None: window.bounds = Bounds(20, 20, 60, 60) self.mouse.move_to(drag_from) self.mouse.press(MouseButton.LEFT) self.mouse.move_to(drag_to) self.mouse.release(MouseButton.LEFT) self.context.process() self.assertEqual(expected, window.bounds)
def test_hover(self): window = Window(self.context) window.bounds = Bounds(0, 0, 100, 60) button = LabelButton(self.context) button.text = "AlleyCat" button.text_size = 14 button.bounds = Bounds(10, 10, 80, 40) window.add(button) values = [] rv.observe(button, "hover").subscribe(values.append) self.assertFalse(button.hover) self.assertEqual([False], values) self.mouse.move_to(Point(50, 30)) self.context.process() self.assertTrue(button.hover) self.assertEqual([False, True], values) self.assertImage("hover_mouse_over", self.context, tolerance=Tolerance) self.mouse.move_to(Point(0, 0)) self.context.process() self.assertFalse(button.hover) self.assertEqual([False, True, False], values) self.assertImage("hover_mouse_out", self.context, tolerance=Tolerance) self.mouse.move_to(Point(10, 10)) self.context.process() self.assertTrue(button.hover) self.assertEqual([False, True, False, True], values) self.assertImage("hover_mouse_over2", self.context, tolerance=Tolerance)
def test_size(self): sizes = [] rv.observe(self.fixture.size).subscribe(sizes.append) self.assertEqual(Dimension(0, 0), self.fixture.size) self.assertEqual([Dimension(0, 0)], sizes) self.fixture.bounds = Bounds(0, 0, 50, 150) self.assertEqual(Dimension(50, 150), self.fixture.size) self.assertEqual([Dimension(0, 0), Dimension(50, 150)], sizes)
def test_location(self): locations = [] rv.observe(self.fixture.location).subscribe(locations.append) self.assertEqual(Point(0, 0), self.fixture.location) self.assertEqual([Point(0, 0)], locations) self.fixture.bounds = Bounds(10, -30, 200, 100) self.assertEqual(Point(10, -30), self.fixture.location) self.assertEqual([Point(0, 0), Point(10, -30)], locations)
def create_batch(self, size: Dimension) -> GPUBatch: if size is None: raise ValueError("Argument 'size' is required.") points = Bounds(0, 0, size.width, size.height).points vertices = tuple(map(lambda p: p.tuple, map(self.translate, points))) coords = ((0, 0), (1, 0), (1, 1), (0, 1)) indices = {"pos": vertices, "texCoord": coords} return batch_for_shader(self.shader, "TRI_FAN", indices)
def test_component_at_with_hierarchy(self): parent = Container(self.context) parent.bounds = Bounds(0, 0, 200, 200) child = Container(self.context) child.bounds = Bounds(50, 50, 100, 100) grand_child = Container(self.context) grand_child.bounds = Bounds(25, 25, 50, 50) child.add(grand_child) parent.add(child) self.assertEqual(Nothing, parent.component_at(Point(-1, 0))) self.assertEqual(Nothing, parent.component_at(Point(201, 0))) self.assertEqual(Nothing, parent.component_at(Point(200, 201))) self.assertEqual(Nothing, parent.component_at(Point(-1, 200))) self.assertEqual(Some(parent), parent.component_at(Point(50, 49))) self.assertEqual(Some(parent), parent.component_at(Point(151, 50))) self.assertEqual(Some(parent), parent.component_at(Point(150, 151))) self.assertEqual(Some(parent), parent.component_at(Point(49, 150))) self.assertEqual(Some(child), parent.component_at(Point(50, 50))) self.assertEqual(Some(child), parent.component_at(Point(150, 50))) self.assertEqual(Some(child), parent.component_at(Point(150, 150))) self.assertEqual(Some(child), parent.component_at(Point(50, 150))) self.assertEqual(Some(grand_child), parent.component_at(Point(75, 75))) self.assertEqual(Some(grand_child), parent.component_at(Point(125, 75))) self.assertEqual(Some(grand_child), parent.component_at(Point(125, 125))) self.assertEqual(Some(grand_child), parent.component_at(Point(75, 125))) self.assertEqual(Some(child), parent.component_at(Point(74, 75))) self.assertEqual(Some(child), parent.component_at(Point(125, 74))) self.assertEqual(Some(child), parent.component_at(Point(126, 125))) self.assertEqual(Some(child), parent.component_at(Point(75, 126)))