Пример #1
0
class SelectableText(Text):
    selected = widget.causes_redraw("_selected")

    selected_color = widget.auto_reconfig("_selected_color",
                                          g.resolve_color_alias)
    _selected_color = widget.causes_redraw("__selected_color")
    unselected_color = widget.auto_reconfig("_unselected_color",
                                            g.resolve_color_alias)
    unselected_color = widget.causes_redraw("__unselected_color")

    def __init__(self,
                 parent,
                 pos,
                 size,
                 border_color=None,
                 unselected_color=None,
                 selected_color=None,
                 **kwargs):
        super(SelectableText, self).__init__(parent, pos, size, **kwargs)

        self.border_color = border_color or "text_border"
        self.selected_color = selected_color or "text_background_selected"
        self.unselected_color = unselected_color or "text_background_unselected"

        self.selected = False

    def redraw(self):
        if self.selected:
            self.background_color = self.selected_color
        else:
            self.background_color = self.unselected_color
        super(SelectableText, self).redraw()
Пример #2
0
class SelectableText(Text):
    selected = widget.causes_redraw("_selected")
    selected_color = widget.causes_redraw("_selected_color")
    unselected_color = widget.causes_redraw("_unselected_color")

    def __init__(self,
                 parent,
                 pos,
                 size,
                 border_color=None,
                 unselected_color=None,
                 selected_color=None,
                 **kwargs):
        super(SelectableText, self).__init__(parent, pos, size, **kwargs)

        self.border_color = border_color or g.colors["white"]
        self.selected_color = selected_color or g.colors["light_blue"]
        self.unselected_color = unselected_color or g.colors["dark_blue"]

        self.selected = False

    def redraw(self):
        if self.selected:
            self.background_color = self.selected_color
        else:
            self.background_color = self.unselected_color
        super(SelectableText, self).redraw()
Пример #3
0
class ProgressText(SelectableText):
    progress = widget.causes_redraw("_progress")
    progress_color = widget.causes_redraw("_progress_color")
    def __init__(self, parent, *args, **kwargs):
        self.parent = parent
        self.progress = kwargs.pop("progress", 0)
        self.progress_color = kwargs.pop("progress", g.colors["blue"])
        super(ProgressText, self).__init__(parent, pos, size, **kwargs)

    def redraw(self):
        super(ProgressText, self).redraw()
        width, height = self.real_size
        self.surface.fill(self.progress_color,
                          (0, 0, width * self.progress, height))
        self.draw_borders()
Пример #4
0
class StyledText(Text):
    def update_text(self):
        self.text = "".join(self.chunks)

    chunks = widget.call_on_change("_chunks", update_text)
    styles = widget.causes_redraw("_styles")

    def __init__(self, *args, **kwargs):
        chunks = kwargs.pop("chunks", ())
        styles = kwargs.pop("styles", ())
        super(StyledText, self).__init__(*args, **kwargs)

        self.chunks = chunks
        self.styles = styles

    def print_text(self):
        if self.styles:
            offset = 0
            styles = []
            for chunk, style in zip(self.chunks, self.styles):
                offset += len(chunk)
                styles.append(list(style) + [offset])
            styles[-1][-1] = 0

            print_string(self.surface, self.text, (3, 2), self.font, styles,
                         self.align, self.valign, self.real_size, self.wrap)
        else:
            super(StyledText, self).print_text()
Пример #5
0
class ProgressText(SelectableText):
    progress = widget.causes_redraw("_progress")

    progress_color = widget.auto_reconfig("_progress_color",
                                          g.resolve_color_alias)
    _progress_color = widget.causes_redraw("__progress_color")

    def __init__(self, parent, pos, size, *args, **kwargs):
        self.parent = parent
        self.progress = kwargs.pop("progress", 0)
        self.progress_color = kwargs.pop("progress",
                                         "progress_background_progress")
        kwargs.setdefault("border_color", "progress_border")
        kwargs.setdefault("selected_color", "progress_background_selected")
        kwargs.setdefault("unselected_color", "progress_background_unselected")
        super(ProgressText, self).__init__(parent, pos, size, **kwargs)

    def redraw(self):
        super(ProgressText, self).redraw()
        width, height = self.real_size
        self.surface.fill(self.progress_color,
                          (0, 0, width * self.progress, height))
        self.draw_borders()
Пример #6
0
class FastText(Text):
    """Reduces font searches by assuming a monospace font, single-line text,
       and a fixed widget width."""
    text = widget.set_on_change("_text", "maybe_needs_refont")
    _text = widget.causes_redraw("__text")
    old_text = ""
    maybe_needs_refont = False

    def redraw(self):
        self.pick_font(self.calc_text_size(self._real_size))
        super(FastText, self).redraw()

    def pick_font(self, dimensions=None):
        if self.maybe_needs_refont and not self.needs_refont:
            if len(self.old_text) != len(self.text):
                self.old_text = self.text
                self.needs_refont = True
        self.maybe_needs_refont = False

        return super(FastText, self).pick_font(dimensions)
Пример #7
0
class Slider(button.Button):
    slider_color = widget.causes_redraw("_slider_color")
    slider_pos = widget.causes_rebuild("_slider_pos")
    slider_max = widget.causes_rebuild("_slider_max")
    slider_size = widget.causes_rebuild("_slider_size")
    horizontal = widget.causes_rebuild("_horizontal")

    def __init__(self, parent, pos = (-1,0), size = (-.1, -1),
                 anchor = constants.TOP_RIGHT, borders = constants.ALL,
                 border_color=None, background_color=None, slider_color=None,
                 slider_pos=0, slider_max=10, slider_size=5, horizontal=False,
                 **kwargs):
        kwargs.setdefault("priority", 80)
        super(Slider, self).__init__(parent, pos, size, anchor=anchor, **kwargs)

        self.borders = borders
        self.border_color = border_color or g.colors["white"]
        self.background_color = background_color or g.colors["dark_blue"]
        self.selected_color = self.background_color
        self.unselected_color = self.background_color
        self.slider_color = slider_color or g.colors["light_blue"]

        self.slider_pos = slider_pos
        self.slider_max = slider_max
        self.slider_size = slider_size
        self.horizontal = horizontal

        self.drag_state = None
        self.button = button.Button(self, pos = None, size = None,
                                    anchor = constants.TOP_LEFT,
                                    border_color = self.border_color,
                                    selected_color = self.slider_color,
                                    unselected_color = self.slider_color,
                                    priority = self.priority - 5)

    def redraw(self):
        super(Slider, self).redraw()
        self.button.selected_color = self.slider_color
        self.button.unselected_color = self.slider_color

    def add_hooks(self):
        super(Slider, self).add_hooks()
        self.parent.add_handler(constants.DRAG, self.handle_drag)
        self.parent.add_handler(constants.CLICK, self.handle_click, 50)

    def remove_hooks(self):
        super(Slider, self).remove_hooks()
        self.parent.remove_handler(constants.DRAG, self.handle_drag)
        self.parent.remove_handler(constants.CLICK, self.handle_click)

    def _calc_length(self, items):
        return items / float(self.slider_size + self.slider_max)

    def rebuild(self):
        super(Slider, self).rebuild()
        self.needs_resize = True

    def resize(self):
        super(Slider, self).resize()
        bar_start = self._calc_length(self.slider_pos)
        bar_length = self._calc_length(self.slider_size)

        if self.horizontal:
            self.button.pos = (-bar_start, 0)
            self.button.size = (-bar_length, -1)
            borders = [constants.TOP, constants.BOTTOM]

            self.button.resize()
            real_pos = self.button.real_pos[0]
            real_size = self.button.real_size[0]
            if real_pos == 0:
                borders.append(constants.LEFT)
            if real_pos + real_size == self.real_size[0]:
                borders.append(constants.RIGHT)
            self.button.borders = tuple(borders)
        else:
            self.button.pos = (0, -bar_start)
            self.button.size = (-1, -bar_length)
            borders = [constants.LEFT, constants.RIGHT]

            self.button.resize()
            real_pos = self.button.real_pos[1]
            real_size = self.button.real_size[1]
            if real_pos == 0:
                borders.append(constants.TOP)
            if real_pos + real_size == self.real_size[1]:
                borders.append(constants.BOTTOM)
            self.button.borders = tuple(borders)

    def handle_drag(self, event):
        if not self.visible:
            return

        if self.drag_state == None:
            self.start_pos = tuple(event.pos[i]-event.rel[i] for i in range(2))
            self.start_slider_pos = self.slider_pos
            if self.button.is_over(self.start_pos):
                self.drag_state = True
            else:
                self.drag_state = False

        if self.drag_state == True:
            if self.horizontal:
                dir = 0
            else:
                dir = 1

            mouse_pos = pygame.mouse.get_pos()
            rel = mouse_pos[dir] - self.start_pos[dir]
            unit = self._calc_length(1) * self.real_size[dir]
            movement = int( ( rel + (unit / 2.) ) // unit )

            new_pos = self.safe_pos(self.start_slider_pos + movement)
            self.slider_pos = new_pos

            raise constants.Handled

    def safe_pos(self, value):
        return max(0, min(self.slider_max, value))

    def handle_click(self, event):
        if self.drag_state == True:
            self.drag_state = None
            if not self.is_over(pygame.mouse.get_pos()):
                raise constants.Handled
        else:
            self.drag_state = None

    def jump(self, go_lower, big_jump=False, tiny_jump=False):
        if big_jump:
            jump_dist = max(1, self.slider_max // 2)
        elif tiny_jump:
            jump_dist = max(1, self.slider_max // 100)
        else:
            jump_dist = max(1, self.slider_size - 1)
        if go_lower:
            self.slider_pos = self.safe_pos(self.slider_pos - jump_dist)
        else:
            self.slider_pos = self.safe_pos(self.slider_pos + jump_dist)

    def activated(self, event):
        assert event.type == pygame.MOUSEBUTTONUP
        if self.horizontal:
            self.jump(go_lower=(event.pos[0] < self.button.collision_rect[0]))
        else:
            self.jump(go_lower = event.pos[1] < self.button.collision_rect[1])
        raise constants.Handled
Пример #8
0
class EditableText(widget.FocusWidget, Text):
    cursor_pos = widget.causes_redraw("_cursor_pos")

    def __init__(self, parent, *args, **kwargs):
        super(EditableText, self).__init__(parent, *args, **kwargs)

        if self.text is None:
            self.text = ""

        self.cursor_pos = len(self.text)

    def add_hooks(self):
        super(EditableText, self).add_hooks()
        self.parent.add_handler(constants.KEYDOWN, self.handle_key, 150)
        self.parent.add_handler(constants.CLICK, self.handle_click)

    def remove_hooks(self):
        super(EditableText, self).remove_hooks()
        self.parent.remove_handler(constants.KEYDOWN, self.handle_key)
        self.parent.remove_handler(constants.CLICK, self.handle_click)

    def handle_key(self, event):
        if not self.has_focus:
            return
        assert event.type == pygame.KEYDOWN
        if event.key == pygame.K_BACKSPACE:
            if self.cursor_pos > 0:
                self.text = self.text[:self.cursor_pos - 1] \
                            + self.text[self.cursor_pos:]
                self.cursor_pos -= 1
        elif event.key == pygame.K_DELETE:
            if self.cursor_pos < len(self.text):
                self.text = self.text[:self.cursor_pos] \
                 + self.text[self.cursor_pos + 1:]
        elif event.key == pygame.K_LEFT:
            self.cursor_pos = max(0, self.cursor_pos - 1)
        elif event.key == pygame.K_RIGHT:
            self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
        elif event.key == pygame.K_UP:
            self.cursor_pos = 0
        elif event.key == pygame.K_DOWN:
            self.cursor_pos = len(self.text)
        elif event.unicode:
            char = event.unicode
            if char == "\r":
                char = "\n"
            self.text = self.text[:self.cursor_pos] + char \
                        + self.text[self.cursor_pos:]
            self.cursor_pos += len(char)
        else:
            return

        raise constants.Handled

    hitbox = [0, 0, 0, 0]

    def handle_click(self, event):
        if getattr(self, "collision_rect", None) is None:
            return
        elif not self.collision_rect.collidepoint(event.pos):
            return

        self.has_focus = True
        self.took_focus(self)

        self.font.set_bold(self.bold)

        click_x = event.pos[0] - self.collision_rect[0]
        click_y = event.pos[1] - self.collision_rect[1]

        if self.wrap:
            lines = split_wrap(self.text, self.font, self.real_size[0] - 4)
        else:
            lines = split_wrap(self.text, self.font, 0)

        line_size = self.font.get_linesize()
        self.hitbox[3] = line_size
        real_text_height = line_size * len(lines)

        line_y = 2
        if self.valign != constants.TOP \
           and real_text_height <= self.collision_rect.height - 4:
            excess_space = self.collision_rect.height - real_text_height
            if self.valign == constants.MID:
                line_y = excess_space // 2
            else:  # self.valign == constants.BOTTOM
                line_y = excess_space

        char_offset = 0
        for line in lines:
            line_y += line_size
            char_offset += len(line)
            if line_y >= click_y:
                break

        char_offset -= len(line)

        self.hitbox[1] = line_y - line_size
        line_x = 3
        if self.align != constants.LEFT:
            line_width = self.font.size(line)[0]
            excess_space = self.collision_rect.width - line_width
            if self.align == constants.CENTER:
                line_x = excess_space // 2
            else:  # self.align == constants.LEFT
                line_x = excess_space

        prev_width = 20000
        widths = get_widths(self.font, line)
        for index, width in enumerate(widths):
            if line_x + (width // 2) < click_x:
                line_x += width
                prev_width = width
            else:
                break
        else:
            index += 1
            width = 20000
        self.hitbox[0] = line_x - prev_width // 2
        self.hitbox[2] = prev_width - (prev_width // 2) + width // 2
        self.cursor_pos = char_offset + index

        self.font.set_bold(False)

    def redraw(self):
        super(EditableText, self).redraw()

        if self.wrap:
            lines = split_wrap(self.text, self.font, self.real_size[0] - 4)
        else:
            lines = split_wrap(self.text, self.font, 0)

        if not self.has_focus:
            return

        line_size = self.font.get_linesize()
        real_text_height = line_size * len(lines)

        line_y = 2
        if self.valign != constants.TOP \
           and real_text_height <= self.real_size[1] - 4:
            excess_space = self.real_size[1] - real_text_height
            if self.valign == constants.MID:
                line_y = excess_space // 2
            else:  # self.valign == constants.BOTTOM
                line_y = excess_space

        char_offset = 0
        for line in lines:
            if char_offset + len(line) < self.cursor_pos:
                char_offset += len(line)
                line_y += line_size
            else:
                break

        after_char = self.cursor_pos - char_offset

        line_x = 3
        if self.align != constants.LEFT:
            line_width = self.font.size(line)[0]
            excess_space = self.real_size[0] - line_width
            if self.align == constants.CENTER:
                line_x = excess_space // 2
            else:  # self.align == constants.LEFT
                line_x = excess_space

        line_x += self.font.size(line[:after_char])[0]

        self.surface.fill(self.color, (line_x, line_y, 1, line_size))

        if DEBUG:
            s = pygame.Surface(self.hitbox[2:]).convert_alpha()
            s.fill((255, 0, 255, 100))
            self.surface.blit(s, self.hitbox)
Пример #9
0
class Text(widget.BorderedWidget):
    text = widget.call_on_change("_text", resize_redraw)
    base_font = widget.call_on_change("_base_font", resize_redraw)
    shrink_factor = widget.call_on_change("_shrink_factor", resize_redraw)
    underline = widget.call_on_change("_underline", resize_redraw)
    wrap = widget.call_on_change("_wrap", resize_redraw)
    bold = widget.call_on_change("_bold", resize_redraw)

    color = widget.causes_redraw("_color")
    align = widget.causes_redraw("_align")
    valign = widget.causes_redraw("_valign")

    def __init__(self,
                 parent,
                 pos,
                 size=(0, .05),
                 anchor=constants.TOP_LEFT,
                 text=None,
                 base_font=None,
                 shrink_factor=1,
                 color=None,
                 align=constants.CENTER,
                 valign=constants.MID,
                 underline=-1,
                 wrap=True,
                 bold=False,
                 text_size=36,
                 **kwargs):
        super(Text, self).__init__(parent, pos, size, anchor, **kwargs)

        self.text = text
        self.base_font = base_font or g.font[0]
        self.color = color or g.colors["white"]
        self.shrink_factor = shrink_factor
        self.underline = underline
        self.align = align
        self.valign = valign
        self.wrap = wrap
        self.bold = bold
        self.text_size = text_size

    max_size = property(lambda self: convert_font_size(self.text_size))
    font = property(lambda self: self._font)

    def pick_font(self, dimensions):
        nice_size = self.pick_font_size(dimensions, False)
        mean_size = self.pick_font_size(dimensions)

        if nice_size > mean_size - convert_font_size(5):
            size = nice_size
        else:
            size = mean_size

        return self.base_font[size]

    def font_bisect(self, test_font):
        left = 0
        right = len(self.base_font)
        if self.max_size:
            right = min(right, self.max_size)

        def test_size(size):
            font = self.base_font[size]

            font.set_bold(self.bold)
            result = test_font(font)
            font.set_bold(False)

            return result

        return do_bisect(left, right, test_size)

    def pick_font_size(self, dimensions, break_words=True):
        if dimensions[0]:
            width = int((dimensions[0] - 4) * self.shrink_factor)
        else:
            width = None
        height = int((dimensions[1] - 4) * self.shrink_factor)

        basic_line_count = self.text.count("\n") + 1

        def test_size(test_font):
            too_wide = False
            if width:
                if self.wrap:
                    try:
                        lines = split_wrap(self.text, test_font, width,
                                           break_words)
                    except WrapError:
                        lines = []
                        too_wide = True
                else:
                    lines = split_wrap(self.text, test_font, 0)
                    for line in lines:
                        if test_font.size(line)[0] > width:
                            too_wide = True
                            break
                line_count = len(lines)
            else:
                line_count = basic_line_count

            too_tall = (test_font.get_linesize() * line_count) > height

            return not (too_tall or too_wide)

        return self.font_bisect(test_size)

    def size_using_font(self, font, width=0):
        #Calculate the size of the text block.
        raw_width, raw_height = size_of_block(self.text, font, width)

        #Adjust for shrink_factor and borders.
        width = int(raw_width / self.shrink_factor) + 4
        height = int(raw_height / self.shrink_factor) + 4

        return width, height

    def calc_text_size(self, initial_dimensions):
        if not (initial_dimensions[0] and initial_dimensions[1]):
            if not self.max_size:
                raise ValueError("No font size given, but a dimension is 0.")

            max_font = self.base_font[self.max_size]
            if initial_dimensions[0] == initial_dimensions[1] == 0:
                # No size specified, use the natural size of the max font.
                width, height = self.size_using_font(max_font)
                return (width, height), max_font
            elif not initial_dimensions[1]:
                # Width specified, use the size of the max font, word-wrapped.
                text_width = int(
                    (initial_dimensions[0] - 4) * self.shrink_factor)
                width, height = self.size_using_font(max_font,
                                                     width=text_width)
                return (initial_dimensions[0], height), max_font
            else:
                # Height specified.  Try the natural size of the max font.
                width, height = self.size_using_font(max_font)

                if height <= initial_dimensions[1]:
                    return (width, initial_dimensions[1]), max_font
                else:
                    # Too tall.  Run a binary search to find the largest font
                    # size that fits.
                    def test_size(font):
                        width, height = self.size_using_font(font)
                        width, raw_height = size_of_block(self.text, font)
                        height = int(raw_height / self.shrink_factor) + 4
                        return height <= initial_dimensions[1]

                    font_size = self.font_bisect(test_size)
                    font = self.base_font[font_size]
                    width, height = self.size_using_font(font)

                    return (width, initial_dimensions[1]), font
        else:
            # Both sizes specified.  Search for a usable font size.
            return initial_dimensions, self.pick_font(initial_dimensions)

    def _calc_size(self):
        base_size = list(super(Text, self)._calc_size())

        if self.text is None:
            return tuple(base_size)
        else:
            # Determine the true size and font of the text area.
            text_size, font = self.calc_text_size(base_size)
            self._font = font
            return tuple(text_size)

    def redraw(self):
        super(Text, self).redraw()

        if self.text != None:
            self.print_text()

    def print_text(self):
        # Mark the character to be underlined (if any).
        no_underline = [self.color, None, False]
        underline = [self.color, None, True]
        styles = [no_underline + [0]]
        if 0 <= self.underline < len(self.text):
            styles.insert(0, underline + [self.underline + 1])
            if self.underline != 0:
                styles.insert(0, no_underline + [self.underline])

        self.font.set_bold(self.bold)
        # Print the string itself.
        print_string(self.surface, self.text, (3, 2), self.font, styles,
                     self.align, self.valign, self.real_size, self.wrap)
        self.font.set_bold(False)
Пример #10
0
class Dialog(text.Text):
    """A Dialog is a Widget that has its own event loop and can be faded out."""

    top = None # The top-level dialog.

    faded = widget.causes_redraw("_faded")

    # Used for detecting double-clicks.
    #            (time, (x, y), button)
    last_click = (0,    (0, 0), -1    )

    def __init__(self, parent=None, pos=(.5, .1), size=(1, .9),
                 anchor=constants.TOP_CENTER, **kwargs):
        kwargs.setdefault("background_color", (0, 0, 0, 0))
        kwargs.setdefault("borders", ())
        super(Dialog, self).__init__(parent, pos, size, anchor, **kwargs)
        self.visible = False
        self.faded = False
        self.is_above_mask = True
        self.self_mask = True
        self.needs_remask = True

        self.needs_timer = None

        self.handlers = {}
        self.key_handlers = {}

        self.add_handler(constants.CLICK, self.fake_escape, 200)

    def lost_focus(self):
        self.key_down = None
        self.faded = True
        self.stop_timer()

    def fake_escape(self, event):
        if event.button == 3:
            fake_key(pygame.K_ESCAPE)
            raise constants.Handled

    def regained_focus(self):
        self.faded = False
        self.start_timer()
        self.fake_mouse()

    def make_top(self):
        """Makes this dialog be the top-level dialog."""
        if self.parent != None:
            raise ValueError, \
                  "Dialogs with parents cannot be the top-level dialog."
        else:
            Dialog.top = self

    def remake_surfaces(self):
        """Recreates the surfaces that this widget will draw on."""
        super(Dialog, self).remake_surfaces()

    def start_timer(self, force = False):
        if self.needs_timer == None:
            self.needs_timer = bool(self.handlers.get(constants.TICK, False))
        if self.needs_timer or force:
            pygame.time.set_timer(pygame.USEREVENT, 1000 / g.FPS)

    def stop_timer(self):
        pygame.time.set_timer(pygame.USEREVENT, 0)

    def reset_timer(self):
        self.stop_timer()
        self.start_timer()

    def show(self):
        """Shows the dialog and enters an event-handling loop."""
        from code.g import play_music

        self.visible = True
        self.key_down = None
        self.start_timer()

        # Pretend to jiggle the mouse pointer, to force buttons to update their
        # selected state.
        Dialog.top.maybe_update()
        self.fake_mouse()

        # Force a timer tick at the start to make sure everything's initialized.
        if self.needs_timer:
            self.handle(pygame.event.Event(pygame.USEREVENT))
            Dialog.top.maybe_update()
            pygame.display.flip()

        while True:
            # Update handles updates of all kinds to all widgets, as needed.
            Dialog.top.maybe_update()
            play_music()
            event = pygame.event.wait()
            result = self.handle(event)
            if result != constants.NO_RESULT:
                self.visible = False
                return result
        self.stop_timer()

    def add_handler(self, type, handler, priority = 100):
        """Adds a handler of the given type, with the given priority."""
        bisect.insort( self.handlers.setdefault(type, []),
                       (priority, handler) )

    def remove_handler(self, type, handler):
        """Removes all instances of the given handler from the given type."""
        self.handlers[type] = [h for h in self.handlers.get(type, [])
                                 if h[1] != handler]

    def add_key_handler(self, key, handler, priority = 100):
        """Adds a key handler to the given key, with the given priority."""
        bisect.insort( self.key_handlers.setdefault(key, []),
                       (priority, handler) )

    def remove_key_handler(self, key, handler):
        """Removes all instances of the given handler from the given key."""
        self.key_handlers[key] = [h for h in self.key_handlers.get(key, [])
                                    if h[1] != handler]

    def handle(self, event):
        """Sends an event through all the applicable handlers, returning
           constants.NO_RESULT if the event goes unhandled or is handled without
           requesting the dialog to exit.  Otherwise, returns the value provided
           by the handler."""
        # Get the applicable handlers.  The handlers lists are all sorted.
        # If more than one handler type is applicable, we use [:] to make a
        # copy of the first type's list, then insort_all to insert the elements
        # of the other lists in proper sorted order.
        handlers = []
        if event.type == pygame.MOUSEMOTION:
            # Compress multiple MOUSEMOTION events into one.
            # Note that the pos will be wrong, so pygame.mouse.get_pos() must
            # be used instead.
            time.sleep(1. / g.FPS)
            pygame.event.clear(pygame.MOUSEMOTION)

            # Generic mouse motion handlers.
            handlers = self.handlers.get(constants.MOUSEMOTION, [])[:]

            # Drag handlers.
            if event.buttons[0]:
                insort_all(handlers, self.handlers.get(constants.DRAG, []))
        elif event.type == pygame.USEREVENT:
            # Clear excess timer ticks.
            pygame.event.clear(pygame.USEREVENT)

            # Timer tick handlers.
            handlers = self.handlers.get(constants.TICK, [])

            # Generate repeated keys.
            if self.key_down:
                self.repeat_counter += 1
                if self.repeat_counter >= 5:
                    self.repeat_counter = 0
                    self.handle(self.key_down)
        elif event.type in (pygame.KEYDOWN, pygame.KEYUP):
            # Generic key event handlers.
            handlers = self.handlers.get(constants.KEY, [])[:]

            if event.type == pygame.KEYDOWN:
                # Generic keydown handlers.
                insort_all(handlers, self.handlers.get(constants.KEYDOWN, []))

                if event.unicode:
                    # Unicode-based keydown handlers for this particular key.
                    insort_all(handlers, self.key_handlers.get(event.unicode, []))
                # Keycode-based handlers for this particular key.
                insort_all(handlers, self.key_handlers.get(event.key, []))

                # Begin repeating keys.
                if self.key_down is not event:
                    self.key_down = event
                    self.repeat_counter = -10
                    self.start_timer(force = True)
            else: # event.type == pygame.KEYUP:
                # Stop repeating keys.
                self.key_down = None
                self.reset_timer()

                # Generic keyup handlers.
                insort_all(handlers, self.handlers.get(constants.KEYUP, []))

                # Keycode-based handlers for this particular key.
                insort_all(handlers, self.key_handlers.get(event.key, []))

            # OLPC XO-1 ebook mode.
            if g.ebook_mode and event.key in KEYPAD:
                handlers = [(0, handle_ebook)]
        elif event.type == pygame.MOUSEBUTTONUP:
            # Handle mouse scrolls by imitating PageUp/Dn
            if event.button in (4, 5):
                if event.button == 4:
                    key = pygame.K_PAGEUP
                else:
                    key = pygame.K_PAGEDOWN
                fake_key(key)
                return constants.NO_RESULT

            # Mouse click handlers.
            handlers = [] + self.handlers.get(constants.CLICK, [])

            when = time.time()
            where = event.pos
            what = event.button

            old_when, old_where, old_what = self.last_click
            self.last_click = when, where, what

            if what == old_what and when - old_when < .5:
                # Taxicab distance.
                dist = (abs(where[0] - old_where[0]) +
                        abs(where[1] - old_where[1]))

                if dist < 10:
                    # Add double-click handlers, but keep the click handlers.
                    insort_all(handlers,
                               self.handlers.get(constants.DOUBLECLICK, []))
        elif event.type == pygame.QUIT:
            raise SystemExit

        return self.call_handlers(handlers, event)

    def fake_mouse(self):
        """Fakes a MOUSEMOTION event.  MOUSEMOTION handlers must be able to
           handle a None event, in order to support this method."""
        handlers = self.handlers.get(constants.MOUSEMOTION, [])[:]
        self.call_handlers(handlers, event=None)

    def call_handlers(self, handlers, event):
        # Feed the event to all the handlers, in priority order.
        for __, handler in handlers:
            try:
                handler(event)
            except constants.Handled:
                break # If it's been handled, we leave the rest alone.
            except constants.ExitDialog, e:
                # Exiting the dialog.
                if e.args:
                    # If we're given a return value, we pass it on.
                    return e.args[0]
                else:
                    # Otherwise, exit with a return value of None.
                    return

        # None of the handlers instructed the dialog to close, so we pass that
        # information back up to the event loop.
        return constants.NO_RESULT
Пример #11
0
class Listbox(widget.FocusWidget, text.SelectableText):
    list = widget.causes_rebuild("_list")
    align = widget.causes_redraw("_align")
    list_size = widget.causes_rebuild("_list_size")
    list_pos = widget.causes_rebuild("_list_pos")

    def __init__(self,
                 parent,
                 pos,
                 size,
                 anchor=constants.TOP_LEFT,
                 list=None,
                 list_pos=0,
                 list_size=-20,
                 borders=constants.ALL,
                 align=constants.CENTER,
                 **kwargs):
        super(Listbox, self).__init__(parent,
                                      pos,
                                      size,
                                      anchor=anchor,
                                      **kwargs)

        self.list = list or []
        self.display_elements = []
        self.borders = borders

        self.align = align
        self.list_size = list_size
        self.list_pos = list_pos

        self.auto_scroll = True
        self.scrollbar = scrollbar.UpdateScrollbar(self,
                                                   update_func=self.on_scroll)

    def add_hooks(self):
        super(Listbox, self).add_hooks()
        self.parent.add_handler(constants.CLICK, self.on_click, 90)
        self.parent.add_key_handler(pygame.K_UP, self.got_key)
        self.parent.add_key_handler(pygame.K_DOWN, self.got_key)
        self.parent.add_key_handler(pygame.K_PAGEUP, self.got_key)
        self.parent.add_key_handler(pygame.K_PAGEDOWN, self.got_key)

    def remove_hooks(self):
        super(Listbox, self).remove_hooks()
        self.parent.remove_handler(constants.CLICK, self.on_click)
        self.parent.remove_key_handler(pygame.K_UP, self.got_key)
        self.parent.remove_key_handler(pygame.K_DOWN, self.got_key)
        self.parent.remove_key_handler(pygame.K_PAGEUP, self.got_key)
        self.parent.remove_key_handler(pygame.K_PAGEDOWN, self.got_key)

    def on_scroll(self, scroll_pos):
        self.needs_rebuild = True

    def on_click(self, event):
        if self.collision_rect.collidepoint(event.pos):
            self.has_focus = True
            self.took_focus(self)

            # Figure out which element was clicked...
            local_vert_abs = event.pos[1] - self.collision_rect[1]
            local_vert_pos = local_vert_abs / float(self.collision_rect.height)
            index = int(local_vert_pos * len(self.display_elements))

            # ... and select it.
            self.list_pos = index + self.scrollbar.scroll_pos

    def safe_pos(self, raw_pos):
        return max(0, min(len(self.list) - 1, raw_pos))

    def got_key(self, event):
        if not self.has_focus:
            return

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_UP:
                new_pos = self.list_pos - 1
            elif event.key == pygame.K_DOWN:
                new_pos = self.list_pos + 1
            elif event.key == pygame.K_PAGEUP:
                new_pos = self.list_pos - (self.scrollbar.window - 1)
            elif event.key == pygame.K_PAGEDOWN:
                new_pos = self.list_pos + (self.scrollbar.window - 1)
            else:
                return

            self.list_pos = self.safe_pos(new_pos)
            self.scrollbar.scroll_to(self.list_pos)
            raise constants.Handled

    def num_elements(self):
        # If self.list_size is negative, we interpret it as a minimum height
        # for each element and calculate the number of elements to show.
        list_size = self.list_size
        if list_size < 0:
            min_height = -list_size
            list_size = max(1,
                            self._make_collision_rect().height // min_height)
        return list_size

    def remake_elements(self):
        list_size = self.num_elements()
        current_size = len(self.display_elements)

        if current_size > list_size:
            # Remove the excess ones.
            for child in self.display_elements[list_size:]:
                child.remove_hooks()
            del self.display_elements[list_size:]
        elif current_size < list_size:
            if current_size > 0:
                self.display_elements[-1].borders = \
                    (constants.LEFT, constants.TOP)

            # Create the new ones.
            for i in range(list_size - current_size):
                self.display_elements.append(self.make_element())

        self.display_elements[-1].borders = (constants.TOP, constants.LEFT,
                                             constants.BOTTOM)

        # Move the scrollbar to the end so that it gets drawn on top.
        self.children.remove(self.scrollbar)
        self.children.append(self.scrollbar)

    def make_element(self):
        return text.SelectableText(self,
                                   None,
                                   None,
                                   anchor=constants.TOP_LEFT,
                                   borders=(constants.TOP, constants.LEFT),
                                   border_color=self.border_color,
                                   selected_color=self.selected_color,
                                   unselected_color=self.unselected_color,
                                   align=self.align)

    def resize(self):
        super(Listbox, self).resize()

        if self.num_elements() != len(self.display_elements):
            self.remake_elements()

        self.scrollbar.resize()

        self.rebuild()

    def rebuild(self):
        self.list_pos = self.safe_pos(self.list_pos)

        if self.needs_resize:
            self.resize()
            return

        window_size = len(self.display_elements)
        list_size = len(self.list)

        self.scrollbar.window = len(self.display_elements)
        self.scrollbar.elements = list_size

        if self.auto_scroll:
            self.auto_scroll = False
            self.scrollbar.center(self.list_pos)

        self.scrollbar.rebuild()

        scrollbar_width = self.scrollbar.real_size[0]
        my_width = self.real_size[0]
        scrollbar_rel_width = scrollbar_width / float(my_width)

        offset = self.scrollbar.scroll_pos
        for index, element in enumerate(self.display_elements):
            list_index = index + offset

            # Position and size the element.
            element.pos = (0, -index / float(window_size))
            element.size = (-1 + scrollbar_rel_width, -1 / float(window_size))

            # Set up the element contents.
            element.selected = (list_index == self.list_pos)
            self.update_element(element, list_index)

        self.needs_redraw = True
        super(Listbox, self).rebuild()

    def update_element(self, element, list_index):
        if 0 <= list_index < len(self.list):
            element.text = self.list[list_index]
        else:
            element.text = ""
Пример #12
0
class Text(widget.BorderedWidget):
    text = causes_refont("_text")
    base_font = causes_refont("_base_font")
    color = widget.causes_redraw("_color")
    shrink_factor = causes_refont("_shrink_factor")
    underline = causes_refont("_underline")
    align = widget.causes_redraw("_align")
    valign = widget.causes_redraw("_valign")
    wrap = causes_refont("_wrap")
    bold = causes_refont("_bold")
    oversize = causes_refont("_oversize")

    needs_refont = widget.causes_resize("_needs_refont")

    lorem_ipsum = {}

    def __init__(self, parent, pos, size=(0, .05), anchor=constants.TOP_LEFT,
                 text=None, base_font=None, shrink_factor=1,
                 color=None, align=constants.CENTER, valign=constants.MID,
                 underline=-1, wrap=True, bold=False, oversize=False, **kwargs):
        super(Text, self).__init__(parent, pos, size, anchor, **kwargs)

        self.needs_refont = True

        self.text = text
        self.base_font = base_font or g.font[0]
        self.color = color or g.colors["white"]
        self.shrink_factor = shrink_factor
        self.underline = underline
        self.align = align
        self.valign = valign
        self.wrap = wrap
        self.bold = bold
        self.oversize = oversize

    def pick_font(self, dimensions=None):
        if dimensions and self.needs_refont:
            nice_size = self.pick_font_size(dimensions, False)
            mean_size = self.pick_font_size(dimensions)

            if nice_size > mean_size - 5:
                size = nice_size
            else:
                size = mean_size
            self._font = self.base_font[size]

            self.needs_refont = False

        return self._font

    font = property(pick_font)

    def pick_font_size(self, dimensions, break_words=True):
        if dimensions[0]:
            width = dimensions[0] - 4
        else:
            width = None
        height = dimensions[1]

        basic_line_count = self.text.count("\n") + 1

        # Run a binary search for the best font size.
        # Thanks to bisect.bisect_left for the basic implementation.
        left = 8
        if self.oversize or self.base_font[0] not in Text.lorem_ipsum:
            right = len(self.base_font)
        else:
            right = Text.lorem_ipsum[self.base_font[0]].font_size

        while left + 1 < right:
            test_index = (left + right) // 2
            test_font = self.base_font[test_index]
            test_font.set_bold(self.bold)

            too_wide = False
            if width:
                if self.wrap:
                    try:
                        lines = split_wrap(self.text, test_font, width,
                                           break_words)
                    except WrapError:
                        lines = []
                        too_wide = True
                else:
                    lines = split_wrap(self.text, test_font, 0)
                    for line in lines:
                        if test_font.size(line)[0] > width:
                            too_wide = True
                            break
                line_count = len(lines)
            else:
                line_count = basic_line_count

            too_tall = (test_font.get_linesize() * line_count) > height
            if too_tall or too_wide:
                right = test_index
            else:
                left = test_index

            test_font.set_bold(False)

        return left

    def calc_text_size(self, dimensions=None):
        if dimensions == None:
            dimensions = self.real_size

        # Calculate the text height.
        height = int( (dimensions[1] - 4) * self.shrink_factor )
        width = dimensions[0]

        return width, height

    def _calc_size(self):
        base_size = list(super(Text, self)._calc_size())

        if self.text != None:
            # Determine the true size of the text area.
            text_size = self.calc_text_size(base_size)

            # Pick a font based on that size.
            self.needs_refont = True
            font = self.pick_font(text_size)

            # If the width is unspecified, calculate it from the font and text.
            if base_size[0] == 0:
                base_size[0] = font.size(self.text)[0] + 16
        return tuple(base_size)

    def redraw(self):
        super(Text, self).redraw()

        if self.text != None:
            self.print_text()

    def print_text(self):
        # Mark the character to be underlined (if any).
        no_underline = [self.color, None, False]
        underline = [self.color, None, True]
        styles = [no_underline + [0]]
        if 0 <= self.underline < len(self.text):
            styles.insert(0, underline + [self.underline + 1])
            if self.underline != 0:
                styles.insert(0, no_underline + [self.underline])

        self.font.set_bold(self.bold)
        # Print the string itself.
        print_string(self.surface, self.text, (3, 2), self.font, styles,
                     self.align, self.valign, self.real_size, self.wrap)
        self.font.set_bold(False)