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

Gui/small improvements #2321

Merged
merged 5 commits into from
Jul 27, 2024
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
5 changes: 5 additions & 0 deletions arcade/gui/surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from contextlib import contextmanager
from typing import Generator, Optional

from PIL import Image
from typing_extensions import Self

import arcade
Expand Down Expand Up @@ -251,3 +252,7 @@ def resize(self, *, size: tuple[int, int], pixel_ratio: float) -> None:
self.texture = self.ctx.texture(self.size_scaled, components=4)
self.fbo = self.ctx.framebuffer(color_attachments=[self.texture])
self.fbo.clear()

def to_image(self) -> Image.Image:
"""Convert the surface to an PIL image"""
return self.ctx.get_framebuffer_image(self.fbo)
54 changes: 20 additions & 34 deletions arcade/gui/widgets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

from abc import ABC
from random import randint
from typing import NamedTuple, Iterable, Optional, Union, TYPE_CHECKING, TypeVar, Tuple, List, Dict

from pyglet.event import EventDispatcher, EVENT_HANDLED, EVENT_UNHANDLED
Expand All @@ -22,7 +21,7 @@
from arcade.gui.nine_patch import NinePatchTexture
from arcade.gui.property import Property, bind, ListProperty
from arcade.gui.surface import Surface
from arcade.types import RGBA255, Color, AnchorPoint, AsFloat
from arcade.types import Color, AnchorPoint, AsFloat
from arcade.utils import copy_dunders_unimplemented

if TYPE_CHECKING:
Expand Down Expand Up @@ -389,7 +388,7 @@ def resize(self, *, width=None, height=None, anchor: Vec2 = AnchorPoint.CENTER):
"""
self.rect = self.rect.resize(width=width, height=height, anchor=anchor)

def with_border(self, *, width=2, color=arcade.color.GRAY) -> Self:
def with_border(self, *, width=2, color: Color | None = arcade.color.GRAY) -> Self:
"""Sets border properties

Args:
Expand Down Expand Up @@ -523,11 +522,9 @@ class UIInteractiveWidget(UIWidget):
size_hint: Tuple of floats (0.0-1.0), how much space of the
parent should be requested
size_hint_min: min width and height in pixel
size_hint_max: max width and height in pixel:param x: center x
of widget
size_hint_max: max width and height in pixel
interaction_buttons: defines, which mouse buttons should trigger
the interaction (default: left mouse button)
style: not used
"""

# States
Expand Down Expand Up @@ -617,8 +614,9 @@ def on_click(self, event: UIOnClickEvent):


class UIDummy(UIInteractiveWidget):
"""Solid color widget used for testing & examples
"""Solid color widget used for testing & examples.

Starts with a random color.
It should not be subclassed for real-world usage.

When clicked, it does the following:
Expand All @@ -629,14 +627,13 @@ class UIDummy(UIInteractiveWidget):
Args:
x: x coordinate of bottom left
y: y coordinate of bottom left
color: fill color for the widget
width: width of widget
height: height of widget
size_hint: Tuple of floats (0.0-1.0), how much space of the
parent should be requested
size_hint_min: min width and height in pixel
size_hint_max: max width and height in pixel
style: not used
**kwargs: passed to UIWidget
"""

def __init__(
Expand All @@ -659,25 +656,22 @@ def __init__(
size_hint=size_hint,
size_hint_min=size_hint_min,
size_hint_max=size_hint_max,
**kwargs,
)
self.color: RGBA255 = (randint(0, 255), randint(0, 255), randint(0, 255), 255)
self.border_color = arcade.color.BATTLESHIP_GREY
self.border_width = 0
self.with_background(color=Color.random(a=255))
self.with_border(color=arcade.color.BATTLESHIP_GREY, width=0)

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

def on_update(self, dt):
"""Update the border of the widget if hovered"""
self.border_width = 2 if self.hovered else 0
self.border_color = arcade.color.WHITE if self.pressed else arcade.color.BATTLESHIP_GREY

def do_render(self, surface: Surface):
"""Render solid color"""
self.prepare_render(surface)
surface.clear(self.color)
self.with_border(
width=2 if self.hovered else 0,
color=arcade.color.WHITE if self.pressed else arcade.color.BATTLESHIP_GREY,
)


class UISpriteWidget(UIWidget):
Expand All @@ -693,7 +687,6 @@ class UISpriteWidget(UIWidget):
parent should be requested
size_hint_min: min width and height in pixel
size_hint_max: max width and height in pixel
style: not used
"""

def __init__(
Expand Down Expand Up @@ -749,7 +742,6 @@ class UILayout(UIWidget):
parent should be requested
size_hint_min: min width and height in pixel
size_hint_max: max width and height in pixel
style: not used
"""

@staticmethod
Expand Down Expand Up @@ -809,7 +801,8 @@ class UISpace(UIWidget):
y: y coordinate of bottom left
width: width of widget
height: height of widget
color: Color for widget area
color: Color for widget area, if None, it will be transparent
(this will set the background color)
size_hint: Tuple of floats (0.0-1.0), how much space of the
parent should be requested
size_hint_min: min width and height in pixel
Expand Down Expand Up @@ -840,20 +833,13 @@ def __init__(
size_hint_max=size_hint_max,
**kwargs,
)
self._color = color
self.with_background(color=color)

@property
def color(self):
"""Color of the widget"""
return self._color
"""Color of the widget, alias for background color"""
return self._bg_color

@color.setter
def color(self, value):
self._color = value
self.trigger_render()

def do_render(self, surface: Surface):
"""Render the widget, mainly the background color"""
self.prepare_render(surface)
if self._color:
surface.clear(self._color)
self.with_background(color=value)
15 changes: 13 additions & 2 deletions arcade/gui/widgets/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ class UIInputText(UIWidget):
around the caret. Arcade confirms that the field is active before allowing
users to type, so it is okay to have multiple of these.

By default, a border is drawn around the input field.

Args:
x: x position (default anchor is bottom-left).
y: y position (default anchor is bottom-left).
Expand All @@ -380,6 +382,9 @@ class UIInputText(UIWidget):
is the same thing as a :py:class:`~arcade.gui.UITextArea`.
caret_color: An RGBA or RGB color for the caret with each
channel between 0 and 255, inclusive.
border_color: An RGBA or RGB color for the border with each
channel between 0 and 255, inclusive, can be None to remove border.
border_width: Width of the border in pixels.
size_hint: A tuple of floats between 0 and 1 defining the amount
of space of the parent should be requested.
size_hint_min: Minimum size hint width and height in pixel.
Expand All @@ -398,13 +403,15 @@ def __init__(
x: float = 0,
y: float = 0,
width: float = 100,
height: float = 24,
height: float = 23, # required height for font size 12 + border width 1
text: str = "",
font_name=("Arial",),
font_size: float = 12,
text_color: RGBOrA255 = arcade.color.WHITE,
multiline=False,
caret_color: RGBOrA255 = arcade.color.WHITE,
border_color: Color | None = arcade.color.WHITE,
border_width: int = 2,
size_hint=None,
size_hint_min=None,
size_hint_max=None,
Expand All @@ -421,6 +428,8 @@ def __init__(
**kwargs,
)

self.with_border(color=border_color, width=border_width)

self._active = False
self._text_color = Color.from_iterable(text_color)

Expand Down Expand Up @@ -467,7 +476,8 @@ def on_event(self, event: UIEvent) -> Optional[bool]:
if not self._active and isinstance(event, UIMousePressEvent):
if self.rect.point_in_rect(event.pos):
self.activate()
return EVENT_HANDLED
# return unhandled to allow other widgets to deactivate
return EVENT_UNHANDLED

# If active check to deactivate
if self._active and isinstance(event, UIMousePressEvent):
Expand All @@ -477,6 +487,7 @@ def on_event(self, event: UIEvent) -> Optional[bool]:
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
Expand Down
Loading