Source code for arcade.gui.widgets.buttons
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional, Dict, Union
import arcade
from arcade import Texture
from arcade.gui.nine_patch import NinePatchTexture
from arcade.gui.property import bind, DictProperty
from arcade.gui.style import UIStyleBase, UIStyledWidget
from arcade.gui.surface import Surface
from arcade.gui.widgets import UIInteractiveWidget
from arcade.gui.widgets.text import UITextWidget
from arcade.text import FontNameOrNames
from arcade.types import RGBA255
[docs]
@dataclass
class UITextureButtonStyle(UIStyleBase):
"""
Used to style the texture button. Below is its use case.
.. code:: py
button = UITextureButton(style={"normal": UITextureButton.UIStyle(...),})
"""
font_size: int = 12
font_name: FontNameOrNames = ("calibri", "arial")
font_color: RGBA255 = arcade.color.WHITE
border_width: int = 2
[docs]
class UITextureButton(
UIInteractiveWidget, UIStyledWidget[UITextureButtonStyle], UITextWidget
):
"""
A button with an image for the face of the button.
There are four states of the UITextureButton i.e normal, hovered, pressed and disabled.
:param x: x coordinate of bottom left
:param y: y coordinate of bottom left
:param width: width of widget. Defaults to texture width if not specified.
:param height: height of widget. Defaults to texture height if not specified.
:param texture: texture to display for the widget.
:param texture_hovered: different texture to display if mouse is hovering over button.
:param texture_pressed: different texture to display if mouse button is pressed while hovering over button.
:param text: text to add to the button.
:param multiline: allows to wrap text, if not enough width available
:param style: Used to style the button for different states.
:param scale: scale the button, based on the base texture size.
:param size_hint: Tuple of floats (0.0-1.0), how much space of the parent should be requested
:param size_hint_min: min width and height in pixel
:param size_hint_max: max width and height in pixel
"""
_textures: Dict[str, Union[Texture, NinePatchTexture]] = DictProperty() # type: ignore
UIStyle = UITextureButtonStyle
DEFAULT_STYLE = {
"normal": UIStyle(),
"hover": UIStyle(
font_size=12,
font_name=("calibri", "arial"),
font_color=arcade.color.WHITE,
border_width=2,
),
"press": UIStyle(
font_size=12,
font_name=("calibri", "arial"),
font_color=arcade.color.BLACK,
border_width=2,
),
"disabled": UIStyle(
font_size=12,
font_name=("calibri", "arial"),
font_color=arcade.color.WHITE,
border_width=2,
),
}
def __init__(
self,
*,
x: float = 0,
y: float = 0,
width: Optional[float] = None,
height: Optional[float] = None,
texture: Union[None, Texture, NinePatchTexture] = None,
texture_hovered: Union[None, Texture, NinePatchTexture] = None,
texture_pressed: Union[None, Texture, NinePatchTexture] = None,
texture_disabled: Union[None, Texture, NinePatchTexture] = None,
text: str = "",
multiline: bool = False,
scale: Optional[float] = None,
style: Optional[Dict[str, UIStyleBase]] = None,
size_hint=None,
size_hint_min=None,
size_hint_max=None,
**kwargs,
):
if width is None and texture is not None:
width = texture.size[0]
if height is None and texture is not None:
height = texture.size[1]
if width is None:
raise ValueError("Unable to determine a width.")
if height is None:
raise ValueError("Unable to determine a height.")
if scale is not None and texture is not None:
width = texture.size[0] * scale
height = texture.size[1] * scale
super().__init__(
x=x,
y=y,
width=width,
height=height,
style=style or self.DEFAULT_STYLE,
size_hint=size_hint,
size_hint_min=size_hint_min,
size_hint_max=size_hint_max,
text=text,
multiline=multiline,
**kwargs,
)
self._textures = {}
if texture:
self._textures["normal"] = texture
self._textures["hover"] = texture
self._textures["press"] = texture
self._textures["disabled"] = texture
if texture_hovered:
self._textures["hover"] = texture_hovered
if texture_pressed:
self._textures["press"] = texture_pressed
if texture_disabled:
self._textures["disabled"] = texture_disabled
bind(self, "_textures", self.trigger_render)
[docs]
def get_current_state(self) -> str:
"""Returns the current state of the button i.e disabled, press, hover or normal."""
if self.disabled:
return "disabled"
elif self.pressed:
return "press"
elif self.hovered:
return "hover"
else:
return "normal"
@property
def texture(self):
"""Returns the normal texture for the face of the button."""
return self._textures["normal"]
@texture.setter
def texture(self, value: Texture):
self._textures["normal"] = value
self.trigger_render()
@property
def texture_hovered(self):
"""Returns the hover texture for the face of the button."""
return self._textures["hover"]
@texture_hovered.setter
def texture_hovered(self, value: Texture):
self._textures["hover"] = value
self.trigger_render()
@property
def texture_pressed(self):
"""Returns the pressed texture for the face of the button."""
return self._textures["press"]
@texture_pressed.setter
def texture_pressed(self, value: Texture):
self._textures["press"] = value
self.trigger_render()
[docs]
def do_render(self, surface: Surface):
self.prepare_render(surface)
style = self.get_current_style()
# update label
self._apply_style(style)
current_state = self.get_current_state()
current_texture = self._textures.get(current_state)
if current_texture:
surface.draw_texture(
0, 0, self.content_width, self.content_height, current_texture
)
def _apply_style(self, style: UITextureButtonStyle):
"""
Callback which is called right before rendering to apply changes for rendering.
"""
font_name = style.get("font_name", UIFlatButton.UIStyle.font_name)
font_size = style.get("font_size", UIFlatButton.UIStyle.font_size)
font_color = style.get("font_color", UIFlatButton.UIStyle.font_color)
font_name_changed = self._label.label.font_name != font_name
font_size_changed = self._label.label.font_size != font_size
font_color_changed = self._label.label.color != font_color
if font_name_changed or font_size_changed or font_color_changed:
with self._label.label:
self._label.label.font_name = font_name
self._label.label.font_size = font_size
self._label.label.color = font_color
# make label fit its content, but limit size to button size
self._label.fit_content()
self.ui_label.rect = self.ui_label.rect.max_size(
self.content_width, self.content_height
)
[docs]
class UIFlatButton(UIInteractiveWidget, UIStyledWidget, UITextWidget):
"""
A text button, with support for background color and a border.
There are four states of the UITextureButton i.e normal, hovered, pressed and disabled.
:param x: x coordinate of bottom left
:param y: y coordinate of bottom left
:param width: width of widget. Defaults to texture width if not specified.
:param height: height of widget. Defaults to texture height if not specified.
:param text: text to add to the button.
:param multiline: allows to wrap text, if not enough width available
:param style: Used to style the button
"""
[docs]
@dataclass
class UIStyle(UIStyleBase):
"""
Used to style the button. Below is its use case.
.. code:: py
button = UIFlatButton(style={"normal": UIFlatButton.UIStyle(...),})
"""
font_size: int = 12
font_name: FontNameOrNames = ("calibri", "arial")
font_color: RGBA255 = arcade.color.WHITE
bg: RGBA255 = (21, 19, 21, 255)
border: Optional[RGBA255] = None
border_width: int = 0
DEFAULT_STYLE = {
"normal": UIStyle(),
"hover": UIStyle(
font_size=12,
font_name=("calibri", "arial"),
font_color=arcade.color.WHITE,
bg=(21, 19, 21, 255),
border=(77, 81, 87, 255),
border_width=2,
),
"press": UIStyle(
font_size=12,
font_name=("calibri", "arial"),
font_color=arcade.color.BLACK,
bg=arcade.color.WHITE,
border=arcade.color.WHITE,
border_width=2,
),
"disabled": UIStyle(
font_size=12,
font_name=("calibri", "arial"),
font_color=arcade.color.WHITE,
bg=arcade.color.GRAY,
border=None,
border_width=2,
),
}
def __init__(
self,
*,
x: float = 0,
y: float = 0,
width: float = 100,
height: float = 50,
text="",
multiline=False,
size_hint=None,
size_hint_min=None,
size_hint_max=None,
style=None,
**kwargs,
):
super().__init__(
x=x,
y=y,
width=width,
height=height,
size_hint=size_hint,
size_hint_min=size_hint_min,
size_hint_max=size_hint_max,
style=style or self.DEFAULT_STYLE,
text=text,
multiline=multiline,
**kwargs,
)
[docs]
def get_current_state(self) -> str:
"""Returns the current state of the button i.e disabled, press, hover or normal."""
if self.disabled:
return "disabled"
elif self.pressed:
return "press"
elif self.hovered:
return "hover"
else:
return "normal"
[docs]
def do_render(self, surface: Surface):
self.prepare_render(surface)
style: UIFlatButton.UIStyle = self.get_current_style()
# update label
self._apply_style(style)
# Render button
border_width = style.get("border_width", UIFlatButton.UIStyle.border_width)
border_color = style.get("border", UIFlatButton.UIStyle.border)
bg_color = style.get("bg", UIFlatButton.UIStyle.bg)
if bg_color:
surface.clear(bg_color)
# render button border (which is not the widgets border)
if border_color and border_width:
arcade.draw_xywh_rectangle_outline(
border_width,
border_width,
self.content_width - 2 * border_width,
self.content_height - 2 * border_width,
color=border_color,
border_width=border_width,
)
def _apply_style(self, style: UIStyle):
"""
Callback which is called right before rendering to apply changes for rendering.
"""
font_name = style.get("font_name", UIFlatButton.UIStyle.font_name)
font_size = style.get("font_size", UIFlatButton.UIStyle.font_size)
font_color = style.get("font_color", UIFlatButton.UIStyle.font_color)
font_name_changed = self._label.label.font_name != font_name
font_size_changed = self._label.label.font_size != font_size
font_color_changed = self._label.label.color != font_color
if font_name_changed or font_size_changed or font_color_changed:
with self._label.label:
self._label.label.font_name = font_name
self._label.label.font_size = font_size
self._label.label.color = font_color
# make label fit its content, but limit size to button size
self._label.fit_content()
self.ui_label.rect = self.ui_label.rect.max_size(
self.content_width, self.content_height
)