From 9fc44f897f5417c1b6fae7d904ad9abba2c6e29b Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Tue, 19 Nov 2024 13:45:24 +0100 Subject: [PATCH 1/6] Small cleanup --- arcade/future/splash.py | 2 +- arcade/gui/ui_manager.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/arcade/future/splash.py b/arcade/future/splash.py index 5f3318fc62..104279c7e0 100644 --- a/arcade/future/splash.py +++ b/arcade/future/splash.py @@ -21,7 +21,7 @@ class ArcadeSplash(View): dark_mode: If True, the splash screen will be shown in dark mode. (Default False) """ - def __init__(self, view: View, duration: int = 3, dark_mode: bool = False): + def __init__(self, view: View, duration: float = 1.5, dark_mode: bool = False): super().__init__() self._next_view = view self._duration = duration diff --git a/arcade/gui/ui_manager.py b/arcade/gui/ui_manager.py index b3731940dc..22264bacb3 100644 --- a/arcade/gui/ui_manager.py +++ b/arcade/gui/ui_manager.py @@ -88,6 +88,7 @@ def on_draw(): _enabled = False + DEFAULT_LAYER = 0 OVERLAY_LAYER = 10 def __init__(self, window: Optional[arcade.Window] = None): @@ -103,7 +104,7 @@ def __init__(self, window: Optional[arcade.Window] = None): self.register_event_type("on_event") - def add(self, widget: W, *, index=None, layer=0) -> W: + def add(self, widget: W, *, index=None, layer=DEFAULT_LAYER) -> W: """Add a widget to the :class:`UIManager`. Added widgets will receive ui events and be rendered. @@ -141,7 +142,9 @@ def remove(self, child: UIWidget): child.parent = None self.trigger_render() - def walk_widgets(self, *, root: Optional[UIWidget] = None, layer=0) -> Iterable[UIWidget]: + def walk_widgets( + self, *, root: Optional[UIWidget] = None, layer=DEFAULT_LAYER + ) -> Iterable[UIWidget]: """Walks through widget tree, in reverse draw order (most top drawn widget first) Args: @@ -165,7 +168,9 @@ def clear(self): for widget in layer[:]: self.remove(widget) - def get_widgets_at(self, pos: Point2, cls: type[W] = UIWidget, layer=0) -> Iterable[W]: + def get_widgets_at( + self, pos: Point2, cls: type[W] = UIWidget, layer=DEFAULT_LAYER + ) -> Iterable[W]: """Yields all widgets containing a position, returns first top laying widgets which is instance of cls. From fc270c191eb894ab38a592f555bc58669649a459 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Tue, 19 Nov 2024 13:46:14 +0100 Subject: [PATCH 2/6] Fix some linting --- arcade/camera/camera_2d.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arcade/camera/camera_2d.py b/arcade/camera/camera_2d.py index 549009b3f3..a1a03ae4b6 100644 --- a/arcade/camera/camera_2d.py +++ b/arcade/camera/camera_2d.py @@ -484,8 +484,8 @@ def point_in_view(self, point: Point2) -> bool: """ # This is unwrapped from standard Vec2 operations, # The construction and garbage collection of the vectors would - # increase this method's cost by ~4x - + # increase this method's cost by ~4x + pos = self.position diff = point[0] - pos[0], point[1] - pos[1] @@ -569,7 +569,6 @@ def projection(self) -> Rect: @projection.setter def projection(self, value: Rect) -> None: - # Unpack and validate if not value: raise ZeroProjectionDimension((f"Projection area is 0, {value.lrbt}")) From 5eda3c4fd1e968dbfa5fcad3d423e1835bda7392 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Tue, 19 Nov 2024 11:41:25 +0100 Subject: [PATCH 3/6] gui: Check transparency to determine if background clears the widgets area or use rect draw. This can be overwritten by setting UIWidget._strong_background for now. --- arcade/gui/widgets/__init__.py | 21 +++++++++++++++------ arcade/gui/widgets/text.py | 4 +++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/arcade/gui/widgets/__init__.py b/arcade/gui/widgets/__init__.py index 7b1279e61d..7221dbb831 100644 --- a/arcade/gui/widgets/__init__.py +++ b/arcade/gui/widgets/__init__.py @@ -1,14 +1,14 @@ from __future__ import annotations from abc import ABC -from typing import NamedTuple, Iterable, Optional, Union, TYPE_CHECKING, TypeVar, Tuple, List, Dict +from typing import Dict, Iterable, List, NamedTuple, Optional, TYPE_CHECKING, Tuple, TypeVar, Union -from pyglet.event import EventDispatcher, EVENT_HANDLED, EVENT_UNHANDLED +from pyglet.event import EVENT_HANDLED, EVENT_UNHANDLED, EventDispatcher from pyglet.math import Vec2 from typing_extensions import Self import arcade -from arcade import Sprite, Texture, LBWH, Rect +from arcade import LBWH, Rect, Sprite, Texture from arcade.color import TRANSPARENT_BLACK from arcade.gui.events import ( UIEvent, @@ -19,9 +19,9 @@ UIOnUpdateEvent, ) from arcade.gui.nine_patch import NinePatchTexture -from arcade.gui.property import Property, bind, ListProperty +from arcade.gui.property import ListProperty, Property, bind from arcade.gui.surface import Surface -from arcade.types import Color, AnchorPoint, AsFloat +from arcade.types import AnchorPoint, AsFloat, Color from arcade.utils import copy_dunders_unimplemented if TYPE_CHECKING: @@ -73,6 +73,11 @@ class UIWidget(EventDispatcher, ABC): _padding_right = Property(0) _padding_bottom = Property(0) _padding_left = Property(0) + _strong_background = Property(False) + """If True, the background will clear the surface, even if it is not fully opaque. + This is not part of the public API and subject to change. + UILabel have a strong background if set. + """ def __init__( self, @@ -116,6 +121,7 @@ def __init__( bind(self, "_padding_right", self.trigger_render) bind(self, "_padding_bottom", self.trigger_render) bind(self, "_padding_left", self.trigger_render) + bind(self, "_strong_background", self.trigger_render) def add(self, child: W, **kwargs) -> W: """Add a widget as a child. @@ -253,7 +259,10 @@ def do_render_base(self, surface: Surface): # draw background if self._bg_color: - surface.clear(self._bg_color) + if self._bg_color.a == 255 or self._strong_background: + surface.clear(self._bg_color) + else: + arcade.draw_rect_filled(LBWH(0, 0, self.width, self.height), color=self._bg_color) # draw background texture if self._bg_tex: surface.draw_texture(x=0, y=0, width=self.width, height=self.height, tex=self._bg_tex) diff --git a/arcade/gui/widgets/text.py b/arcade/gui/widgets/text.py index 52847951f9..186274bb2d 100644 --- a/arcade/gui/widgets/text.py +++ b/arcade/gui/widgets/text.py @@ -25,7 +25,7 @@ from arcade.gui.widgets import UIWidget from arcade.gui.widgets.layout import UIAnchorLayout from arcade.text import FontNameOrNames -from arcade.types import LBWH, RGBA255, Color, RGBOrA255 +from arcade.types import Color, LBWH, RGBA255, RGBOrA255 class UILabel(UIWidget): @@ -119,6 +119,8 @@ def __init__( multiline=multiline, **kwargs, ) + self._strong_background = True + if adaptive_multiline: # +1 is required to prevent line wrap width = self._label.content_width + 1 From 9238761eb9ccec48d896872a598309c15c227436 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Tue, 19 Nov 2024 13:37:40 +0100 Subject: [PATCH 4/6] override pyglet text blending --- arcade/text.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/arcade/text.py b/arcade/text.py index a060d686ca..31931ea5c9 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -18,6 +18,67 @@ __all__ = ["load_font", "Text", "create_text_sprite", "draw_text"] +class ArcadeTextLayoutGroup(pyglet.text.layout.TextLayoutGroup): + """Create a text layout rendering group. + + Overrides pyglet blending handling to allow for additive blending. + Furthermore, it resets the blend function to the previous state. + """ + + _prev_blend = None + _prev_blend_func = None + + def set_state(self) -> None: + import pyglet.gl as gl + from ctypes import c_int, c_ubyte + + self.program.use() + self.program["scissor"] = False + + gl.glActiveTexture(gl.GL_TEXTURE0) + gl.glBindTexture(self.texture.target, self.texture.id) + + blend = c_ubyte() + gl.glGetBooleanv(gl.GL_BLEND, blend) + self._prev_blend = bool(blend.value) + + src_rgb = c_int() + dst_rgb = c_int() + src_alpha = c_int() + dst_alpha = c_int() + gl.glGetIntegerv(gl.GL_BLEND_SRC_RGB, src_rgb) + gl.glGetIntegerv(gl.GL_BLEND_DST_RGB, dst_rgb) + gl.glGetIntegerv(gl.GL_BLEND_SRC_ALPHA, src_alpha) + gl.glGetIntegerv(gl.GL_BLEND_DST_ALPHA, dst_alpha) + + self._prev_blend_func = (src_rgb.value, dst_rgb.value, src_alpha.value, dst_alpha.value) + + gl.glEnable(gl.GL_BLEND) + gl.glBlendFuncSeparate( + gl.GL_SRC_ALPHA, + gl.GL_ONE_MINUS_SRC_ALPHA, + gl.GL_ONE, + gl.GL_ONE, + ) + + def unset_state(self) -> None: + import pyglet.gl as gl + + if not self._prev_blend: + gl.glDisable(gl.GL_BLEND) + + gl.glBlendFuncSeparate( + self._prev_blend_func[0], + self._prev_blend_func[1], + self._prev_blend_func[2], + self._prev_blend_func[3], + ) + self.program.stop() + + +pyglet.text.layout.TextLayout.group_class = ArcadeTextLayoutGroup + + def load_font(path: str | Path) -> None: """ Load fonts in a file (usually .ttf) adding them to a global font registry. @@ -253,7 +314,8 @@ def __init__( bold=bold, italic=italic, multiline=multiline, - rotation=rotation, # type: ignore # pending https://github.com/pyglet/pyglet/issues/843 + rotation=rotation, + # type: ignore # pending https://github.com/pyglet/pyglet/issues/843 batch=batch, group=group, **kwargs, From 46fd0bf714639f6fd87b338be1545dbd46660b50 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Tue, 19 Nov 2024 13:49:17 +0100 Subject: [PATCH 5/6] Fix some linting --- arcade/gui/widgets/__init__.py | 2 +- arcade/text.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arcade/gui/widgets/__init__.py b/arcade/gui/widgets/__init__.py index 7221dbb831..cf39e3b690 100644 --- a/arcade/gui/widgets/__init__.py +++ b/arcade/gui/widgets/__init__.py @@ -75,7 +75,7 @@ class UIWidget(EventDispatcher, ABC): _padding_left = Property(0) _strong_background = Property(False) """If True, the background will clear the surface, even if it is not fully opaque. - This is not part of the public API and subject to change. + This is not part of the public API and subject to change. UILabel have a strong background if set. """ diff --git a/arcade/text.py b/arcade/text.py index 31931ea5c9..de7ac9abf8 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -25,8 +25,8 @@ class ArcadeTextLayoutGroup(pyglet.text.layout.TextLayoutGroup): Furthermore, it resets the blend function to the previous state. """ - _prev_blend = None - _prev_blend_func = None + _prev_blend: bool + _prev_blend_func: tuple[int, int, int, int] def set_state(self) -> None: import pyglet.gl as gl From 976e6b8ec99af70a62e98a3a3bd17adf2ab05d13 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Tue, 19 Nov 2024 14:03:47 +0100 Subject: [PATCH 6/6] Fix some linting --- arcade/gui/widgets/text.py | 2 +- arcade/text.py | 40 +++++++++++++++++--------------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/arcade/gui/widgets/text.py b/arcade/gui/widgets/text.py index 186274bb2d..5b1e628c2f 100644 --- a/arcade/gui/widgets/text.py +++ b/arcade/gui/widgets/text.py @@ -25,7 +25,7 @@ from arcade.gui.widgets import UIWidget from arcade.gui.widgets.layout import UIAnchorLayout from arcade.text import FontNameOrNames -from arcade.types import Color, LBWH, RGBA255, RGBOrA255 +from arcade.types import LBWH, RGBA255, Color, RGBOrA255 class UILabel(UIWidget): diff --git a/arcade/text.py b/arcade/text.py index de7ac9abf8..fe404839fd 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -4,6 +4,7 @@ from __future__ import annotations +from ctypes import c_int, c_ubyte from pathlib import Path from typing import Any, Union @@ -18,7 +19,7 @@ __all__ = ["load_font", "Text", "create_text_sprite", "draw_text"] -class ArcadeTextLayoutGroup(pyglet.text.layout.TextLayoutGroup): +class _ArcadeTextLayoutGroup(pyglet.text.layout.TextLayoutGroup): """Create a text layout rendering group. Overrides pyglet blending handling to allow for additive blending. @@ -29,45 +30,40 @@ class ArcadeTextLayoutGroup(pyglet.text.layout.TextLayoutGroup): _prev_blend_func: tuple[int, int, int, int] def set_state(self) -> None: - import pyglet.gl as gl - from ctypes import c_int, c_ubyte - self.program.use() self.program["scissor"] = False - gl.glActiveTexture(gl.GL_TEXTURE0) - gl.glBindTexture(self.texture.target, self.texture.id) + pyglet.gl.glActiveTexture(pyglet.gl.GL_TEXTURE0) + pyglet.gl.glBindTexture(self.texture.target, self.texture.id) blend = c_ubyte() - gl.glGetBooleanv(gl.GL_BLEND, blend) + pyglet.gl.glGetBooleanv(pyglet.gl.GL_BLEND, blend) self._prev_blend = bool(blend.value) src_rgb = c_int() dst_rgb = c_int() src_alpha = c_int() dst_alpha = c_int() - gl.glGetIntegerv(gl.GL_BLEND_SRC_RGB, src_rgb) - gl.glGetIntegerv(gl.GL_BLEND_DST_RGB, dst_rgb) - gl.glGetIntegerv(gl.GL_BLEND_SRC_ALPHA, src_alpha) - gl.glGetIntegerv(gl.GL_BLEND_DST_ALPHA, dst_alpha) + pyglet.gl.glGetIntegerv(pyglet.gl.GL_BLEND_SRC_RGB, src_rgb) + pyglet.gl.glGetIntegerv(pyglet.gl.GL_BLEND_DST_RGB, dst_rgb) + pyglet.gl.glGetIntegerv(pyglet.gl.GL_BLEND_SRC_ALPHA, src_alpha) + pyglet.gl.glGetIntegerv(pyglet.gl.GL_BLEND_DST_ALPHA, dst_alpha) self._prev_blend_func = (src_rgb.value, dst_rgb.value, src_alpha.value, dst_alpha.value) - gl.glEnable(gl.GL_BLEND) - gl.glBlendFuncSeparate( - gl.GL_SRC_ALPHA, - gl.GL_ONE_MINUS_SRC_ALPHA, - gl.GL_ONE, - gl.GL_ONE, + pyglet.gl.glEnable(pyglet.gl.GL_BLEND) + pyglet.gl.glBlendFuncSeparate( + pyglet.gl.GL_SRC_ALPHA, + pyglet.gl.GL_ONE_MINUS_SRC_ALPHA, + pyglet.gl.GL_ONE, + pyglet.gl.GL_ONE, ) def unset_state(self) -> None: - import pyglet.gl as gl - if not self._prev_blend: - gl.glDisable(gl.GL_BLEND) + pyglet.gl.glDisable(pyglet.gl.GL_BLEND) - gl.glBlendFuncSeparate( + pyglet.gl.glBlendFuncSeparate( self._prev_blend_func[0], self._prev_blend_func[1], self._prev_blend_func[2], @@ -76,7 +72,7 @@ def unset_state(self) -> None: self.program.stop() -pyglet.text.layout.TextLayout.group_class = ArcadeTextLayoutGroup +pyglet.text.layout.TextLayout.group_class = _ArcadeTextLayoutGroup def load_font(path: str | Path) -> None: