Ejemplo n.º 1
0
class Scrollable(Wrapper):
    """
    Wraps a layout or widget and limits it to a maximum, or fixed, size.
    If the layout exceeds the viewable limits then it is truncated and
    scrollbars will be displayed so the user can pan around.
    """
    def __init__(self,
                 content=None,
                 width=None,
                 height=None,
                 is_fixed_size=False,
                 always_show_scrollbars=False):
        """
        Creates a new Scrollable.

        @param content The layout or Widget to be scrolled
        @param width Maximum width, or None
        @param height Maximum height, or None
        @param is_fixed_size True if we should always be at maximum size;
                             otherwise we shrink to match our content
        @param always_show_scrollbars True if we should always show scrollbars
        """
        if is_fixed_size:
            assert width is not None and height is not None
        Wrapper.__init__(self, content)
        self.max_width = width
        self.max_height = height
        self.is_fixed_size = is_fixed_size
        self.always_show_scrollbars = always_show_scrollbars
        self.hscrollbar = None
        self.vscrollbar = None
        self.content_width = 0
        self.content_height = 0
        self.content_x = 0
        self.content_y = 0
        self.hscrollbar_height = 0
        self.vscrollbar_width = 0

        # We emulate some aspects of Dialog here.  We cannot just inherit
        # from Dialog because pyglet event handling won't allow keyword
        # arguments to be passed through.
        self.theme = None
        self.batch = None
        self.root_group = None
        self.panel_group = None
        self.bg_group = None
        self.fg_group = None
        self.highlight_group = None
        self.needs_layout = False

    def _get_controls(self):
        """
        We represent ourself as a Control to the Dialog, but we pass through
        the events we receive from Dialog.
        """
        base_controls = Wrapper._get_controls(self)
        controls = []
        our_left = self.content_x
        our_right = our_left + self.content_width
        our_bottom = self.content_y
        our_top = our_bottom + self.content_height
        for control, left, right, top, bottom in base_controls:
            controls.append((control, max(left,
                                          our_left), min(right, our_right),
                             min(top, our_top), max(bottom, our_bottom)))
        if self.hscrollbar is not None:
            controls += self.hscrollbar._get_controls()
        if self.vscrollbar is not None:
            controls += self.vscrollbar._get_controls()
        return controls

    def delete(self):
        """
        Delete all graphical elements associated with the Scrollable
        """
        Wrapper.delete(self)
        if self.hscrollbar is not None:
            self.hscrollbar.delete()
            self.hscrollbar = None
        if self.vscrollbar is not None:
            self.vscrollbar.delete()
            self.vscrollbar = None
        self.root_group = None
        self.panel_group = None
        self.bg_group = None
        self.fg_group = None
        self.highlight_group = None

    def ensure_visible(self, control):
        """
        Make sure a control is visible.
        """
        offset_x = 0
        if self.hscrollbar:
            offset_x = self.hscrollbar.get(self.content_width,
                                           self.content.width)
        offset_y = 0
        if self.vscrollbar:
            offset_y = self.content.height - self.content_height - \
                     self.vscrollbar.get(self.content_height,
                                         self.content.height)
        control_left = control.x - self.content_x - offset_x
        control_right = control_left + control.width
        control_bottom = control.y - self.content_y + offset_y
        control_top = control_bottom + control.height
        if self.hscrollbar is not None:
            self.hscrollbar.ensure_visible(
                control_left, control_right,
                max(self.content_width, self.content.width))
        if self.vscrollbar is not None:
            self.vscrollbar.ensure_visible(
                control_top, control_bottom,
                max(self.content_height, self.content.height))

    def expand(self, width, height):
        if self.content.is_expandable():
            if self.vscrollbar is not None:
                self.content_width = width - self.vscrollbar_width
            else:
                self.content_width = width
            if self.hscrollbar is not None:
                self.content_height = height - self.hscrollbar_height
            else:
                self.content_height = height
            self.content.expand(max(self.content_width, self.content.width),
                                max(self.content_height, self.content.height))
        self.width, self.height = width, height

    def get_root(self):
        if self.saved_dialog:
            return self.saved_dialog.get_root()
        else:
            return self

    def hit_test(self, x, y):
        """
        We only intercept events for the content region, not for
        our scrollbars.  They can handle themselves!
        """
        return x >= self.content_x and y >= self.content_y and \
               x < self.content_x + self.content_width and \
               y < self.content_y + self.content_height

    def is_expandable(self):
        return True

    def layout(self, x, y):
        """
        Reposition the Scrollable

        @param x X coordinate of lower left corner
        @param y Y coordinate of lower left corner
        """
        self.x, self.y = x, y

        # Work out the adjusted content width and height
        if self.hscrollbar is not None:
            self.hscrollbar.layout(x, y)
            y += self.hscrollbar.height
        if self.vscrollbar is not None:
            self.vscrollbar.layout(x + self.content_width, y)

        # Set the scissor group
        self.root_group.x, self.root_group.y = x - 1, y - 1
        self.root_group.width = self.content_width + 1
        self.root_group.height = self.content_height + 1

        # Work out the content layout
        self.content_x, self.content_y = x, y
        left = x
        top = y + self.content_height - self.content.height
        if self.hscrollbar:
            left -= self.hscrollbar.get(self.content_width, self.content.width)
        if self.vscrollbar:
            top += self.vscrollbar.get(self.content_height,
                                       self.content.height)
        self.content.layout(left, top)

        self.needs_layout = False

    def on_update(self, dt):
        """
        On updates, we redo the layout if scrollbars have changed position

        @param dt Time passed since last update event (in seconds)
        """
        if self.needs_layout:
            width, height = self.width, self.height
            self.size(self.saved_dialog)
            self.expand(width, height)
            self.layout(self.x, self.y)

    def set_needs_layout(self):
        self.needs_layout = True
        if self.saved_dialog is not None:
            self.saved_dialog.set_needs_layout()

    def set_wheel_hint(self, control):
        if self.saved_dialog is not None:
            self.saved_dialog.set_wheel_hint(control)

    def set_wheel_target(self, control):
        if self.saved_dialog is not None:
            self.saved_dialog.set_wheel_target(control)

    def size(self, dialog):
        """
        Recalculate the size of the Scrollable.

        @param dialog Dialog which contains us
        """
        if dialog is None:
            return
        Widget.size(self, dialog)
        if self.is_fixed_size:
            self.width, self.height = self.max_width, self.max_height

        self.hscrollbar_height = \
            dialog.theme['hscrollbar']['left']['image'].height
        self.vscrollbar_width = \
            dialog.theme['vscrollbar']['up']['image'].width

        if self.root_group is None:  # do we need to re-clone dialog groups?
            self.theme = dialog.theme
            self.batch = dialog.batch
            self.root_group = ScrollableGroup(0,
                                              0,
                                              self.width,
                                              self.height,
                                              parent=dialog.fg_group)
            self.panel_group = pyglet.graphics.OrderedGroup(0, self.root_group)
            self.bg_group = pyglet.graphics.OrderedGroup(1, self.root_group)
            self.fg_group = pyglet.graphics.OrderedGroup(2, self.root_group)
            self.highlight_group = pyglet.graphics.OrderedGroup(
                3, self.root_group)
            Wrapper.delete(self)  # force children to abandon old groups

        Wrapper.size(self, self)  # all children are to use our groups

        if self.always_show_scrollbars or \
           (self.max_width and self.width > self.max_width):
            if self.hscrollbar is None:
                self.hscrollbar = HScrollbar(self.max_width)
        else:
            if self.hscrollbar is not None:
                self.hscrollbar.delete()
                self.hscrollbar = None

        if self.always_show_scrollbars or \
           (self.max_height and self.height > self.max_height):
            if self.vscrollbar is None:
                self.vscrollbar = VScrollbar(self.max_height)
        else:
            if self.vscrollbar is not None:
                self.vscrollbar.delete()
                self.vscrollbar = None

        self.width = min(self.max_width or self.width, self.width)
        self.content_width = self.width
        self.height = min(self.max_height or self.height, self.height)
        self.content_height = self.height

        if self.hscrollbar is not None:
            self.hscrollbar.size(dialog)
            self.hscrollbar.set(self.max_width,
                                max(self.content.width, self.max_width))
            self.height += self.hscrollbar.height

        if self.vscrollbar is not None:
            self.vscrollbar.size(dialog)
            self.vscrollbar.set(self.max_height,
                                max(self.content.height, self.max_height))
            self.width += self.vscrollbar.width
Ejemplo n.º 2
0
class Document(Control):
    """
    Allows you to embed a document within the GUI, which includes a
    vertical scrollbar as needed.
    """
    def __init__(self, document, width=1000, height=5000,
                 is_fixed_size=False, always_show_scrollbar=False):
        """
        Creates a new Document.
        """
        Control.__init__(self, width, height)
        self.max_height = height
        self.content_width = width
        if isinstance(document, basestring):
            self.document = pyglet.text.document.UnformattedDocument(document)
        else:
            self.document = document
        self.content = None
        self.content_width = width
        self.scrollbar = None
        self.set_document_style = False
        self.is_fixed_size = is_fixed_size
        self.always_show_scrollbar = always_show_scrollbar
        self.needs_layout = False

    def _do_set_document_style(self, attr, value):
        length = len(self.document.text)
        runs = [(start, end, doc_value) for start, end, doc_value in
                self.document.get_style_runs(attr).ranges(0, length)
                if doc_value is not None]
        if not runs:
            terminator = len(self.document.text)
        else:
            terminator = runs[0][0]
        self.document.set_style(0, terminator, {attr: value})

    def _get_controls(self):
        controls = []
        if self.scrollbar:
            controls += self.scrollbar._get_controls()
        controls += Control._get_controls(self)
        return controls

    def delete(self):
        if self.content is not None:
            self.content.delete()
            self.content = None
        if self.scrollbar is not None:
            self.scrollbar.delete()
            self.scrollbar = None

    def do_set_document_style(self, dialog):
        self.set_document_style = True

        # Check the style runs to make sure we don't stamp on anything
        # set by the user
        self._do_set_document_style('color', dialog.theme['text_color'])
        self._do_set_document_style('font_name', dialog.theme['font'])
        self._do_set_document_style('font_size', dialog.theme['font_size'])

    def get_text(self):
        return self.document.text

    def layout(self, x, y):
        self.x, self.y = x, y
        self.content.begin_update()
        self.content.x = x
        self.content.y = y
        self.content.end_update()
        if self.scrollbar is not None:
            self.scrollbar.layout(x + self.content_width, y)

    def on_update(self, dt):
        """
        On updates, we update the scrollbar and then set our view offset
        if it has changed.

        @param dt Time passed since last update event (in seconds)
        """
        if self.scrollbar is not None:
            self.scrollbar.dispatch_event('on_update', dt)
            pos = self.scrollbar.get(self.max_height,
                                     self.content.content_height)
            if pos != -self.content.view_y:
                self.content.view_y = -pos

        if self.needs_layout:
            self.needs_layout = False
            self.saved_dialog.set_needs_layout()

    def size(self, dialog):
        if dialog is None:
            return

        Control.size(self, dialog)
        if not self.set_document_style:
            self.do_set_document_style(dialog)
        if self.content is None:
            self.content = pyglet.text.layout.IncrementalTextLayout(
                self.document,
                self.content_width,
                self.max_height,
                multiline=True, batch=dialog.batch, group=dialog.fg_group)
            if self.is_fixed_size or (self.max_height and
                self.content.content_height > self.max_height):
                self.height = self.max_height
            else:
                self.height = self.content.content_height
            self.content.height = self.height
        if self.always_show_scrollbar or \
           (self.max_height and self.content.content_height > self.max_height):
            if self.scrollbar is None:
                self.scrollbar = VScrollbar(self.max_height)
            self.scrollbar.size(dialog)
            self.scrollbar.set(self.max_height, self.content.content_height)
        if self.scrollbar is not None:
            self.width = self.content_width + self.scrollbar.width
        else:
            self.width = self.content_width

    def set_text(self, text):
        self.document.text = text
        self.needs_layout = True
Ejemplo n.º 3
0
class Scrollable(Wrapper):
    """
    Wraps a layout or widget and limits it to a maximum, or fixed, size.
    If the layout exceeds the viewable limits then it is truncated and
    scrollbars will be displayed so the user can pan around.
    """

    def __init__(self, content=None, width=None, height=None, is_fixed_size=False, always_show_scrollbars=False):
        """
        Creates a new Scrollable.

        @param content The layout or Widget to be scrolled
        @param width Maximum width, or None
        @param height Maximum height, or None
        @param is_fixed_size True if we should always be at maximum size;
                             otherwise we shrink to match our content
        @param always_show_scrollbars True if we should always show scrollbars
        """
        if is_fixed_size:
            assert width is not None and height is not None
        Wrapper.__init__(self, content)
        self.max_width = width
        self.max_height = height
        self.is_fixed_size = is_fixed_size
        self.always_show_scrollbars = always_show_scrollbars
        self.hscrollbar = None
        self.vscrollbar = None
        self.content_width = 0
        self.content_height = 0
        self.content_x = 0
        self.content_y = 0
        self.hscrollbar_height = 0
        self.vscrollbar_width = 0

        # We emulate some aspects of Dialog here.  We cannot just inherit
        # from Dialog because pyglet event handling won't allow keyword
        # arguments to be passed through.
        self.theme = None
        self.batch = None
        self.root_group = None
        self.panel_group = None
        self.bg_group = None
        self.fg_group = None
        self.highlight_group = None
        self.needs_layout = False

    def _get_controls(self):
        """
        We represent ourself as a Control to the Dialog, but we pass through
        the events we receive from Dialog.
        """
        base_controls = Wrapper._get_controls(self)
        controls = []
        our_left = self.content_x
        our_right = our_left + self.content_width
        our_bottom = self.content_y
        our_top = our_bottom + self.content_height
        for control, left, right, top, bottom in base_controls:
            controls.append(
                (control, max(left, our_left), min(right, our_right), min(top, our_top), max(bottom, our_bottom))
            )
        if self.hscrollbar is not None:
            controls += self.hscrollbar._get_controls()
        if self.vscrollbar is not None:
            controls += self.vscrollbar._get_controls()
        return controls

    def delete(self):
        """
        Delete all graphical elements associated with the Scrollable
        """
        Wrapper.delete(self)
        if self.hscrollbar is not None:
            self.hscrollbar.delete()
            self.hscrollbar = None
        if self.vscrollbar is not None:
            self.vscrollbar.delete()
            self.vscrollbar = None
        self.root_group = None
        self.panel_group = None
        self.bg_group = None
        self.fg_group = None
        self.highlight_group = None

    def ensure_visible(self, control):
        """
        Make sure a control is visible.
        """
        offset_x = 0
        if self.hscrollbar:
            offset_x = self.hscrollbar.get(self.content_width, self.content.width)
        offset_y = 0
        if self.vscrollbar:
            offset_y = (
                self.content.height
                - self.content_height
                - self.vscrollbar.get(self.content_height, self.content.height)
            )
        control_left = control.x - self.content_x - offset_x
        control_right = control_left + control.width
        control_bottom = control.y - self.content_y + offset_y
        control_top = control_bottom + control.height
        if self.hscrollbar is not None:
            self.hscrollbar.ensure_visible(control_left, control_right, max(self.content_width, self.content.width))
        if self.vscrollbar is not None:
            self.vscrollbar.ensure_visible(control_top, control_bottom, max(self.content_height, self.content.height))

    def expand(self, width, height):
        if self.content.is_expandable():
            if self.vscrollbar is not None:
                self.content_width = width - self.vscrollbar_width
            else:
                self.content_width = width
            if self.hscrollbar is not None:
                self.content_height = height - self.hscrollbar_height
            else:
                self.content_height = height
            self.content.expand(
                max(self.content_width, self.content.width), max(self.content_height, self.content.height)
            )
        self.width, self.height = width, height

    def get_root(self):
        if self.saved_dialog:
            return self.saved_dialog.get_root()
        else:
            return self

    def hit_test(self, x, y):
        """
        We only intercept events for the content region, not for
        our scrollbars.  They can handle themselves!
        """
        return (
            x >= self.content_x
            and y >= self.content_y
            and x < self.content_x + self.content_width
            and y < self.content_y + self.content_height
        )

    def is_expandable(self):
        return True

    def layout(self, x, y):
        """
        Reposition the Scrollable

        @param x X coordinate of lower left corner
        @param y Y coordinate of lower left corner
        """
        self.x, self.y = x, y

        # Work out the adjusted content width and height
        if self.hscrollbar is not None:
            self.hscrollbar.layout(x, y)
            y += self.hscrollbar.height
        if self.vscrollbar is not None:
            self.vscrollbar.layout(x + self.content_width, y)

        # Set the scissor group
        self.root_group.x, self.root_group.y = x - 1, y - 1
        self.root_group.width = self.content_width + 1
        self.root_group.height = self.content_height + 1

        # Work out the content layout
        self.content_x, self.content_y = x, y
        left = x
        top = y + self.content_height - self.content.height
        if self.hscrollbar:
            left -= self.hscrollbar.get(self.content_width, self.content.width)
        if self.vscrollbar:
            top += self.vscrollbar.get(self.content_height, self.content.height)
        self.content.layout(left, top)

        self.needs_layout = False

    def on_update(self, dt):
        """
        On updates, we redo the layout if scrollbars have changed position

        @param dt Time passed since last update event (in seconds)
        """
        if self.needs_layout:
            width, height = self.width, self.height
            self.size(self.saved_dialog)
            self.expand(width, height)
            self.layout(self.x, self.y)

    def set_needs_layout(self):
        self.needs_layout = True
        if self.saved_dialog is not None:
            self.saved_dialog.set_needs_layout()

    def set_wheel_hint(self, control):
        if self.saved_dialog is not None:
            self.saved_dialog.set_wheel_hint(control)

    def set_wheel_target(self, control):
        if self.saved_dialog is not None:
            self.saved_dialog.set_wheel_target(control)

    def size(self, dialog):
        """
        Recalculate the size of the Scrollable.

        @param dialog Dialog which contains us
        """
        if dialog is None:
            return
        Widget.size(self, dialog)
        if self.is_fixed_size:
            self.width, self.height = self.max_width, self.max_height

        self.hscrollbar_height = dialog.theme["hscrollbar"]["left"]["image"].height
        self.vscrollbar_width = dialog.theme["vscrollbar"]["up"]["image"].width

        if self.root_group is None:  # do we need to re-clone dialog groups?
            self.theme = dialog.theme
            self.batch = dialog.batch
            self.root_group = ScrollableGroup(0, 0, self.width, self.height, parent=dialog.fg_group)
            self.panel_group = pyglet.graphics.OrderedGroup(0, self.root_group)
            self.bg_group = pyglet.graphics.OrderedGroup(1, self.root_group)
            self.fg_group = pyglet.graphics.OrderedGroup(2, self.root_group)
            self.highlight_group = pyglet.graphics.OrderedGroup(3, self.root_group)
            Wrapper.delete(self)  # force children to abandon old groups

        Wrapper.size(self, self)  # all children are to use our groups

        if self.always_show_scrollbars or (self.max_width and self.width > self.max_width):
            if self.hscrollbar is None:
                self.hscrollbar = HScrollbar(self.max_width)
        else:
            if self.hscrollbar is not None:
                self.hscrollbar.delete()
                self.hscrollbar = None

        if self.always_show_scrollbars or (self.max_height and self.height > self.max_height):
            if self.vscrollbar is None:
                self.vscrollbar = VScrollbar(self.max_height)
        else:
            if self.vscrollbar is not None:
                self.vscrollbar.delete()
                self.vscrollbar = None

        self.width = min(self.max_width or self.width, self.width)
        self.content_width = self.width
        self.height = min(self.max_height or self.height, self.height)
        self.content_height = self.height

        if self.hscrollbar is not None:
            self.hscrollbar.size(dialog)
            self.hscrollbar.set(self.max_width, max(self.content.width, self.max_width))
            self.height += self.hscrollbar.height

        if self.vscrollbar is not None:
            self.vscrollbar.size(dialog)
            self.vscrollbar.set(self.max_height, max(self.content.height, self.max_height))
            self.width += self.vscrollbar.width
Ejemplo n.º 4
0
class Document(Control):
    """
    Allows you to embed a document within the GUI, which includes a
    vertical scrollbar as needed.
    """
    def __init__(self,
                 document,
                 width=1000,
                 height=5000,
                 is_fixed_size=False,
                 always_show_scrollbar=False):
        """
        Creates a new Document.
        """
        Control.__init__(self, width, height)
        self.max_height = height
        self.content_width = width
        if isinstance(document, basestring):
            self.document = pyglet.text.document.UnformattedDocument(document)
        else:
            self.document = document
        self.content = None
        self.content_width = width
        self.scrollbar = None
        self.set_document_style = False
        self.is_fixed_size = is_fixed_size
        self.always_show_scrollbar = always_show_scrollbar
        self.needs_layout = False

    def _do_set_document_style(self, attr, value):
        length = len(self.document.text)
        runs = [(start, end, doc_value) for start, end, doc_value in
                self.document.get_style_runs(attr).ranges(0, length)
                if doc_value is not None]
        if not runs:
            terminator = len(self.document.text)
        else:
            terminator = runs[0][0]
        self.document.set_style(0, terminator, {attr: value})

    def _get_controls(self):
        controls = []
        if self.scrollbar:
            controls += self.scrollbar._get_controls()
        controls += Control._get_controls(self)
        return controls

    def delete(self):
        if self.content is not None:
            self.content.delete()
            self.content = None
        if self.scrollbar is not None:
            self.scrollbar.delete()
            self.scrollbar = None

    def do_set_document_style(self, dialog):
        self.set_document_style = True

        # Check the style runs to make sure we don't stamp on anything
        # set by the user
        self._do_set_document_style('color', dialog.theme['text_color'])
        self._do_set_document_style('font_name', dialog.theme['font'])
        self._do_set_document_style('font_size', dialog.theme['font_size'])

    def get_text(self):
        return self.document.text

    def layout(self, x, y):
        self.x, self.y = x, y
        self.content.begin_update()
        self.content.x = x
        self.content.y = y
        self.content.end_update()
        if self.scrollbar is not None:
            self.scrollbar.layout(x + self.content_width, y)

    def on_update(self, dt):
        """
        On updates, we update the scrollbar and then set our view offset
        if it has changed.

        @param dt Time passed since last update event (in seconds)
        """
        if self.scrollbar is not None:
            self.scrollbar.dispatch_event('on_update', dt)
            pos = self.scrollbar.get(self.max_height,
                                     self.content.content_height)
            if pos != -self.content.view_y:
                self.content.view_y = -pos

        if self.needs_layout:
            self.needs_layout = False
            self.saved_dialog.set_needs_layout()

    def size(self, dialog):
        if dialog is None:
            return

        Control.size(self, dialog)
        if not self.set_document_style:
            self.do_set_document_style(dialog)
        if self.content is None:
            self.content = pyglet.text.layout.IncrementalTextLayout(
                self.document,
                self.content_width,
                self.max_height,
                multiline=True,
                batch=dialog.batch,
                group=dialog.fg_group)
            if self.is_fixed_size or (
                    self.max_height
                    and self.content.content_height > self.max_height):
                self.height = self.max_height
            else:
                self.height = self.content.content_height
            self.content.height = self.height
        if self.always_show_scrollbar or \
           (self.max_height and self.content.content_height > self.max_height):
            if self.scrollbar is None:
                self.scrollbar = VScrollbar(self.max_height)
            self.scrollbar.size(dialog)
            self.scrollbar.set(self.max_height, self.content.content_height)
        if self.scrollbar is not None:
            self.width = self.content_width + self.scrollbar.width
        else:
            self.width = self.content_width

    def set_text(self, text):
        self.document.text = text
        self.needs_layout = True