Skip to content

Commit a362ee1

Browse files
authored
Changes for arcade 3.0 RC2 (dev34) (#2357)
- Add more gui examples - hidden password - camera - revamp UIGridLayout, use box algorithm, add documentation
1 parent 1b8ca03 commit a362ee1

26 files changed

+815
-480
lines changed

arcade/examples/gui/4_gui_and_camera.py renamed to arcade/examples/gui/4_with_camera.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
At the beginning of the game, the UI camera is used, to apply some animations.
88
99
If arcade and Python are properly installed, you can run this example with:
10-
python -m arcade.examples.gui.4_gui_and_camera
10+
python -m arcade.examples.gui.4_with_camera
1111
"""
1212

1313
from __future__ import annotations
@@ -37,6 +37,7 @@ class MyCoinGame(UIView):
3737

3838
def __init__(self):
3939
super().__init__()
40+
self.bg_color = arcade.uicolor.DARK_BLUE_MIDNIGHT_BLUE
4041

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

263264
if __name__ == "__main__":
264265
window = arcade.Window(1280, 720, "GUI Example: Coin Game (Camera)", resizable=False)
265-
window.background_color = arcade.uicolor.DARK_BLUE_MIDNIGHT_BLUE
266266
window.show_view(MyCoinGame())
267267
window.run()

arcade/examples/gui/5_uicolor_picker.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,7 @@ def __init__(self):
144144
ColorButton(
145145
color_name=name,
146146
color=color,
147-
# giving width and height is a workaround for a bug in the grid layout
148-
# we would want to set size_hint=(1, 1) and let
149-
# the grid layout handle the size
150-
width=self.window.width // 5,
151-
height=self.window.height // 4,
152-
size_hint=None,
147+
size_hint=(1, 1),
153148
)
154149
)
155150
self.grid.add(button, row=i // 5, column=i % 5)

arcade/examples/gui/size_hints.py renamed to arcade/examples/gui/6_size_hints.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
always be set.
1515
1616
If arcade and Python are properly installed, you can run this example with:
17-
python -m arcade.examples.gui.size_hints
17+
python -m arcade.examples.gui.6_size_hints
1818
"""
1919

2020
from __future__ import annotations

arcade/examples/gui/exp_hidden_password.py

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
33
This example demonstrates how to create a custom text input
44
which hides the contents behind a custom character, as often
5-
required for login screens
5+
required for login screens.
6+
7+
Due to a bug in the current version of pyglet, the example uses ENTER to switch
8+
fields instead of TAB. This will be fixed in future versions.
9+
(https://github.com/pyglet/pyglet/issues/1197)
610
711
If arcade and Python are properly installed, you can run this example with:
812
python -m arcade.examples.gui.exp_hidden_password
@@ -11,55 +15,100 @@
1115
from __future__ import annotations
1216

1317
import arcade
14-
from arcade.gui import UIManager, UIInputText, UIOnClickEvent
18+
from arcade.gui import UIInputText, UIOnClickEvent, UIView
1519
from arcade.gui.experimental.password_input import UIPasswordInput
1620
from arcade.gui.widgets.buttons import UIFlatButton
1721
from arcade.gui.widgets.layout import UIGridLayout, UIAnchorLayout
1822
from arcade.gui.widgets.text import UILabel
23+
from arcade import resources
24+
25+
# Load kenny fonts shipped with arcade
26+
resources.load_system_fonts()
1927

2028

21-
class MyView(arcade.View):
29+
class MyView(UIView):
2230
def __init__(self):
2331
super().__init__()
24-
self.ui = UIManager()
32+
self.background_color = arcade.uicolor.BLUE_BELIZE_HOLE
2533

2634
grid = UIGridLayout(
2735
size_hint=(0, 0), # wrap children
28-
row_count=3, # user, pw and login button
36+
row_count=5, # title | user, pw | login button
2937
column_count=2, # label and input field
3038
vertical_spacing=10,
3139
horizontal_spacing=5,
3240
)
41+
grid.with_padding(all=50)
42+
grid.with_background(color=arcade.uicolor.GREEN_GREEN_SEA)
43+
44+
title = grid.add(
45+
UILabel(text="Login", width=150, font_size=20, font_name="Kenney Future"),
46+
column=0,
47+
row=0,
48+
column_span=2,
49+
)
50+
title.with_padding(bottom=20)
3351

34-
grid.add(UILabel(text="Username:", width=80), column=0, row=0)
35-
self.username_input = grid.add(UIInputText(), column=1, row=0)
36-
37-
grid.add(UILabel(text="Password:", width=80), column=0, row=1)
38-
self.password_input = grid.add(UIPasswordInput(), column=1, row=1)
52+
grid.add(UILabel(text="Username:", width=80, font_name="Kenney Future"), column=0, row=1)
53+
self.username_input = grid.add(
54+
UIInputText(width=150, font_name="Kenney Future"), column=1, row=1
55+
)
3956

40-
self.login_button = grid.add(UIFlatButton(text="Login"), column=0, row=2, column_span=2)
57+
grid.add(UILabel(text="Password:", width=80, font_name="Kenney Future"), column=0, row=2)
58+
self.password_input = grid.add(
59+
UIPasswordInput(width=150, font_name="Kenney Future"), column=1, row=2
60+
)
61+
self.password_input.with_background(color=arcade.uicolor.GREEN_GREEN_SEA)
62+
# set background to prevent full render on blinking caret
63+
64+
self.login_button = grid.add(
65+
UIFlatButton(text="Login", height=30, width=150, size_hint=(1, None)),
66+
column=0,
67+
row=3,
68+
column_span=2,
69+
)
4170
self.login_button.on_click = self.on_login
4271

72+
# add warning label
73+
self.warning_label = grid.add(
74+
UILabel(
75+
text="Use [enter] to switch fields, then enter to login",
76+
width=150,
77+
font_size=10,
78+
font_name="Kenney Future",
79+
),
80+
column=0,
81+
row=4,
82+
column_span=2,
83+
)
84+
4385
anchor = UIAnchorLayout() # to center grid on screen
4486
anchor.add(grid)
4587

46-
self.ui.add(anchor)
47-
48-
def on_login(self, event: UIOnClickEvent):
49-
print(f"User logged in with: {self.username_input.text} {self.password_input.text}")
50-
51-
def on_show_view(self):
52-
self.window.background_color = arcade.color.DARK_BLUE_GRAY
53-
# Enable UIManager when view is shown to catch window events
54-
self.ui.enable()
55-
56-
def on_hide_view(self):
57-
# Disable UIManager when view gets inactive
58-
self.ui.disable()
59-
60-
def on_draw(self):
61-
self.clear()
62-
self.ui.draw()
88+
self.add_widget(anchor)
89+
90+
# activate username input field
91+
self.username_input.activate()
92+
93+
def on_key_press(self, symbol: int, modifiers: int) -> bool | None:
94+
# if username field active, switch fields with enter
95+
if self.username_input.active:
96+
if symbol == arcade.key.ENTER:
97+
self.username_input.deactivate()
98+
self.password_input.activate()
99+
return True
100+
# if password field active, login with enter
101+
elif self.password_input.active:
102+
if symbol == arcade.key.ENTER:
103+
self.password_input.deactivate()
104+
self.on_login(None)
105+
return True
106+
return False
107+
108+
def on_login(self, event: UIOnClickEvent | None):
109+
username = self.username_input.text.strip()
110+
password = self.password_input.text.strip()
111+
print(f"User logged in with: {username} {password}")
63112

64113

65114
if __name__ == "__main__":

arcade/examples/gui/grid_layout.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@ def __init__(self):
2121
super().__init__()
2222
self.ui = UIManager()
2323

24-
dummy1 = UIDummy(width=100, height=100)
24+
dummy1 = UIDummy(size_hint=(1, 1))
2525
dummy2 = UIDummy(width=50, height=50)
2626
dummy3 = UIDummy(width=50, height=50, size_hint=(0.5, 0.5))
27-
dummy4 = UIDummy(width=100, height=100)
28-
dummy5 = UIDummy(width=200, height=100)
29-
dummy6 = UIDummy(width=100, height=300)
27+
dummy4 = UIDummy(size_hint=(1, 1))
28+
dummy5 = UIDummy(size_hint=(1, 1))
29+
dummy6 = UIDummy(size_hint=(1, 1))
3030

3131
subject = (
3232
UIGridLayout(
3333
column_count=3,
3434
row_count=3,
3535
size_hint=(0.5, 0.5),
3636
)
37-
.with_border()
38-
.with_padding()
37+
.with_border(color=arcade.color.RED)
38+
.with_padding(all=2)
3939
)
4040

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

5151
self.ui.add(anchor)
5252

53+
self.ui.execute_layout()
54+
print(subject.size)
55+
self.grid = subject
56+
5357
def on_show_view(self):
5458
self.window.background_color = arcade.color.DARK_BLUE_GRAY
5559
# Enable UIManager when view is shown to catch window events
@@ -59,6 +63,11 @@ def on_hide_view(self):
5963
# Disable UIManager when view gets inactive
6064
self.ui.disable()
6165

66+
def on_key_press(self, symbol: int, modifiers: int) -> bool | None:
67+
if symbol == arcade.key.D:
68+
self.grid.legacy_mode = not self.grid.legacy_mode
69+
return True
70+
6271
def on_draw(self):
6372
self.clear()
6473
self.ui.draw()

arcade/examples/gui/gui_slider.py

Lines changed: 0 additions & 61 deletions
This file was deleted.

arcade/gui/experimental/password_input.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66

77

88
class UIPasswordInput(UIInputText):
9-
"""A password input field. The text is hidden with asterisks."""
9+
"""A password input field. The text is hidden with asterisks.
10+
11+
Hint: It is recommended to set a background color to prevent full render cycles
12+
when the caret blinks.
13+
14+
"""
1015

1116
def on_event(self, event: UIEvent) -> Optional[bool]:
1217
"""Remove new lines from the input, which are not allowed in passwords."""

arcade/gui/surface.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,12 @@ def limit(self, rect: Rect):
197197
w = max(w, 1)
198198
h = max(h, 1)
199199

200+
# round to nearest pixel, to avoid off by 1-pixel errors in ui
200201
viewport_rect = LBWH(
201-
int(l * self._pixel_ratio),
202-
int(b * self._pixel_ratio),
203-
int(w * self._pixel_ratio),
204-
int(h * self._pixel_ratio),
202+
round(l * self._pixel_ratio),
203+
round(b * self._pixel_ratio),
204+
round(w * self._pixel_ratio),
205+
round(h * self._pixel_ratio),
205206
)
206207
self.fbo.viewport = viewport_rect.viewport
207208

arcade/gui/widgets/__init__.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class UIWidget(EventDispatcher, ABC):
6060
rect: Rect = Property(LBWH(0, 0, 1, 1)) # type: ignore
6161
visible: bool = Property(True) # type: ignore
6262

63-
size_hint: Optional[Tuple[float, float]] = Property(None) # type: ignore
63+
size_hint: Optional[Tuple[float | None, float | None]] = Property(None) # type: ignore
6464
size_hint_min: Optional[Tuple[float, float]] = Property(None) # type: ignore
6565
size_hint_max: Optional[Tuple[float, float]] = Property(None) # type: ignore
6666

@@ -83,9 +83,9 @@ def __init__(
8383
height: float = 100,
8484
children: Iterable["UIWidget"] = tuple(),
8585
# Properties which might be used by layouts
86-
size_hint=None, # in percentage
87-
size_hint_min=None, # in pixel
88-
size_hint_max=None, # in pixel
86+
size_hint: Optional[Tuple[float | None, float | None]] = None, # in percentage
87+
size_hint_min: Optional[Tuple[float, float]] = None, # in pixel
88+
size_hint_max: Optional[Tuple[float, float]] = None, # in pixel
8989
**kwargs,
9090
):
9191
self._requires_render = True
@@ -510,6 +510,9 @@ def center_on_screen(self: W) -> W:
510510
self.rect = self.rect.align_center(center)
511511
return self
512512

513+
def __repr__(self):
514+
return f"<{self.__class__.__name__} {self.rect.lbwh}>"
515+
513516

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

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

0 commit comments

Comments
 (0)