Difficult-Rocket/libs/pyglet/gui/widgets.py

325 lines
11 KiB
Python
Raw Normal View History

2021-04-16 23:21:06 +08:00
# ----------------------------------------------------------------------------
# pyglet
# Copyright (c) 2006-2008 Alex Holkner
2021-04-17 01:14:38 +08:00
# Copyright (c) 2008-2021 pyglet contributors
2021-04-16 23:21:06 +08:00
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# * Neither the name of pyglet nor the names of its
# contributors may be used to endorse or promote products
# derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
import pyglet
from pyglet.event import EventDispatcher
from pyglet.graphics import OrderedGroup
from pyglet.text.caret import Caret
from pyglet.text.layout import IncrementalTextLayout
class WidgetBase(EventDispatcher):
def __init__(self, x, y, width, height):
self._x = x
self._y = y
self._width = width
self._height = height
self._bg_group = None
self._fg_group = None
def update_groups(self, order):
pass
@property
def x(self):
return self._x
@property
def y(self):
return self._y
@property
def width(self):
return self._width
@property
def height(self):
return self._height
@property
def aabb(self):
return self._x, self._y, self._x + self._width, self._y + self._height
def _check_hit(self, x, y):
return self._x < x < self._x + self._width and self._y < y < self._y + self._height
def on_mouse_press(self, x, y, buttons, modifiers):
pass
def on_mouse_release(self, x, y, buttons, modifiers):
pass
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
pass
def on_mouse_scroll(self, x, y, mouse, direction):
pass
def on_text(self, text):
pass
def on_text_motion(self, motion):
pass
def on_text_motion_select(self, motion):
pass
WidgetBase.register_event_type('on_mouse_press')
WidgetBase.register_event_type('on_mouse_release')
WidgetBase.register_event_type('on_mouse_motion')
WidgetBase.register_event_type('on_mouse_scroll')
WidgetBase.register_event_type('on_mouse_drag')
WidgetBase.register_event_type('on_text')
WidgetBase.register_event_type('on_text_motion')
WidgetBase.register_event_type('on_text_motion_select')
class PushButton(WidgetBase):
def __init__(self, x, y, pressed, depressed, hover=None, batch=None, group=None):
super().__init__(x, y, depressed.width, depressed.height)
self._pressed_img = pressed
self._depressed_img = depressed
self._hover_img = hover or depressed
# TODO: add `draw` method or make Batch required.
self._batch = batch or pyglet.graphics.Batch()
self._user_group = group
bg_group = OrderedGroup(0, parent=group)
self._sprite = pyglet.sprite.Sprite(self._depressed_img, x, y, batch=batch, group=bg_group)
self._pressed = False
def update_groups(self, order):
self._sprite.group = OrderedGroup(order + 1, self._user_group)
def on_mouse_press(self, x, y, buttons, modifiers):
if not self._check_hit(x, y):
return
self._sprite.image = self._pressed_img
self._pressed = True
self.dispatch_event('on_press')
def on_mouse_release(self, x, y, buttons, modifiers):
if not self._pressed:
return
self._sprite.image = self._hover_img if self._check_hit(x, y) else self._depressed_img
self._pressed = False
self.dispatch_event('on_release')
def on_mouse_motion(self, x, y, dx, dy):
if self._pressed:
return
self._sprite.image = self._hover_img if self._check_hit(x, y) else self._depressed_img
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
if self._pressed:
return
self._sprite.image = self._hover_img if self._check_hit(x, y) else self._depressed_img
PushButton.register_event_type('on_press')
PushButton.register_event_type('on_release')
class ToggleButton(PushButton):
def _get_release_image(self, x, y):
return self._hover_img if self._check_hit(x, y) else self._depressed_img
def on_mouse_press(self, x, y, buttons, modifiers):
if not self._check_hit(x, y):
return
self._pressed = not self._pressed
self._sprite.image = self._pressed_img if self._pressed else self._get_release_image(x, y)
self.dispatch_event('on_toggle', self._pressed)
def on_mouse_release(self, x, y, buttons, modifiers):
if self._pressed:
return
self._sprite.image = self._get_release_image(x, y)
ToggleButton.register_event_type('on_toggle')
class Slider(WidgetBase):
def __init__(self, x, y, base, knob, edge=0, batch=None, group=None):
super().__init__(x, y, base.width, knob.height)
self._edge = edge
self._base_img = base
self._knob_img = knob
self._half_knob_width = knob.width / 2
self._half_knob_height = knob.height / 2
self._knob_img.anchor_y = knob.height / 2
self._min_knob_x = x + edge
self._max_knob_x = x + base.width - knob.width - edge
self._user_group = group
bg_group = OrderedGroup(0, parent=group)
fg_group = OrderedGroup(1, parent=group)
self._base_spr = pyglet.sprite.Sprite(self._base_img, x, y, batch=batch, group=bg_group)
self._knob_spr = pyglet.sprite.Sprite(self._knob_img, x+edge, y+base.height/2, batch=batch, group=fg_group)
self._value = 0
self._in_update = False
def update_groups(self, order):
self._base_spr.group = OrderedGroup(order + 1, self._user_group)
self._knob_spr.group = OrderedGroup(order + 2, self._user_group)
@property
def _min_x(self):
return self._x + self._edge
@property
def _max_x(self):
return self._x + self._width - self._edge
@property
def _min_y(self):
return self._y - self._half_knob_height
@property
def _max_y(self):
return self._y + self._half_knob_height + self._base_img.height / 2
def _check_hit(self, x, y):
return self._min_x < x < self._max_x and self._min_y < y < self._max_y
def _update_knob(self, x):
self._knob_spr.x = max(self._min_knob_x, min(x - self._half_knob_width, self._max_knob_x))
self._value = abs(((self._knob_spr.x - self._min_knob_x) * 100) / (self._min_knob_x - self._max_knob_x))
self.dispatch_event('on_change', self._value)
def on_mouse_press(self, x, y, buttons, modifiers):
if self._check_hit(x, y):
self._in_update = True
self._update_knob(x)
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
if self._in_update:
self._update_knob(x)
def on_mouse_scroll(self, x, y, mouse, direction):
if self._check_hit(x, y):
self._update_knob(self._knob_spr.x + self._half_knob_width + direction)
def on_mouse_release(self, x, y, buttons, modifiers):
self._in_update = False
Slider.register_event_type('on_change')
class TextEntry(WidgetBase):
def __init__(self, text, x, y, width, color=(255, 255, 255, 255), batch=None, group=None):
self._doc = pyglet.text.document.UnformattedDocument(text)
self._doc.set_style(0, len(self._doc.text), dict(color=(0, 0, 0, 255)))
font = self._doc.get_font()
height = font.ascent - font.descent
self._user_group = group
bg_group = OrderedGroup(0, parent=group)
fg_group = OrderedGroup(1, parent=group)
# Rectangular outline with 2-pixel pad:
p = 2
self._outline = pyglet.shapes.Rectangle(x-p, y-p, width+p+p, height+p+p, color[:3], batch, bg_group)
self._outline.opacity = color[3]
# Text and Caret:
self._layout = IncrementalTextLayout(self._doc, width, height, multiline=False, batch=batch, group=fg_group)
self._layout.x = x
self._layout.y = y
self._caret = Caret(self._layout)
self._caret.visible = False
self._focus = False
super().__init__(x, y, width, height)
def _check_hit(self, x, y):
return self._x < x < self._x + self._width and self._y < y < self._y + self._height
def _set_focus(self, value):
self._focus = value
self._caret.visible = value
def update_groups(self, order):
self._outline.group = OrderedGroup(order + 1, self._user_group)
self._layout.group = OrderedGroup(order + 2, self._user_group)
def on_mouse_motion(self, x, y, dx, dy):
if not self._check_hit(x, y):
self._set_focus(False)
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
if self._focus:
self._caret.on_mouse_drag(x, y, dx, dy, buttons, modifiers)
def on_mouse_press(self, x, y, buttons, modifiers):
if self._check_hit(x, y):
self._set_focus(True)
self._caret.on_mouse_press(x, y, buttons, modifiers)
def on_text(self, text):
if self._focus:
if text in ('\r', '\n'):
self.dispatch_event('on_commit', self._layout.document.text)
self._set_focus(False)
return
self._caret.on_text(text)
def on_text_motion(self, motion):
if self._focus:
self._caret.on_text_motion(motion)
def on_text_motion_select(self, motion):
if self._focus:
self._caret.on_text_motion_select(motion)
def on_commit(self, text):
"""Text has been commited via Enter/Return key."""
TextEntry.register_event_type('on_commit')