Skip to content

Commit

Permalink
Changes for arcade 3.0 RC2 (dev34) (#2357)
Browse files Browse the repository at this point in the history
- Add more gui examples
  - hidden password
  - camera
- revamp UIGridLayout, use box algorithm, add documentation
  • Loading branch information
eruvanos authored Aug 31, 2024
1 parent 1b8ca03 commit a362ee1
Show file tree
Hide file tree
Showing 26 changed files with 815 additions and 480 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
At the beginning of the game, the UI camera is used, to apply some animations.
If arcade and Python are properly installed, you can run this example with:
python -m arcade.examples.gui.4_gui_and_camera
python -m arcade.examples.gui.4_with_camera
"""

from __future__ import annotations
Expand Down Expand Up @@ -37,6 +37,7 @@ class MyCoinGame(UIView):

def __init__(self):
super().__init__()
self.bg_color = arcade.uicolor.DARK_BLUE_MIDNIGHT_BLUE

# basic camera setup
self.keys = set()
Expand Down Expand Up @@ -262,6 +263,5 @@ def on_key_release(self, symbol: int, modifiers: int) -> Optional[bool]:

if __name__ == "__main__":
window = arcade.Window(1280, 720, "GUI Example: Coin Game (Camera)", resizable=False)
window.background_color = arcade.uicolor.DARK_BLUE_MIDNIGHT_BLUE
window.show_view(MyCoinGame())
window.run()
7 changes: 1 addition & 6 deletions arcade/examples/gui/5_uicolor_picker.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,7 @@ def __init__(self):
ColorButton(
color_name=name,
color=color,
# giving width and height is a workaround for a bug in the grid layout
# we would want to set size_hint=(1, 1) and let
# the grid layout handle the size
width=self.window.width // 5,
height=self.window.height // 4,
size_hint=None,
size_hint=(1, 1),
)
)
self.grid.add(button, row=i // 5, column=i % 5)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
always be set.
If arcade and Python are properly installed, you can run this example with:
python -m arcade.examples.gui.size_hints
python -m arcade.examples.gui.6_size_hints
"""

from __future__ import annotations
Expand Down
105 changes: 77 additions & 28 deletions arcade/examples/gui/exp_hidden_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
This example demonstrates how to create a custom text input
which hides the contents behind a custom character, as often
required for login screens
required for login screens.
Due to a bug in the current version of pyglet, the example uses ENTER to switch
fields instead of TAB. This will be fixed in future versions.
(https://github.com/pyglet/pyglet/issues/1197)
If arcade and Python are properly installed, you can run this example with:
python -m arcade.examples.gui.exp_hidden_password
Expand All @@ -11,55 +15,100 @@
from __future__ import annotations

import arcade
from arcade.gui import UIManager, UIInputText, UIOnClickEvent
from arcade.gui import UIInputText, UIOnClickEvent, UIView
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.text import UILabel
from arcade import resources

# Load kenny fonts shipped with arcade
resources.load_system_fonts()


class MyView(arcade.View):
class MyView(UIView):
def __init__(self):
super().__init__()
self.ui = UIManager()
self.background_color = arcade.uicolor.BLUE_BELIZE_HOLE

grid = UIGridLayout(
size_hint=(0, 0), # wrap children
row_count=3, # user, pw and login button
row_count=5, # title | user, pw | login button
column_count=2, # label and input field
vertical_spacing=10,
horizontal_spacing=5,
)
grid.with_padding(all=50)
grid.with_background(color=arcade.uicolor.GREEN_GREEN_SEA)

title = grid.add(
UILabel(text="Login", width=150, font_size=20, font_name="Kenney Future"),
column=0,
row=0,
column_span=2,
)
title.with_padding(bottom=20)

grid.add(UILabel(text="Username:", width=80), column=0, row=0)
self.username_input = grid.add(UIInputText(), column=1, row=0)

grid.add(UILabel(text="Password:", width=80), column=0, row=1)
self.password_input = grid.add(UIPasswordInput(), column=1, row=1)
grid.add(UILabel(text="Username:", width=80, font_name="Kenney Future"), column=0, row=1)
self.username_input = grid.add(
UIInputText(width=150, font_name="Kenney Future"), column=1, row=1
)

self.login_button = grid.add(UIFlatButton(text="Login"), column=0, row=2, column_span=2)
grid.add(UILabel(text="Password:", width=80, font_name="Kenney Future"), column=0, row=2)
self.password_input = grid.add(
UIPasswordInput(width=150, font_name="Kenney Future"), column=1, row=2
)
self.password_input.with_background(color=arcade.uicolor.GREEN_GREEN_SEA)
# set background to prevent full render on blinking caret

self.login_button = grid.add(
UIFlatButton(text="Login", height=30, width=150, size_hint=(1, None)),
column=0,
row=3,
column_span=2,
)
self.login_button.on_click = self.on_login

# add warning label
self.warning_label = grid.add(
UILabel(
text="Use [enter] to switch fields, then enter to login",
width=150,
font_size=10,
font_name="Kenney Future",
),
column=0,
row=4,
column_span=2,
)

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

self.ui.add(anchor)

def on_login(self, event: UIOnClickEvent):
print(f"User logged in with: {self.username_input.text} {self.password_input.text}")

def on_show_view(self):
self.window.background_color = arcade.color.DARK_BLUE_GRAY
# Enable UIManager when view is shown to catch window events
self.ui.enable()

def on_hide_view(self):
# Disable UIManager when view gets inactive
self.ui.disable()

def on_draw(self):
self.clear()
self.ui.draw()
self.add_widget(anchor)

# activate username input field
self.username_input.activate()

def on_key_press(self, symbol: int, modifiers: int) -> bool | None:
# if username field active, switch fields with enter
if self.username_input.active:
if symbol == arcade.key.ENTER:
self.username_input.deactivate()
self.password_input.activate()
return True
# if password field active, login with enter
elif self.password_input.active:
if symbol == arcade.key.ENTER:
self.password_input.deactivate()
self.on_login(None)
return True
return False

def on_login(self, event: UIOnClickEvent | None):
username = self.username_input.text.strip()
password = self.password_input.text.strip()
print(f"User logged in with: {username} {password}")


if __name__ == "__main__":
Expand Down
21 changes: 15 additions & 6 deletions arcade/examples/gui/grid_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,21 @@ def __init__(self):
super().__init__()
self.ui = UIManager()

dummy1 = UIDummy(width=100, height=100)
dummy1 = UIDummy(size_hint=(1, 1))
dummy2 = UIDummy(width=50, height=50)
dummy3 = UIDummy(width=50, height=50, size_hint=(0.5, 0.5))
dummy4 = UIDummy(width=100, height=100)
dummy5 = UIDummy(width=200, height=100)
dummy6 = UIDummy(width=100, height=300)
dummy4 = UIDummy(size_hint=(1, 1))
dummy5 = UIDummy(size_hint=(1, 1))
dummy6 = UIDummy(size_hint=(1, 1))

subject = (
UIGridLayout(
column_count=3,
row_count=3,
size_hint=(0.5, 0.5),
)
.with_border()
.with_padding()
.with_border(color=arcade.color.RED)
.with_padding(all=2)
)

subject.add(child=dummy1, column=0, row=0)
Expand All @@ -50,6 +50,10 @@ def __init__(self):

self.ui.add(anchor)

self.ui.execute_layout()
print(subject.size)
self.grid = subject

def on_show_view(self):
self.window.background_color = arcade.color.DARK_BLUE_GRAY
# Enable UIManager when view is shown to catch window events
Expand All @@ -59,6 +63,11 @@ def on_hide_view(self):
# Disable UIManager when view gets inactive
self.ui.disable()

def on_key_press(self, symbol: int, modifiers: int) -> bool | None:
if symbol == arcade.key.D:
self.grid.legacy_mode = not self.grid.legacy_mode
return True

def on_draw(self):
self.clear()
self.ui.draw()
Expand Down
61 changes: 0 additions & 61 deletions arcade/examples/gui/gui_slider.py

This file was deleted.

7 changes: 6 additions & 1 deletion arcade/gui/experimental/password_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@


class UIPasswordInput(UIInputText):
"""A password input field. The text is hidden with asterisks."""
"""A password input field. The text is hidden with asterisks.
Hint: It is recommended to set a background color to prevent full render cycles
when the caret blinks.
"""

def on_event(self, event: UIEvent) -> Optional[bool]:
"""Remove new lines from the input, which are not allowed in passwords."""
Expand Down
9 changes: 5 additions & 4 deletions arcade/gui/surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,12 @@ def limit(self, rect: Rect):
w = max(w, 1)
h = max(h, 1)

# round to nearest pixel, to avoid off by 1-pixel errors in ui
viewport_rect = LBWH(
int(l * self._pixel_ratio),
int(b * self._pixel_ratio),
int(w * self._pixel_ratio),
int(h * self._pixel_ratio),
round(l * self._pixel_ratio),
round(b * self._pixel_ratio),
round(w * self._pixel_ratio),
round(h * self._pixel_ratio),
)
self.fbo.viewport = viewport_rect.viewport

Expand Down
16 changes: 10 additions & 6 deletions arcade/gui/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class UIWidget(EventDispatcher, ABC):
rect: Rect = Property(LBWH(0, 0, 1, 1)) # type: ignore
visible: bool = Property(True) # type: ignore

size_hint: Optional[Tuple[float, float]] = Property(None) # type: ignore
size_hint: Optional[Tuple[float | None, float | None]] = Property(None) # type: ignore
size_hint_min: Optional[Tuple[float, float]] = Property(None) # type: ignore
size_hint_max: Optional[Tuple[float, float]] = Property(None) # type: ignore

Expand All @@ -83,9 +83,9 @@ def __init__(
height: float = 100,
children: Iterable["UIWidget"] = tuple(),
# Properties which might be used by layouts
size_hint=None, # in percentage
size_hint_min=None, # in pixel
size_hint_max=None, # in pixel
size_hint: Optional[Tuple[float | None, float | None]] = None, # in percentage
size_hint_min: Optional[Tuple[float, float]] = None, # in pixel
size_hint_max: Optional[Tuple[float, float]] = None, # in pixel
**kwargs,
):
self._requires_render = True
Expand Down Expand Up @@ -510,6 +510,9 @@ def center_on_screen(self: W) -> W:
self.rect = self.rect.align_center(center)
return self

def __repr__(self):
return f"<{self.__class__.__name__} {self.rect.lbwh}>"


class UIInteractiveWidget(UIWidget):
"""Base class for widgets which use mouse interaction (hover, pressed, clicked)
Expand Down Expand Up @@ -785,8 +788,9 @@ def _do_layout(self):
super()._do_layout()

def do_layout(self):
"""Triggered by the UIManager before rendering, :class:`UILayout` s should place
themselves and/or children. Do layout will be triggered on children afterward.
"""do_layout is triggered by the UIManager before rendering.
:class:`UILayout` should position their children.
Afterward, do_layout of child widgets will be triggered.
Use :meth:`UIWidget.trigger_render` to trigger a rendering before the next
frame, this will happen automatically if the position or size of this widget changed.
Expand Down
Loading

0 comments on commit a362ee1

Please sign in to comment.