Source code for arcade.gui.surface

from contextlib import contextmanager
from typing import Tuple

import arcade
from arcade import Texture
from arcade.gl import Framebuffer
from arcade.gl import geometry


[docs]class Surface: """ Holds a :class:`arcade.gl.Framebuffer` and abstracts the drawing on it. Used internally for rendering widgets. """ def __init__( self, *, size: Tuple[int, int], position: Tuple[int, int] = (0, 0), pixel_ratio: float = 1.0, ): self.ctx = arcade.get_window().ctx self._size = size self._pos = position self._pixel_ratio = pixel_ratio self.texture = self.ctx.texture(self.size_scaled, components=4) self.fbo: Framebuffer = self.ctx.framebuffer(color_attachments=[self.texture]) self.fbo.clear() # Create 1 pixel rectangle we scale and move using pos and size self._quad = geometry.screen_rectangle(0, 0, 1, 1) self._program = self.ctx.program( vertex_shader=""" #version 330 uniform Projection { uniform mat4 matrix; } proj; uniform vec2 pos; uniform vec2 size; in vec2 in_vert; in vec2 in_uv; out vec2 uv; void main() { gl_Position = proj.matrix * vec4((in_vert * size) + pos, 0.0, 1.0); uv = in_uv; } """, fragment_shader=""" #version 330 uniform sampler2D ui_texture; in vec2 uv; out vec4 fragColor; void main() { fragColor = texture(ui_texture, uv); } """, ) @property def position(self) -> Tuple[int, int]: """Get or set the surface position""" return self._pos @position.setter def position(self, value): self._pos = value @property def size(self): """Size of the surface in window coordinates""" return self._size @property def size_scaled(self): """The physical size of the buffer""" return ( int(self._size[0] * self._pixel_ratio), int(self._size[1] * self._pixel_ratio) ) @property def pixel_ratio(self) -> float: return self._pixel_ratio @property def width(self) -> int: return self._size[0] @property def height(self) -> int: return self._size[1]
[docs] def clear(self, color: arcade.Color = (0, 0, 0, 0)): """Clear the surface""" self.fbo.clear(color=color)
def draw_texture(self, x: float, y: float, width: float, height: float, tex: Texture, angle=0, alpha: int = 255): arcade.draw_lrwh_rectangle_textured(bottom_left_x=x, bottom_left_y=y, width=width, height=height, texture=tex, angle=angle, alpha=alpha)
[docs] def draw_sprite(self, x, y, width, height, sprite): """Draw a sprite to the surface""" sprite.set_position(x + width // 2, y + height // 2) sprite.width = width sprite.height = height sprite.draw()
[docs] @contextmanager def activate(self): """ Save and restore projection and activate Surface buffer to draw on. Also resets the limit of the surface (viewport). """ proj = self.ctx.projection_2d self.limit(0, 0, *self.size) with self.fbo.activate(): yield self self.ctx.projection_2d = proj
[docs] def limit(self, x, y, width, height): """Reduces the draw area to the given rect""" self.fbo.viewport = ( int(x * self._pixel_ratio), int(y * self._pixel_ratio), int(width * self._pixel_ratio), int(height * self._pixel_ratio), ) width = max(width, 1) height = max(height, 1) self.ctx.projection_2d = 0, width, 0, height
[docs] def draw(self) -> None: """Draws the current buffer on screen""" self.texture.use(0) self._program["pos"] = self._pos self._program["size"] = self._size self._quad.render(self._program)
[docs] def resize(self, *, size: Tuple[int, int], pixel_ratio: float) -> None: """ Resize the internal texture by re-allocating a new one :param Tuple[int,int] size: The new size in pixels (xy) :param float pixel_ratio: The pixel scale of the window """ # Texture re-allocation is expensive so we should block unnecessary calls. if self._size == size and self._pixel_ratio == pixel_ratio: return self._size = size self._pixel_ratio = pixel_ratio # Create new texture and fbo self.texture = self.ctx.texture(self.size_scaled, components=4) self.fbo = self.ctx.framebuffer(color_attachments=[self.texture]) self.fbo.clear()