Skip to content

Commit

Permalink
feat(gui): UIInteractive interactions limited to left mouse button
Browse files Browse the repository at this point in the history
  • Loading branch information
eruvanos committed May 10, 2024
1 parent 84ad869 commit 3b34bca
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 73 deletions.
93 changes: 22 additions & 71 deletions arcade/gui/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,13 @@
from abc import ABC
from math import floor
from random import randint
from typing import (
NamedTuple,
Iterable,
Optional,
Union,
TYPE_CHECKING,
TypeVar,
Tuple,
List,
Dict,
Callable
)
from typing import NamedTuple, Iterable, Optional, Union, TYPE_CHECKING, TypeVar, Tuple, List, Dict, Callable

from pyglet.event import EventDispatcher, EVENT_HANDLED, EVENT_UNHANDLED
from typing_extensions import Self

import arcade
from arcade import Sprite, get_window, Texture
from arcade import Sprite, Texture
from arcade.color import TRANSPARENT_BLACK
from arcade.gui.events import (
UIEvent,
Expand Down Expand Up @@ -81,11 +70,7 @@ def collide_with_point(self, x: AsFloat, y: AsFloat) -> bool:
left, bottom, width, height = self
return left <= x <= left + width and bottom <= y <= bottom + height

def scale(
self,
scale: float,
rounding: Optional[Callable[..., float]] = floor
) -> "Rect":
def scale(self, scale: float, rounding: Optional[Callable[..., float]] = floor) -> "Rect":
"""Return a new rect scaled relative to the origin.
By default, the new rect's values are rounded down to whole
Expand Down Expand Up @@ -115,11 +100,7 @@ def scale(
height * scale,
)

def resize(
self,
width: float | None = None,
height: float | None = None
) -> "Rect":
def resize(self, width: float | None = None, height: float | None = None) -> "Rect":
"""Return a rect with a new width or height but same lower left.
Fix x and y coordinate.
Expand Down Expand Up @@ -213,11 +194,7 @@ def align_center_y(self, value: AsFloat) -> "Rect":
diff_y = value - self.center_y
return self.move(dy=diff_y)

def min_size(
self,
width: Optional[AsFloat] = None,
height: Optional[AsFloat] = None
) -> "Rect":
def min_size(self, width: Optional[AsFloat] = None, height: Optional[AsFloat] = None) -> "Rect":
"""
Sets the size to at least the given min values.
"""
Expand All @@ -228,11 +205,7 @@ def min_size(
max(height or 0.0, self.height),
)

def max_size(
self,
width: Optional[AsFloat] = None,
height: Optional[AsFloat] = None
) -> "Rect":
def max_size(self, width: Optional[AsFloat] = None, height: Optional[AsFloat] = None) -> "Rect":
"""
Limits the size to the given max values.
"""
Expand Down Expand Up @@ -333,9 +306,7 @@ def __init__(
self.add(child)

bind(self, "rect", self.trigger_full_render)
bind(
self, "visible", self.trigger_full_render
) # TODO maybe trigger_parent_render would be enough
bind(self, "visible", self.trigger_full_render) # TODO maybe trigger_parent_render would be enough
bind(self, "_children", self.trigger_render)
bind(self, "_border_width", self.trigger_render)
bind(self, "_border_color", self.trigger_render)
Expand Down Expand Up @@ -364,9 +335,7 @@ def add(self, child: W, **kwargs) -> W:
else:
if not 0 <= index <= len(self.children):
raise ValueError("Index must be between 0 and the number of children")
self._children.insert(
index, _ChildEntry(child, kwargs)
)
self._children.insert(index, _ChildEntry(child, kwargs))

return child

Expand Down Expand Up @@ -478,9 +447,7 @@ def do_render_base(self, surface: Surface):
surface.clear(self._bg_color)
# draw background texture
if self._bg_tex:
surface.draw_texture(
x=0, y=0, width=self.width, height=self.height, tex=self._bg_tex
)
surface.draw_texture(x=0, y=0, width=self.width, height=self.height, tex=self._bg_tex)

# draw border
if self._border_width and self._border_color:
Expand Down Expand Up @@ -675,21 +642,11 @@ def content_size(self):

@property
def content_width(self):
return (
self.rect.width
- 2 * self._border_width
- self._padding_left
- self._padding_right
)
return self.rect.width - 2 * self._border_width - self._padding_left - self._padding_right

@property
def content_height(self):
return (
self.rect.height
- 2 * self._border_width
- self._padding_top
- self._padding_bottom
)
return self.rect.height - 2 * self._border_width - self._padding_top - self._padding_bottom

@property
def content_rect(self):
Expand Down Expand Up @@ -777,19 +734,24 @@ def on_event(self, event: UIEvent) -> Optional[bool]:
if isinstance(event, UIMouseMovementEvent):
self.hovered = self.rect.collide_with_point(event.x, event.y)

if isinstance(event, UIMousePressEvent) and self.rect.collide_with_point(
event.x, event.y
if (
isinstance(event, UIMousePressEvent)
and self.rect.collide_with_point(event.x, event.y)
and event.button == arcade.MOUSE_BUTTON_LEFT
):
self.pressed = True
return EVENT_HANDLED

if self.pressed and isinstance(event, UIMouseReleaseEvent):
if self.pressed and isinstance(event, UIMouseReleaseEvent) and event.button == arcade.MOUSE_BUTTON_LEFT:
self.pressed = False
if self.rect.collide_with_point(event.x, event.y):
if not self.disabled:
# Dispatch new on_click event, source is this widget itself
self.dispatch_event(
"on_click", UIOnClickEvent(self, event.x, event.y)
"on_click",
UIOnClickEvent(
source=self, x=event.x, y=event.y, button=event.button, modifiers=event.modifiers
),
)
# TODO unsure if it makes more sense to mark the event handled if the click event is handled.
return EVENT_HANDLED
Expand Down Expand Up @@ -845,31 +807,20 @@ def __init__(
)
self.color: RGBA255 = (randint(0, 255), randint(0, 255), randint(0, 255), 255)
self.border_color = arcade.color.BATTLESHIP_GREY
self.border_width = 0

def on_click(self, event: UIOnClickEvent):
print("UIDummy.rect:", self.rect)
self.color = Color.random(a=255)

def on_update(self, dt):
self.border_width = 2 if self.hovered else 0
self.border_color = (
arcade.color.WHITE if self.pressed else arcade.color.BATTLESHIP_GREY
)
self.border_color = arcade.color.WHITE if self.pressed else arcade.color.BATTLESHIP_GREY

def do_render(self, surface: Surface):
self.prepare_render(surface)
surface.clear(self.color)

# if self.hovered:
# arcade.draw_xywh_rectangle_outline(
# 0,
# 0,
# self.width,
# self.height,
# color=arcade.color.BATTLESHIP_GREY,
# border_width=3,
# )


class UISpriteWidget(UIWidget):
"""Create a UI element with a sprite that controls what is displayed.
Expand Down
25 changes: 23 additions & 2 deletions tests/unit/gui/test_interactions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import List
from unittest.mock import Mock

import arcade
from arcade.gui.events import UIEvent, UIOnClickEvent, UIMousePressEvent, UIMouseReleaseEvent
from arcade.gui.widgets import UIDummy
from . import record_ui_events
Expand Down Expand Up @@ -33,15 +34,15 @@ def test_overlapping_hover_on_widget(uimanager):
assert widget2.hovered is True


def test_click_on_widget(uimanager):
def test_left_click_on_widget(uimanager):
# GIVEN
widget1 = UIDummy()
widget1.on_click = Mock()
uimanager.add(widget1)

# WHEN
with record_ui_events(widget1, "on_event", "on_click") as records:
uimanager.click(widget1.center_x, widget1.center_y)
uimanager.click(widget1.center_x, widget1.center_y, button=arcade.MOUSE_BUTTON_LEFT)

# THEN
records: List[UIEvent]
Expand All @@ -54,10 +55,30 @@ def test_click_on_widget(uimanager):
assert click_event.source == widget1
assert click_event.x == widget1.center_x
assert click_event.y == widget1.center_y
assert click_event.button == arcade.MOUSE_BUTTON_LEFT
assert click_event.modifiers == 0

assert widget1.on_click.called


def test_ignores_right_click_on_widget(uimanager):
# GIVEN
widget1 = UIDummy()
widget1.on_click = Mock()
uimanager.add(widget1)

# WHEN
with record_ui_events(widget1, "on_event", "on_click") as records:
uimanager.click(widget1.center_x, widget1.center_y, button=arcade.MOUSE_BUTTON_RIGHT)

# THEN
records: List[UIEvent]
assert len(records) == 2
assert isinstance(records[0], UIMousePressEvent)
assert isinstance(records[1], UIMouseReleaseEvent)
assert not widget1.on_click.called


def test_click_on_widget_if_disabled(uimanager):
# GIVEN
widget1 = UIDummy()
Expand Down

0 comments on commit 3b34bca

Please sign in to comment.