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.types import RGBA255
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
[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)