tobspr/RenderPipeline

View on GitHub
rpcore/gui/draggable_window.py

Summary

Maintainability
A
1 hr
Test Coverage
"""

RenderPipeline

Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>

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.

"""

from panda3d.core import Vec2, TransparencyAttrib, Vec3
from direct.gui.DirectFrame import DirectFrame
from direct.gui.DirectButton import DirectButton
from direct.gui.DirectGui import DGG

from rpcore.rpobject import RPObject
from rpcore.globals import Globals
from rpcore.gui.text import Text


class DraggableWindow(RPObject):

    """ This is a simple draggable but not resizeable window """

    def __init__(self, width=800, height=500, title="Window", parent=None):
        """ Constructs a new window with the given dimensions and title """
        RPObject.__init__(self, "Window-" + title)
        self._width = width
        self._height = height
        self._title = title
        self._visible = True
        self._parent = parent if parent else Globals.base.pixel2d
        self._dragging = False
        self._drag_offset = Vec2(0)
        self._pos = Vec2(0)

    def center_on_screen(self):
        """ Centers the window on screen """
        self._context_scale = 1.0 / self._parent.get_sx()
        self._context_width = Globals.native_resolution.x * self._context_scale
        self._context_height = Globals.native_resolution.y * self._context_scale
        self._set_pos(Vec2((self._context_width - self._width) / 2,
                           (self._context_height - self._height) / 2))

    def set_title(self, title):
        """ Sets the window title """
        self._title = title
        self._window_title.set_text(title)

    def show(self):
        """ Shows the window """
        self._visible = True
        self.center_on_screen()
        self._node.show()

    def hide(self):
        """ Hides the window """
        self._visible = False
        self._stop_drag()
        self._node.hide()

    def remove(self):
        """ Removes the window from the scene graph. You should still delete the
        instance """
        self._stop_drag()
        self._node.remove_node()

    def _create_components(self):
        """ Creates the window components """
        self._node = self._parent.attach_new_node("Window")
        self._node.set_pos(self._pos.x, 1, -self._pos.y)
        border_px = 1
        border_frame_size = (-border_px, self._width + border_px,
                             border_px, -self._height - border_px)
        self._border_frame = DirectFrame(
            pos=(0, 1, 0), frameSize=border_frame_size,
            frameColor=(24 / 255.0, 131 / 255.0, 215 / 255.0, 1),
            parent=self._node, state=DGG.NORMAL)
        self._background = DirectFrame(
            pos=(0, 1, 0), frameSize=(0, self._width, 0, -self._height),
            frameColor=(0.1, 0.1, 0.1, 1.0), parent=self._node)
        self._title_bar = DirectFrame(
            pos=(0, 1, 0), frameSize=(0, self._width, 0, -25),
            # frameColor=(0.058, 0.058, 0.058, 1),
            frameColor=(1, 1, 1, 1),
            parent=self._node,
            state=DGG.NORMAL)
        self._window_title = Text(
            parent=self._node, x=8, y=17, text=self._title, size=13,
            color=Vec3(0.15), may_change=True)
        self._btn_close = DirectButton(
            relief=DGG.FLAT, pressEffect=1, pos=(self._width - 22, 1, -12),
            frameColor=(1.0, 0.2, 0.2, 0.5), parent=self._node,
            scale=(45 / 2, 1, 24 / 2),
            image="/$$rp/data/gui/close_window.png")

        # Init bindings
        self._btn_close.set_transparency(TransparencyAttrib.M_alpha)
        self._btn_close.bind(DGG.B1CLICK, self._request_close)
        self._btn_close.bind(DGG.WITHIN, self._on_close_btn_hover)
        self._btn_close.bind(DGG.WITHOUT, self._on_close_btn_out)
        self._title_bar.bind(DGG.B1PRESS, self._start_drag)
        self._title_bar.bind(DGG.B1RELEASE, self._stop_drag)

    def _start_drag(self, evt=None):  # pylint: disable=unused-argument
        """ Gets called when the user starts dragging the window """
        self._dragging = True
        self._node.detach_node()
        self._node.reparent_to(self._parent)
        Globals.base.taskMgr.add(self._on_tick, "UIWindowDrag",
                                 uponDeath=self._stop_drag)
        self._drag_offset = self._pos - self._get_mouse_pos()

    def _on_close_btn_hover(self, evt=None):  # pylint: disable=unused-argument
        """ Internal method when the close button got hovered """
        self._btn_close["image"] = "/$$rp/data/gui/close_window_hover.png"

    def _on_close_btn_out(self, evt=None):  # pylint: disable=unused-argument
        """ Internal method when the close button is no longer hovered """
        self._btn_close["image"] = "/$$rp/data/gui/close_window.png"

    def _request_close(self, evt=None):  # pylint: disable=unused-argument
        """ This method gets called when the close button gets clicked """
        self.hide()

    def _stop_drag(self, evt=None):  # pylint: disable=unused-argument
        """ Gets called when the user stops dragging the window """
        Globals.base.taskMgr.remove("UIWindowDrag")
        self._dragging = False

    def _get_mouse_pos(self):
        """ Internal helper function to get the mouse position, scaled by
        the context scale """
        mouse_x, mouse_y = (Globals.base.win.get_pointer(0).x,
                            Globals.base.win.get_pointer(0).y)
        return Vec2(mouse_x, mouse_y) * self._context_scale

    def _set_pos(self, pos):
        """ Moves the window to the specified position """
        self._pos = pos
        self._pos.x = max(self._pos.x, -self._width + 100)
        self._pos.y = max(self._pos.y, 25)
        self._pos.x = min(self._pos.x, self._context_width - 100)
        self._pos.y = min(self._pos.y, self._context_height - 50)
        self._node.set_pos(self._pos.x, 1, -self._pos.y)

    def _on_tick(self, task):
        """ Task which updates the window while being dragged """
        self._set_pos(self._get_mouse_pos() + self._drag_offset)
        return task.cont