Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Arcade [PyPi Release History](https://pypi.org/project/arcade/#history) page.
- GUI
- Fix a bug, where the caret of UIInputText was misplaced after resizing the widget
- Use incremental layout for UIScrollArea to improve performance of changing text
- Refactored and improved focus handling

## 3.3.2

Expand Down
9 changes: 8 additions & 1 deletion arcade/examples/gui/exp_controller_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
python -m arcade.examples.gui.exp_controller_support
"""


import arcade
from arcade import Texture
from arcade.experimental.controller_window import ControllerWindow, ControllerView
Expand Down Expand Up @@ -143,6 +142,7 @@ def __init__(self):
root.add(UIFlatButton(text="Close")).on_click = self.close

self.detect_focusable_widgets()
self.set_focus()

def on_event(self, event):
if super().on_event(event):
Expand Down Expand Up @@ -190,6 +190,13 @@ def on_button_click(self, event: UIOnClickEvent):
print("Button clicked")
self.root.add(ControllerModal())

def on_key_press(self, symbol: int, modifiers: int) -> bool | None:
# make the example close with the escape key
if symbol == arcade.key.ESCAPE:
self.window.close()
return True
return super().on_key_press(symbol, modifiers)


if __name__ == "__main__":
window = ControllerWindow(title="Controller UI Example")
Expand Down
8 changes: 7 additions & 1 deletion arcade/examples/gui/exp_controller_support_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
python -m arcade.examples.gui.exp_controller_support_grid
"""


import arcade
from arcade.examples.gui.exp_controller_support import ControllerIndicator
from arcade.experimental.controller_window import ControllerView, ControllerWindow
Expand Down Expand Up @@ -88,6 +87,13 @@ def __init__(self):

self.root.detect_focusable_widgets()

def on_key_press(self, symbol: int, modifiers: int) -> bool | None:
# make the example close with the escape key
if symbol == arcade.key.ESCAPE:
self.window.close()
return True
return super().on_key_press(symbol, modifiers)


if __name__ == "__main__":
window = ControllerWindow(title="Controller UI Example")
Expand Down
33 changes: 14 additions & 19 deletions arcade/examples/gui/exp_hidden_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
"""

import arcade
from arcade.experimental.controller_window import ControllerWindow
from arcade.gui import UIInputText, UIOnClickEvent, UIView
from arcade.gui.experimental.focus import UIFocusGroup
from arcade.gui.experimental.password_input import UIPasswordInput
from arcade.gui.widgets.buttons import UIFlatButton
from arcade.gui.widgets.layout import UIGridLayout, UIAnchorLayout
from arcade.gui.widgets.layout import UIGridLayout
from arcade.gui.widgets.text import UILabel
from arcade import resources

Expand Down Expand Up @@ -80,32 +82,25 @@ def __init__(self):
column_span=2,
)

anchor = UIAnchorLayout() # to center grid on screen
anchor = UIFocusGroup() # to center grid on screen
anchor.add(grid)

self.add_widget(anchor)

# activate username input field
self.username_input.activate()
anchor.detect_focusable_widgets()
anchor.set_focus()

def on_key_press(self, symbol: int, modifiers: int) -> bool | None:
# make the example close with the escape key
if symbol == arcade.key.ESCAPE:
self.window.close()
return True

# if username field active, switch fields with enter
if self.username_input.active:
if symbol == arcade.key.TAB:
self.username_input.deactivate()
self.password_input.activate()
return True
elif symbol == arcade.key.ENTER:
elif self.username_input.active or self.password_input.active:
if symbol == arcade.key.ENTER:
self.username_input.deactivate()
self.on_login(None)
return True
# if password field active, login with enter
elif self.password_input.active:
if symbol == arcade.key.TAB:
self.username_input.activate()
self.password_input.deactivate()
return True
elif symbol == arcade.key.ENTER:
self.password_input.deactivate()
self.on_login(None)
return True
Expand All @@ -118,7 +113,7 @@ def on_login(self, event: UIOnClickEvent | None):


def main():
window = arcade.Window(title="GUI Example: Hidden Password")
window = ControllerWindow(title="GUI Example: Hidden Password")
window.show_view(MyView())
window.run()

Expand Down
3 changes: 1 addition & 2 deletions arcade/experimental/controller_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ def __init__(self, window: arcade.Window):
self.on_connect(controller)

def on_connect(self, controller: Controller):
controller.push_handlers(self)

try:
controller.open()
except Exception as e:
warnings.warn(f"Failed to open controller {controller}: {e}")
controller.push_handlers(self)

self.window.dispatch_event("on_connect", controller)

Expand Down
106 changes: 13 additions & 93 deletions arcade/gui/experimental/focus.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,16 @@
from pyglet.math import Vec2

import arcade
from arcade import MOUSE_BUTTON_LEFT
from arcade.gui.events import (
UIControllerButtonPressEvent,
UIControllerButtonReleaseEvent,
UIControllerDpadEvent,
UIControllerEvent,
UIEvent,
UIKeyPressEvent,
UIKeyReleaseEvent,
UIMousePressEvent,
UIMouseReleaseEvent,
)
from arcade.gui.property import ListProperty, Property, bind
from arcade.gui.surface import Surface
from arcade.gui.widgets import FocusMode, UIInteractiveWidget, UIWidget
from arcade.gui.widgets import FocusMode, UIWidget
from arcade.gui.widgets.layout import UIAnchorLayout
from arcade.gui.widgets.slider import UIBaseSlider


class UIFocusable(UIWidget):
Expand Down Expand Up @@ -100,54 +93,22 @@ def on_event(self, event: UIEvent) -> bool | None:

return EVENT_HANDLED

elif event.symbol == arcade.key.SPACE:
self._start_interaction()
elif isinstance(event, UIControllerDpadEvent):
# switch focus
if event.vector.x == 1:
self.focus_right()
return EVENT_HANDLED

elif isinstance(event, UIKeyReleaseEvent):
if event.symbol == arcade.key.SPACE:
self._end_interaction()
elif event.vector.y == 1:
self.focus_up()
return EVENT_HANDLED

elif isinstance(event, UIControllerDpadEvent):
if self._interacting:
# TODO this should be handled in the slider!
# pass dpad events to the interacting widget
if event.vector.x == 1 and isinstance(self._interacting, UIBaseSlider):
self._interacting.norm_value += 0.1
return EVENT_HANDLED

elif event.vector.x == -1 and isinstance(self._interacting, UIBaseSlider):
self._interacting.norm_value -= 0.1
return EVENT_HANDLED

elif event.vector.x == -1:
self.focus_left()
return EVENT_HANDLED

else:
# switch focus
if event.vector.x == 1:
self.focus_right()
return EVENT_HANDLED

elif event.vector.y == 1:
self.focus_up()
return EVENT_HANDLED

elif event.vector.x == -1:
self.focus_left()
return EVENT_HANDLED

elif event.vector.y == -1:
self.focus_down()
return EVENT_HANDLED

elif isinstance(event, UIControllerButtonPressEvent):
if event.button == "a":
self._start_interaction()
return EVENT_HANDLED
elif isinstance(event, UIControllerButtonReleaseEvent):
if event.button == "a":
self._end_interaction()
elif event.vector.y == -1:
self.focus_down()
return EVENT_HANDLED

return EVENT_UNHANDLED
Expand Down Expand Up @@ -278,48 +239,6 @@ def focus_previous(self):
# automatically wrap around via index -1
self.set_focus(self._focusable_widgets[focused_index])

def _start_interaction(self):
# TODO this should be handled in the widget

widget = self.focused_widget

if isinstance(widget, UIInteractiveWidget):
widget.dispatch_ui_event(
UIMousePressEvent(
source=self,
x=int(widget.rect.center_x),
y=int(widget.rect.center_y),
button=MOUSE_BUTTON_LEFT,
modifiers=0,
)
)
self._interacting = widget
else:
print("Cannot interact widget")

def _end_interaction(self):
widget = self.focused_widget

if isinstance(widget, UIInteractiveWidget):
if isinstance(self._interacting, UIBaseSlider):
# if slider, release outside the slider
x = self._interacting.rect.left - 1
y = self._interacting.rect.bottom - 1
else:
x = widget.rect.center_x
y = widget.rect.center_y

self._interacting = None
widget.dispatch_ui_event(
UIMouseReleaseEvent(
source=self,
x=int(x),
y=int(y),
button=MOUSE_BUTTON_LEFT,
modifiers=0,
)
)

def _do_render(self, surface: Surface, force=False) -> bool:
rendered = super()._do_render(surface, force)

Expand Down Expand Up @@ -373,4 +292,5 @@ def is_focusable(widget):


class UIFocusGroup(UIFocusMixin, UIAnchorLayout):
pass
"""This will be removed in the future.
UIFocusMixin is planned to be integrated into UILayout."""
49 changes: 49 additions & 0 deletions arcade/gui/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
UIMouseReleaseEvent,
UIOnClickEvent,
UIOnUpdateEvent,
UIControllerButtonPressEvent,
UIControllerButtonReleaseEvent,
UIKeyPressEvent,
UIKeyReleaseEvent,
)
from arcade.gui.nine_patch import NinePatchTexture
from arcade.gui.property import ListProperty, Property, bind
Expand Down Expand Up @@ -745,6 +749,13 @@ def __init__(
bind(self, "pressed", UIInteractiveWidget.trigger_render)
bind(self, "hovered", UIInteractiveWidget.trigger_render)
bind(self, "disabled", UIInteractiveWidget.trigger_render)
bind(self, "focused", UIInteractiveWidget._on_focus_change)

def _on_focus_change(self):
"""If focus lost, release active state"""
if self.pressed and not self.focused:
self.pressed = False
self._release_active()

def on_event(self, event: UIEvent) -> bool | None:
"""Handles mouse events and triggers on_click event if the widget is clicked.
Expand All @@ -754,6 +765,7 @@ def on_event(self, event: UIEvent) -> bool | None:
if super().on_event(event):
return EVENT_HANDLED

# mouse event handling
if isinstance(event, UIMouseMovementEvent):
self.hovered = self.rect.point_in_rect(event.pos)

Expand Down Expand Up @@ -788,6 +800,43 @@ def on_event(self, event: UIEvent) -> bool | None:
)
return EVENT_HANDLED # TODO should we return the result from on_click?

# focus related events
if self.focused:
if isinstance(event, UIKeyPressEvent) and event.symbol == arcade.key.SPACE:
self.pressed = True
self._grap_active() # make this the active widget
return EVENT_HANDLED

if isinstance(event, UIControllerButtonPressEvent) and event.button in ("a",):
self.pressed = True
self._grap_active() # make this the active widget
return EVENT_HANDLED

if self.pressed:
keyboard_interaction = (
isinstance(event, UIKeyReleaseEvent) and event.symbol == arcade.key.SPACE
)
controller_interaction = isinstance(
event, UIControllerButtonReleaseEvent
) and event.button in ("a",)

if keyboard_interaction or controller_interaction:
self.pressed = False
if not self.disabled:
# Dispatch new on_click event, source is this widget itself
self._grap_active()
self.dispatch_event(
"on_click",
UIOnClickEvent( # simulate mouse click
source=self,
x=int(self.center_x),
y=int(self.center_y),
button=self.interaction_buttons[0],
modifiers=0,
),
)
return EVENT_HANDLED # TODO should we return the result from on_click?

return EVENT_UNHANDLED

def on_click(self, event: UIOnClickEvent):
Expand Down
Loading
Loading