Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encapsulate relative position logic in UITextInput.on_event #2327

Draft
wants to merge 5 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion arcade/gui/widgets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
from __future__ import annotations

from abc import ABC
from typing import NamedTuple, Iterable, Optional, Union, TYPE_CHECKING, TypeVar, Tuple, List, Dict
from typing import (
NamedTuple,
Iterable,
Optional,
Union,
TYPE_CHECKING,
TypeVar,
Tuple,
List,
Dict,
)

from pyglet.event import EventDispatcher, EVENT_HANDLED, EVENT_UNHANDLED
from pyglet.math import Vec2
Expand All @@ -17,6 +27,7 @@
UIMouseReleaseEvent,
UIOnClickEvent,
UIOnUpdateEvent,
UIMouseEvent,
)
from arcade.gui.nine_patch import NinePatchTexture
from arcade.gui.property import Property, bind, ListProperty
Expand Down Expand Up @@ -167,6 +178,20 @@ def on_update(self, dt):
"""Custom logic which will be triggered."""
pass

def _event_pos_relative_to_self(self, mouse_event: UIMouseEvent) -> Vec2 | None:
"""Gets coords relative to bottom left if inside the widget.

Args:
mouse_event: Any :py:class:`UIMouseEvent`.
Returns:
``None`` if outside the widget or coords relative to
the widget's bottom left.
"""
pos = mouse_event.pos
if not self.rect.point_in_rect(pos):
return None
return Vec2(pos[0] - self.left, pos[1] - self.bottom)

Comment on lines +181 to +194
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update to use the new Rect method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't update it yet because I want to give it more thought after rereading the UI code and considering the following:

  1. 3.9 features will be our new minimum
  2. The new rect method seems to perform more ops
  3. It may add even more call overhead

def on_event(self, event: UIEvent) -> Optional[bool]:
"""Passes :class:`UIEvent` s through the widget tree."""
# UpdateEvents are past to the first invisible widget
Expand Down
60 changes: 40 additions & 20 deletions arcade/gui/widgets/text.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from __future__ import annotations

from typing import Optional
from typing import Final, Optional

import pyglet
from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED
from pyglet.math import Vec2
from pyglet.text.caret import Caret
from pyglet.text.document import AbstractDocument
from typing_extensions import override
Expand Down Expand Up @@ -396,6 +397,7 @@ class UIInputText(UIWidget):
# Move layout one pixel into the scissor box so the caret is also shown at
# position 0.
LAYOUT_OFFSET = 1
LAYOUT_OFFSET_VEC2: Final[Vec2] = Vec2(0.0, LAYOUT_OFFSET)

def __init__(
self,
Expand Down Expand Up @@ -467,30 +469,47 @@ def on_update(self, dt):
self._blink_state = current_state
self.trigger_full_render()

@override
def _event_pos_relative_to_self(self, mouse_event: UIMouseEvent) -> Vec2 | None:
if result := super()._event_pos_relative_to_self(mouse_event):
return result - self.LAYOUT_OFFSET_VEC2
return result

@override
def on_event(self, event: UIEvent) -> Optional[bool]:
"""Handle events for the text input field.

Text input is only active when the user clicks on the input field."""
# If not active, check to activate, return
if not self._active and isinstance(event, UIMousePressEvent):
if self.rect.point_in_rect(event.pos):
Text input and other editing events only work after a user clicks
inside the field (:py:class:`~arcade.gui.events.UIMousePress`).
Dragging to select works as expected because it is handled by a separate
event class (:py:class:`~arcade.gui.events.UIMouseEventDrag`).

Args:
event: The UI event to be handled here or in a superclass.
Returns:
``True`` if the event was handled.
"""

# Handle mouse presses to activate or deactivate the caret. All other mouse
# events, including dragging and scrolling, are handled in the next if block.
if isinstance(event, UIMousePressEvent):
inside_xy = self._event_pos_relative_to_self(event)
if self._active:
if inside_xy:
x, y = map(int, inside_xy)
self.caret.on_mouse_press(x, y, event.button, event.modifiers)
else:
self.deactivate()
# return unhandled to allow other widgets to activate
return EVENT_UNHANDLED

elif not self._active and inside_xy:
self.activate()
# return unhandled to allow other widgets to deactivate
return EVENT_UNHANDLED

# If active check to deactivate
if self._active and isinstance(event, UIMousePressEvent):
if self.rect.point_in_rect(event.pos):
x = int(event.x - self.left - self.LAYOUT_OFFSET)
y = int(event.y - self.bottom)
self.caret.on_mouse_press(x, y, event.button, event.modifiers)
else:
self.deactivate()
# return unhandled to allow other widgets to activate
return EVENT_UNHANDLED

# If active pass all non press events to caret
# When active, handle any supported non-press events by passing them
# to the caret object.
if self._active:
# Act on events if active
if isinstance(event, UITextInputEvent):
Expand All @@ -503,9 +522,10 @@ def on_event(self, event: UIEvent) -> Optional[bool]:
self.caret.on_text_motion_select(event.selection)
self.trigger_full_render()

if isinstance(event, UIMouseEvent) and self.rect.point_in_rect(event.pos):
x = int(event.x - self.left - self.LAYOUT_OFFSET)
y = int(event.y - self.bottom)
if isinstance(event, UIMouseEvent) and (
inside_xy := self._event_pos_relative_to_self(event)
):
x, y = map(int, inside_xy)
if isinstance(event, UIMouseDragEvent):
self.caret.on_mouse_drag(
x, y, event.dx, event.dy, event.buttons, event.modifiers
Expand Down
Loading