CampBuddy/Camp.Buddy v2.2.1/Camp_Buddy-2.2.1-pc/renpy/display/viewport.py
2025-03-03 23:00:33 +01:00

633 lines
20 KiB
Python

# Copyright 2004-2019 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# This file contains classes that handle layout of displayables on
# the screen.
from __future__ import print_function
import renpy.display
import pygame_sdl2 as pygame
def edgescroll_proportional(n):
"""
An edgescroll function that causes the move speed to be proportional
from the edge distance.
"""
return n
class Viewport(renpy.display.layout.Container):
__version__ = 5
arrowkeys = False
pagekeys = False
def after_upgrade(self, version):
if version < 1:
self.xadjustment = renpy.display.behavior.Adjustment(1, 0)
self.yadjustment = renpy.display.behavior.Adjustment(1, 0)
self.set_adjustments = False
self.mousewheel = False
self.draggable = False
self.width = 0
self.height = 0
if version < 2:
self.drag_position = None
if version < 3:
self.edge_size = False
self.edge_speed = False
self.edge_function = None
self.edge_xspeed = 0
self.edge_yspeed = 0
self.edge_last_st = None
if version < 4:
self.xadjustment_param = None
self.yadjustment_param = None
self.offsets_param = (None, None)
self.set_adjustments_param = True
self.xinitial_param = None
self.yinitial_param = None
if version < 5:
self.focusable = self.draggable
def __init__(self,
child=None,
child_size=(None, None),
offsets=(None, None),
xadjustment=None,
yadjustment=None,
set_adjustments=True,
mousewheel=False,
draggable=False,
edgescroll=None,
style='viewport',
xinitial=None,
yinitial=None,
replaces=None,
arrowkeys=False,
pagekeys=False,
**properties):
super(Viewport, self).__init__(style=style, **properties)
if child is not None:
self.add(child)
self.xadjustment_param = xadjustment
self.yadjustment_param = yadjustment
self.offsets_param = offsets
self.set_adjustments_param = set_adjustments
self.xinitial_param = xinitial
self.yinitial_param = yinitial
self._show()
if isinstance(replaces, Viewport) and replaces.offsets:
self.xadjustment.range = replaces.xadjustment.range
self.xadjustment.value = replaces.xadjustment.value
self.yadjustment.range = replaces.yadjustment.range
self.yadjustment.value = replaces.yadjustment.value
self.xoffset = replaces.xoffset
self.yoffset = replaces.yoffset
self.drag_position = replaces.drag_position
else:
self.drag_position = None
self.child_width, self.child_height = child_size
self.mousewheel = mousewheel
self.draggable = draggable
self.arrowkeys = arrowkeys
self.pagekeys = pagekeys
# Layout participates in the focus system so drags get migrated.
self.focusable = draggable or arrowkeys
self.width = 0
self.height = 0
# The speed at which we scroll in the x and y directions, in pixels
# per second.
self.edge_xspeed = 0
self.edge_yspeed = 0
# The last time we edgescrolled.
self.edge_last_st = None
if edgescroll is not None:
# The size of the edges that trigger scrolling.
self.edge_size = edgescroll[0]
# How far from the edge we can scroll.
self.edge_speed = edgescroll[1]
if len(edgescroll) >= 3:
self.edge_function = edgescroll[2]
else:
self.edge_function = edgescroll_proportional
else:
self.edge_size = 0
self.edge_speed = 0
self.edge_function = edgescroll_proportional
def _show(self):
if self.xadjustment_param is None:
self.xadjustment = renpy.display.behavior.Adjustment(1, 0)
else:
self.xadjustment = self.xadjustment_param
if self.yadjustment_param is None:
self.yadjustment = renpy.display.behavior.Adjustment(1, 0)
else:
self.yadjustment = self.yadjustment_param
if self.xadjustment.adjustable is None:
self.xadjustment.adjustable = True
if self.yadjustment.adjustable is None:
self.yadjustment.adjustable = True
self.set_adjustments = self.set_adjustments_param
offsets = self.offsets_param
self.xoffset = offsets[0] if (offsets[0] is not None) else self.xinitial_param
self.yoffset = offsets[1] if (offsets[1] is not None) else self.yinitial_param
def per_interact(self):
self.xadjustment.register(self)
self.yadjustment.register(self)
def update_offsets(self, cw, ch, st):
"""
This is called by render once we know the width (`cw`) and height (`ch`)
of all the children. It returns a pair of offsets that should be applied
to all children.
It also requires `st`, since hit handles edge scrolling.
The returned offsets will be negative or zero.
"""
width = self.width
height = self.height
if not self.style.xfill:
width = min(cw, width)
if not self.style.yfill:
height = min(ch, height)
width = max(width, self.style.xminimum)
height = max(height, self.style.yminimum)
if (not renpy.display.render.sizing) and self.set_adjustments:
xarange = max(cw - width, 0)
if (self.xadjustment.range != xarange) or (self.xadjustment.page != width):
self.xadjustment.range = xarange
self.xadjustment.page = width
self.xadjustment.update()
yarange = max(ch - height, 0)
if (self.yadjustment.range != yarange) or (self.yadjustment.page != height):
self.yadjustment.range = yarange
self.yadjustment.page = height
self.yadjustment.update()
if self.xoffset is not None:
if isinstance(self.xoffset, int):
value = self.xoffset
else:
value = max(cw - width, 0) * self.xoffset
self.xadjustment.value = value
if self.yoffset is not None:
if isinstance(self.yoffset, int):
value = self.yoffset
else:
value = max(ch - height, 0) * self.yoffset
self.yadjustment.value = value
if self.edge_size and (self.edge_last_st is not None) and (self.edge_xspeed or self.edge_yspeed):
duration = max(st - self.edge_last_st, 0)
self.xadjustment.change(self.xadjustment.value + duration * self.edge_xspeed)
self.yadjustment.change(self.yadjustment.value + duration * self.edge_yspeed)
self.check_edge_redraw(st)
cxo = -int(self.xadjustment.value)
cyo = -int(self.yadjustment.value)
self._clipping = (cw > width) or (ch > height)
return cxo, cyo, width, height
def render(self, width, height, st, at):
self.width = width
self.height = height
child_width = self.child_width or width
child_height = self.child_height or height
surf = renpy.display.render.render(self.child, child_width, child_height, st, at)
cw, ch = surf.get_size()
cxo, cyo, width, height = self.update_offsets(cw, ch, st)
self.offsets = [ (cxo, cyo) ]
rv = renpy.display.render.Render(width, height)
rv.blit(surf, (cxo, cyo))
rv = rv.subsurface((0, 0, width, height), focus=True)
if self.draggable or self.arrowkeys:
rv.add_focus(self, None, 0, 0, width, height)
return rv
def check_edge_redraw(self, st, reset_st=True):
redraw = False
if (self.edge_xspeed > 0) and (self.xadjustment.value < self.xadjustment.range):
redraw = True
if (self.edge_xspeed < 0) and (self.xadjustment.value > 0):
redraw = True
if (self.edge_yspeed > 0) and (self.yadjustment.value < self.yadjustment.range):
redraw = True
if (self.edge_yspeed < 0) and (self.yadjustment.value > 0):
redraw = True
if redraw:
renpy.display.render.redraw(self, 0)
if reset_st or self.edge_last_st is None:
self.edge_last_st = st
else:
self.edge_last_st = None
def event(self, ev, x, y, st):
self.xoffset = None
self.yoffset = None
rv = super(Viewport, self).event(ev, x, y, st)
if rv is not None:
return rv
if self.draggable and renpy.display.focus.get_grab() == self:
old_xvalue = self.xadjustment.value
old_yvalue = self.yadjustment.value
if renpy.display.behavior.map_event(ev, 'viewport_drag_end'):
renpy.display.focus.set_grab(None)
# Invoke rounding adjustment on viewport release
xvalue = self.xadjustment.round_value(old_xvalue, release=True)
self.xadjustment.change(xvalue)
yvalue = self.yadjustment.round_value(old_yvalue, release=True)
self.yadjustment.change(yvalue)
raise renpy.display.core.IgnoreEvent()
oldx, oldy = self.drag_position
dx = x - oldx
dy = y - oldy
new_xvalue = self.xadjustment.round_value(old_xvalue - dx, release=False)
if old_xvalue == new_xvalue:
newx = oldx
else:
self.xadjustment.change(new_xvalue)
newx = x
new_yvalue = self.yadjustment.round_value(old_yvalue - dy, release=False)
if old_yvalue == new_yvalue:
newy = oldy
else:
self.yadjustment.change(new_yvalue)
newy = y
self.drag_position = (newx, newy) # W0201
if not ((0 <= x < self.width) and (0 <= y <= self.height)):
self.edge_xspeed = 0
self.edge_yspeed = 0
self.edge_last_st = None
inside = False
else:
inside = True
if inside and self.mousewheel:
if self.mousewheel == "horizontal-change":
adjustment = self.xadjustment
change = True
elif self.mousewheel == "change":
adjustment = self.yadjustment
change = True
elif self.mousewheel == "horizontal":
adjustment = self.xadjustment
change = False
else:
adjustment = self.yadjustment
change = False
if renpy.display.behavior.map_event(ev, 'viewport_up'):
if change and (adjustment.value == 0):
return None
rv = adjustment.change(adjustment.value - adjustment.step)
if rv is not None:
return rv
else:
raise renpy.display.core.IgnoreEvent()
if renpy.display.behavior.map_event(ev, 'viewport_down'):
if change and (adjustment.value == adjustment.range):
return None
rv = adjustment.change(adjustment.value + adjustment.step)
if rv is not None:
return rv
else:
raise renpy.display.core.IgnoreEvent()
if self.arrowkeys:
if renpy.display.behavior.map_event(ev, 'viewport_leftarrow'):
if self.xadjustment.value == 0:
return None
rv = self.xadjustment.change(self.xadjustment.value - self.xadjustment.step)
if rv is not None:
return rv
else:
raise renpy.display.core.IgnoreEvent()
if renpy.display.behavior.map_event(ev, 'viewport_rightarrow'):
if self.xadjustment.value == self.xadjustment.range:
return None
rv = self.xadjustment.change(self.xadjustment.value + self.xadjustment.step)
if rv is not None:
return rv
else:
raise renpy.display.core.IgnoreEvent()
if renpy.display.behavior.map_event(ev, 'viewport_uparrow'):
if self.yadjustment.value == 0:
return None
rv = self.yadjustment.change(self.yadjustment.value - self.yadjustment.step)
if rv is not None:
return rv
else:
raise renpy.display.core.IgnoreEvent()
if renpy.display.behavior.map_event(ev, 'viewport_downarrow'):
if self.yadjustment.value == self.yadjustment.range:
return None
rv = self.yadjustment.change(self.yadjustment.value + self.yadjustment.step)
if rv is not None:
return rv
else:
raise renpy.display.core.IgnoreEvent()
if self.pagekeys:
if renpy.display.behavior.map_event(ev, 'viewport_pageup'):
rv = self.yadjustment.change(self.yadjustment.value - self.yadjustment.page)
if rv is not None:
return rv
else:
raise renpy.display.core.IgnoreEvent()
if renpy.display.behavior.map_event(ev, 'viewport_pagedown'):
rv = self.yadjustment.change(self.yadjustment.value + self.yadjustment.page)
if rv is not None:
return rv
else:
raise renpy.display.core.IgnoreEvent()
if inside and self.draggable:
if renpy.display.behavior.map_event(ev, 'viewport_drag_start'):
focused = renpy.display.focus.get_focused()
if (focused is None) or (focused is self):
self.drag_position = (x, y)
renpy.display.focus.set_grab(self)
raise renpy.display.core.IgnoreEvent()
if inside and self.edge_size and ev.type in [ pygame.MOUSEMOTION, pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP ]:
def speed(n, zero, one):
"""
Given a position `n`, computes the speed. The speed is 0.0
when `n` == `zero`, 1.0 when `n` == `one`, and linearly
interpolated when between.
Returns 0.0 when outside the bounds - in either direction.
"""
n = 1.0 * (n - zero) / (one - zero)
if n < 0.0:
return 0.0
if n > 1.0:
return 0.0
return n
xspeed = speed(x, self.width - self.edge_size, self.width)
xspeed -= speed(x, self.edge_size, 0)
self.edge_xspeed = self.edge_speed * self.edge_function(xspeed)
yspeed = speed(y, self.height - self.edge_size, self.height)
yspeed -= speed(y, self.edge_size, 0)
self.edge_yspeed = self.edge_speed * self.edge_function(yspeed)
if xspeed or yspeed:
self.check_edge_redraw(st, reset_st=False)
else:
self.edge_last_st = None
return None
def set_xoffset(self, offset):
self.xoffset = offset
renpy.display.render.redraw(self, 0)
def set_yoffset(self, offset):
self.yoffset = offset
renpy.display.render.redraw(self, 0)
# For compatibility with old saves.
renpy.display.layout.Viewport = Viewport
class VPGrid(Viewport):
__version__ = Viewport.__version__
def __init__(self, cols=None, rows=None, transpose=None, style="vpgrid", **properties):
super(VPGrid, self).__init__(style=style, **properties)
if (rows is None) and (cols is None):
raise Exception("A VPGrid must be given the rows or cols property.")
if (rows is not None) and (cols is None) and (transpose is None):
transpose = True
self.grid_cols = cols
self.grid_rows = rows
self.grid_transpose = transpose
def render(self, width, height, st, at):
self.width = width
self.height = height
child_width = self.child_width or width
child_height = self.child_height or height
if not self.children:
self.offsets = [ ]
return renpy.display.render.Render(0, 0)
# The number of children.
lc = len(self.children)
# Figure out the number of columns and rows.
cols = self.grid_cols
rows = self.grid_rows
if cols is None:
cols = lc // rows
if rows * cols < lc:
cols += 1
if rows is None:
rows = lc // cols
if rows * cols < lc:
rows += 1
# Determine the total size.
xspacing = self.style.xspacing
yspacing = self.style.yspacing
if xspacing is None:
xspacing = self.style.spacing
if yspacing is None:
yspacing = self.style.spacing
rend = renpy.display.render.render(self.children[0], child_width, child_height, st, at)
cw, ch = rend.get_size()
tw = (cw + xspacing) * cols - xspacing
th = (ch + yspacing) * rows - yspacing
if self.style.xfill:
tw = child_width
cw = (tw - (cols - 1) * xspacing) / cols
if self.style.yfill:
th = child_height
ch = (th - (rows - 1) * yspacing) / rows
cxo, cyo, width, height = self.update_offsets(tw, th, st)
self.offsets = [ ]
# Render everything.
rv = renpy.display.render.Render(width, height)
for index, c in enumerate(self.children):
if self.grid_transpose:
x = index // rows
y = index % rows
else:
x = index % cols
y = index // cols
x = x * (cw + xspacing) + cxo
y = y * (ch + yspacing) + cyo
if x + cw < 0:
self.offsets.append((x, y))
continue
if y + ch < 0:
self.offsets.append((x, y))
continue
if x >= width:
self.offsets.append((x, y))
continue
if y >= height:
self.offsets.append((x, y))
continue
surf = renpy.display.render.render(c, cw, ch, st, at)
pos = c.place(rv, x, y, cw, ch, surf)
self.offsets.append(pos)
rv = rv.subsurface((0, 0, width, height), focus=True)
if self.draggable or self.arrowkeys:
rv.add_focus(self, None, 0, 0, width, height)
return rv