'''This module contains scene related functions and classes'''
from os import path
from abc import ABC, abstractmethod
import logging
from vulk import PATH_VULK_SHADER
from vulk import vulkanconstant as vc
from vulk import vulkanobject as vo
from vulk.exception import VulkError
from vulk.math.shape import Rectangle
from vulk.math.interpolation import Linear
from vulk.graphic.d2.batch import SpriteBatch, BlockBatch, BlockProperty
from vulk.graphic.d2.font import TextRenderer
logger = logging.getLogger()
# ----------
# WIDGETS
# ----------
[docs]class Scene(BaseWidget):
'''Main 2D Scene'''
def __init__(self, context, width, height, renderers=None):
"""Construct a new Scene
Args:
context (VulkContext)
width (int): Scene width
height (int): Scene height
"""
super().__init__(None)
self.shape.width = width
self.shape.height = height
# Default renderers
self.renderers = {
'default': SpriteBatch(context),
'block': BlockBatch(context),
'text': TextRenderer(context)
}
# Custom renderers
if renderers:
self.renderers.update(renderers)
def _get_block_sp(self, context):
"""Create the `block` shader program
Args:
context (VulkContext)
Returns:
ShaderProgram
"""
vs = path.join(PATH_VULK_SHADER, "block.vs.glsl")
fs = path.join(PATH_VULK_SHADER, "block.fs.glsl")
shaders_mapping = {
vc.ShaderStage.VERTEX: vs,
vc.ShaderStage.FRAGMENT: fs
}
return vo.ShaderProgramGlslFile(context, shaders_mapping)
[docs] def render(self, context):
"""Render Scene
For each widget, get renderer and render it.
Args:
context (VulkContext)
Returns:
Semaphore or None if no semaphore
"""
widgets = self.collect_children()
semaphores = None
for widget in widgets:
try:
renderer = self.renderers[widget.get_renderer_name()]
except KeyError:
msg = ("Cannot find renderer")
logger.critical(msg)
raise VulkError(msg)
renderer.begin(context, semaphores)
widget.render(renderer)
semaphores = [renderer.end()]
return semaphores[0] if semaphores else None
[docs] def update(self, delta):
widgets = self.collect_children()
for widget in widgets:
widget.update(delta)
[docs]class BatchedScene(Scene):
"""Faster Scene but with limitations
If your scene doesnét contain overlapping elements,
this scene will be faster.
"""
[docs] def render(self, context):
"""Render Scene
Collect renderers and then display all widget by renderer.
Renderers are in this order:
- Block renderer
- Sprite renderer
- Text renderer
Args:
context (VulkContext)
Returns:
Semaphore or None if no semaphore
"""
mapping = self.collect_widgets_to_render()
semaphore = None
for renderer, widgets in mapping.items():
semaphores = [semaphore] if semaphore else []
renderer.begin(context, semaphores)
for widget in widgets:
widget.render(renderer)
semaphore = renderer.end()
return semaphore if semaphore else None
[docs]class Image(Widget):
def __init__(self, parent, texture_region):
"""Construct a new Image widget
Args:
parent (Widget): Parent widget (may be a Scene)
texture_region (TextureRegion): Region of the texture to draw
"""
super().__init__(parent)
self.texture_region = texture_region
[docs] def get_renderer_name(self):
return 'default'
[docs] def render(self, renderer):
"""
Args:
renderer (SpriteBatch)
"""
c = self.color_abs
renderer.draw(self.texture_region.texture, self.shape.x,
self.shape.y, self.shape.width, self.shape.height,
r=c[0], g=c[1], b=c[2], a=c[3])
[docs]class Block(Widget):
"""Widget using the shader 'block' with allow lot of customization"""
def __init__(self, parent, border_colors=None, border_widths=None):
if not border_colors:
border_colors = [[0.]*4]*4
if not border_widths:
border_widths = [0.]*4
self.properties = BlockProperty()
self.properties.colors = [[1.]*4]*4
self.properties.border_colors = border_colors
self.properties.border_widths = border_widths
super().__init__(parent)
[docs] def get_renderer_name(self):
return 'block'
[docs] def render(self, renderer):
"""
Args:
renderer (BlockBatch)
"""
self.properties.colors[0][:] = self.color_abs[:]
self.properties.colors[1][:] = self.color_abs[:]
self.properties.colors[2][:] = self.color_abs[:]
self.properties.colors[3][:] = self.color_abs[:]
self.properties.width = self.shape.width
self.properties.height = self.shape.height
self.properties.x = self.shape.x
self.properties.y = self.shape.y
self.properties.rotation = self.rotation
renderer.draw(self.properties)
[docs]class Label(Widget):
"""Widget used to write text"""
def __init__(self, parent, fontdata, text):
"""Construct a new label widget
Args:
parent (Widget): Parent widget (may be a Scene)
fontdata (FontData): Font to render
text (str): Text to write
"""
super().__init__(parent)
self.text = text
self.fontdata = fontdata
[docs] def get_renderer_name(self):
return 'text'
[docs] def render(self, renderer):
"""
Args:
renderer (TextRenderer)
"""
c = self.color_abs
size = 30
renderer.draw(self.fontdata, self.text, self.shape.x, self.shape.y,
size, r=c[0], g=c[1], b=c[2], a=c[3],
rotation=self.rotation)
# ----------
# ACTIONS
# ----------
[docs]class Action():
def __init__(self):
self.widget = None
[docs] def init(self, widget):
"""Init action from widget"""
self.widget = widget
[docs]class TemporalAction(Action):
def __init__(self, duration, interpolation):
super().__init__()
if not interpolation:
interpolation = Linear()
self.duration = duration
self.interpolation = interpolation
self.time = 0
[docs] def init(self, widget):
super().init(widget)
self.time = 0
[docs] def update(self, delta):
self.time += delta
if self.percent() >= 1:
return False
return True
[docs] def percent(self):
return self.time / self.duration
[docs]class MoveTo(TemporalAction):
def __init__(self, x, y, duration, interpolation=None):
super().__init__(duration, interpolation)
self.x_src = 0
self.y_src = 0
self.x_dest = x
self.y_dest = y
self._dest_init = False
[docs] def init(self, widget):
super().init(widget)
self.x_src = widget.shape.x
self.y_src = widget.shape.y
if widget.parent and not self._dest_init:
self._dest_init = True
self.x_dest += widget.parent.shape.x
self.y_dest += widget.parent.shape.y
[docs] def update(self, delta):
super().update(delta)
percent = self.percent()
if percent >= 1:
return False
percent = self.interpolation.apply(percent)
x_current = self.x_src + (self.x_dest - self.x_src) * percent
y_current = self.y_src + (self.y_dest - self.y_src) * percent
self.widget.shape.x = x_current
self.widget.shape.y = y_current
return True
[docs]class MoveBy(TemporalAction):
def __init__(self, x, y, duration, interpolation=None):
super().__init__(duration, interpolation)
self.x_width = x
self.y_width = y
self.x_prev = 0
self.y_prev = 0
[docs] def init(self, widget):
super().init(widget)
self.x_prev = 0
self.y_prev = 0
def _compute_move(self, percent):
x_diff = self.x_width * percent
y_diff = self.y_width * percent
x_res = x_diff - self.x_prev
y_res = y_diff - self.y_prev
self.x_prev = x_diff
self.y_prev = y_diff
return x_res, y_res
[docs] def update(self, delta):
super().update(delta)
percent = self.percent()
if percent >= 1:
return False
x_rel, y_rel = self._compute_move(self.interpolation.apply(percent))
self.widget.shape.x += x_rel
self.widget.shape.y += y_rel
return True
[docs]class RotateTo(TemporalAction):
def __init__(self, rotation, duration, interpolation=None):
super().__init__(duration, interpolation)
self.rotation_src = 0
self.rotation_dest = rotation
self._dest_init = False
[docs] def init(self, widget):
super().init(widget)
self.rotation_src = widget.rotation
if widget.parent and not self._dest_init:
self._dest_init = True
self.rotation_dest += widget.parent.rotation
[docs] def update(self, delta):
super().update(delta)
percent = self.percent()
if percent >= 1:
return False
percent = self.interpolation.apply(percent)
rotation_current = (self.rotation_src +
(self.rotation_dest - self.rotation_src) *
percent)
self.widget.rotation = rotation_current
return True
[docs]class RotateBy(TemporalAction):
def __init__(self, rotation, duration, interpolation=None):
super().__init__(duration, interpolation)
self.rotation_base = rotation
self.rotation_prev = 0
[docs] def init(self, widget):
super().init(widget)
self.rotation_prev = 0
def _compute_move(self, percent):
rotation_diff = self.rotation_base * percent
rotation_res = rotation_diff - self.rotation_prev
self.rotation_prev = rotation_diff
return rotation_res
[docs] def update(self, delta):
super().update(delta)
percent = self.percent()
if percent >= 1:
return False
rotation_rel = self._compute_move(self.interpolation.apply(percent))
self.widget.rotation += rotation_rel
return True
[docs]class FadeIn(TemporalAction):
def __init__(self, duration, interpolation=None):
super().__init__(duration, interpolation)
self.fade_src = 0
[docs] def init(self, widget):
super().init(widget)
self.fade_src = widget.alpha
[docs] def update(self, delta):
super().update(delta)
percent = self.percent()
if percent >= 1:
return False
percent = self.interpolation.apply(percent)
self.widget.alpha = self.fade_src + (1 - self.fade_src) * percent
return True
[docs]class FadeOut(TemporalAction):
def __init__(self, duration, interpolation=None):
super().__init__(duration, interpolation)
self.fade_src = 0
[docs] def init(self, widget):
super().init(widget)
self.fade_src = widget.alpha
[docs] def update(self, delta):
super().update(delta)
percent = self.percent()
if percent >= 1:
return False
percent = self.interpolation.apply(percent)
self.widget.alpha = self.fade_src - self.fade_src * percent
return True
[docs]class FadeTo(TemporalAction):
def __init__(self, fade, duration, interpolation=None):
super().__init__(duration, interpolation)
self.fade_src = 0
self.fade_dst = fade
[docs] def init(self, widget):
super().init(widget)
self.fade_src = widget.alpha
[docs] def update(self, delta):
super().update(delta)
percent = self.percent()
if percent >= 1:
return False
percent = self.interpolation.apply(percent)
a = self.fade_src + (self.fade_dst - self.fade_src) * percent
self.widget.alpha = a
return True
[docs]class Composite(Action):
def __init__(self, actions):
"""
actions: list of Action
"""
super().__init__()
self.actions_src = actions
self.actions = None
[docs] def init(self, widget):
super().init(widget)
self.actions = self.actions_src.copy()
[docs]class Sequence(Composite):
def __init__(self, actions):
"""
actions: list of Action
"""
super().__init__(actions)
self.current_action = None
[docs] def next_action(self):
try:
self.current_action = self.actions.pop(0)
self.current_action.init(self.widget)
except IndexError:
return False
return True
[docs] def update(self, delta):
if not self.current_action and not self.next_action():
return False
if not self.current_action.update(delta) and not self.next_action():
return False
return True
[docs]class Parallel(Composite):
[docs] def init(self, widget):
super().init(widget)
for action in self.actions:
action.init(widget)
[docs] def update(self, delta):
self.actions = [a for a in self.actions if a.update(delta)]
if not self.actions:
return False
return True
[docs]class Repeat(Action):
def __init__(self, action, count=0):
"""
if count = 0: Action is looping forever
"""
super().__init__()
self.action = action
self.count = count
self.current_count = 1
[docs] def init(self, widget):
super().init(widget)
self.action.init(widget)
[docs] def restart_action(self):
self.action.init(self.widget)
[docs] def update(self, delta):
if self.action.update(delta):
return True
if self.current_count == self.count:
return False
self.current_count += 1
self.restart_action()
return True