コード例 #1
0
 def dirty_set(self, rect=None):
     tick = get_current_tick()
     if rect is None:
         rect = Rect((0, 0), self.size)
     else:
         rect = Rect(rect) if not isinstance(rect, Rect) else rect
     self.dirty_registry.reset_to((tick, rect, None))
コード例 #2
0
    def dirty_update(self):

        tick = get_current_tick()

        # If there is any time-dependant image change, there is no way
        # to predict what changes from one frame to the next - just mark
        # all shape as dirty.
        if any("tick" in transformer.signatures
               for transformer in self.context.transformers):
            self.dirty_set()
            return

        # Collect rects from sprites
        if self.has_sprites:
            for sprite in self.sprites:
                if not sprite.active:
                    continue

                for rect in sprite.dirty_rects:
                    self.dirty_registry.push(
                        (tick, sprite.owner_coords(rect), sprite.shape))

        # mark dirty pixels

        tile_size = (DIRTY_TILE_SIZE, DIRTY_TILE_SIZE)
        self_rect = Rect((0, 0), self.size)
        for tile in self.dirty_pixels:
            rect = Rect(tile * DIRTY_TILE_SIZE, width_height=tile_size)
            rect = rect.intersection(self_rect)
            if not rect:
                continue
            self.dirty_registry.push((tick, rect, None))
        self.dirty_pixels = set()
コード例 #3
0
 def rect(self):
     r = Rect(self.shape.size)
     if self.anchor == "topleft":
         r.left = self.pos.x
         r.top = self.pos.y
     elif self.anchor == "center":
         r.center = self.pos
     return r
コード例 #4
0
 def update(self, pos1=None, pos2=None):
     rect = Rect(pos1, pos2)
     if rect.c2 == (0, 0):
         rect.c2 = (self.width, self.height)
     with self.commands:
         for y in range(rect.top, rect.bottom):
             for x in range(rect.left, rect.right):
                 self[x, y] = _REPLAY
コード例 #5
0
    def rect(self, pos1, pos2=(), *, rel=(), erase=False):
        """Draws a rectangle

        Args:
          - pos1 (Union[Rectangle, 2-tuple]): top-left coordinates
          - pos2 (Optional[2-tuple]): bottom-right limit coordinates. If not given, pass "rel" instead
          - rel (Optional[2-tuple]): (width, height) of rectangle. Ignored if "pos2" is given
          - fill (bool): Whether fill-in the rectangle, or only draw the outline. Defaults to False.
          - erase (bool): Whether to draw (set) or erase (reset) pixels.

        Public call to draw a rectangle using character blocks
        on the terminal.
        The color line is defined in the owner's context.color
        attribute. In the case of high-resolution drawing, the background color
        is also taken from the context.
        """
        pos1, pos2 = Rect(pos1, pos2, width_height=rel)
        # Ending interval is open, just as Python works with intervals.
        pos2 -= (1, 1)

        fill = self.context.fill

        x1, y1 = pos1
        x2, y2 = pos2
        self.line(pos1, (x2, y1), erase=erase)
        self.line((x1, y2), pos2, erase=erase)
        if (fill or erase) and y2 != y1:
            direction = int((y2 - y1) / abs(y2 - y1))
            for y in range(y1 + 1, y2, direction):
                self.line((x1, y), (x2, y), erase=erase)
        else:
            self.line(pos1, (x1, y2))
            self.line((x2, y1), pos2)
コード例 #6
0
    def update(self, pos1=None, pos2=None):
        """Main method to update the display

        An interactive application or animation should call this once
        per frame to have th display contents updated on the terminal.

        It can optionally update just a part of the output screen, if
        pos1 or pos2 are given.

        As of pre-0.4.0 development an app should manually provide
        its "mainloop" and call this on each frame. Later development
        will probably have an optional higher level loop
        that will automate calling here.

        Args:
            - pos1, pos2: Corners of a rectangle delimitting the area to be updated.
                (optionally, 'pos1' can be a Rect object)

        """
        tick_forward()

        if self.interactive and terminedia.input.keyboard.enabled and not self._inkey_called_since_last_update:
            # Ensure the dispatch of keypress events:
            terminedia.inkey(consume=False)

        self._inkey_called_since_last_update = False

        self.process_events()
        rect = Rect(pos1, pos2)
        if rect.c2 == (0, 0) and pos2 is None:
            rect.c2 = (self.width, self.height)
        if hasattr(self.commands,
                   "fast_render") and self.root_context.fast_render:
            target = [
                rect
            ] if pos1 is not None or self.root_context.interactive_mode else self.data.dirty_rects
            self.commands.fast_render(self.data, target)
            self.data.dirty_clear()
        else:
            with self.commands:
                for y in range(rect.top, rect.bottom):
                    for x in range(rect.left, rect.right):
                        self[x, y] = _REPLAY
        if self.root_context.interactive_mode:
            # move cursor a couple lines from the bottom to avoid scrolling
            for i in range(3):
                self.commands.up()
コード例 #7
0
 def new_line_start(self, index, direction):
     pos = V2(index)
     direction = V2(direction)
     r = Rect(self.text_plane.size )
     while True:
         pos, direction, flow_changed, position_is_used = self.move_along_marks(pos, direction)
         if flow_changed:
             return pos, direction
         if not pos in r:
             return pos, direction
コード例 #8
0
 def __getitem__(self, index):
     roi = self.roi
     if isinstance(index, Rect):
         return ShapeView(
             self.original,
             Rect(V2.max(roi.c1, (roi.c1 + index.c1)),
                  V2.min((roi.c1 + index.c2), roi.c2)))
     if not 0 <= index[0] < roi.width or not 0 <= index[1] < roi.bottom:
         raise IndexError(f"Value out of limits f{roi.width_height}")
     return self.original[roi.c1 + index]
コード例 #9
0
ファイル: screen.py プロジェクト: GinLuc/terminedia
    def update(self, pos1=None, pos2=None):

        rect = Rect(pos1, pos2)
        if rect.c2 == (0, 0) and pos2 is None:
            rect.c2 = (self.width, self.height)
        if hasattr(self.commands,
                   "fast_render") and self.root_context.fast_render:
            target = [
                rect
            ] if pos1 is not None or self.root_context.interactive_mode else self.data.dirty_rects
            self.commands.fast_render(self.data, target)
            self.data.dirty_clear()
        else:
            with self.commands:
                for y in range(rect.top, rect.bottom):
                    for x in range(rect.left, rect.right):
                        self[x, y] = _REPLAY
        tick_forward()
        if self.root_context.interactive_mode:
            # move cursor a couple lines from the bottom to avoid scrolling
            for i in range(3):
                self.commands.up()
コード例 #10
0
 def __getitem__(self, pos):
     """Values for each pixel are: character, fg_color, bg_color, effects.
     """
     if isinstance(pos, Rect):
         roi = pos
     elif isinstance(pos, tuple) and isinstance(pos[0], slice):
         if any(pos[i].step not in (None, 1) for i in (0, 1)):
             raise NotImplementedError(
                 "Slice stepping not implemented for shapes")
         roi = Rect(*pos)
     else:
         return None
     return ShapeView(self, roi)
コード例 #11
0
    def refresh(self,
                clear=True,
                *,
                preserve_attrs=False,
                rect=None,
                target=None):
        """Render entire text buffer to the owner shape

        Args:
          - clear (bool): whether to render empty spaces. Default=True
          - preserve_attrs: whether to keep colors and effects on the rendered cells,
                or replace all attributes with those in the current context
          - rect (Optional[Rect]): area to render. Defaults to whole text plane
          - target (Optional[Shape]): where to render to. Defaults to owner shape.
        """

        if "current_plane" not in self.__dict__:
            raise TypeError(
                "You must select a text plane to render - use .text[<size>].refresh()"
            )

        if target is None:
            target = self.owner
        data = self.plane

        if not rect:
            rect = Rect((0, 0), data.size)
        elif not isinstance(rect, Rect):
            rect = Rect(rect)
        with target.context as context:
            if preserve_attrs:
                context.color = TRANSPARENT
                context.background = TRANSPARENT
                context.effects = TRANSPARENT

            for pos in rect.iter_cells():
                self.blit(pos, target=target, clear=clear)
コード例 #12
0
    def __getitem__(self, pos):
        """Common logic to create ShapeViews from slices.

        Pixel data retrieving is implemented in the subclasses.
        """
        if isinstance(pos, Rect):
            roi = pos
        elif isinstance(pos, tuple) and isinstance(pos[0], slice):
            if any(pos[i].step not in (None, 1) for i in (0, 1)):
                raise NotImplementedError(
                    "Slice stepping not implemented for shapes")
            roi = Rect(*pos)
        else:
            return None
        return ShapeView(self, roi)
コード例 #13
0
    def ellipse(self, pos1, pos2=(), *, rel=()):
        """Draws an ellipse

        Args:
          - pos1 (Union[Rectangle, 2-tuple]): top-left coordinates
          - pos2 (Optional[2-tuple]): bottom-right limit coordinates. If not given, pass "rel" instead
          - rel (Optional[2-tuple]): (width, height) of rectangle. Ignored if "pos2" is given

        Public call to draw an ellipse using character blocks
        on the terminal.
        The color line is defined in the owner's context.color
        attribute. In the case of high-resolution drawing, the background color
        is also taken from the context.
        """
        pos1, pos2 = Rect(pos1, pos2, width_height=rel)

        pos2 -= (1, 1)

        return (self._empty_ellipse(pos1, pos2)
                if not self.context.fill else self._filled_ellipse(pos1, pos2))
コード例 #14
0
    def floodfill(self, pos, threshold=None):
        """Fills the associated target with context values, starting at seed "pos".

        Any different parameters for target pixels are considered boundaries
        "threshold" may be passed a guard callable which will take each target
        pixel, and should return "False" if it should be painted and "True"
        if it is a boundary.
        Args:
            - pos (V2|Sequence[2]): initial position
            - threshold (Optional[Callable[Pixel, Pixel, Pos]): callable - takes initial value at origin, and target pixel value.
                    Should return 'True' for boundary pixels, False if pixel is to be painted.
        The context attributes are used to fill the target area.
        """
        seed = self.get(pos)
        if threshold is None:
            threshold = lambda seed, target, pos: seed != target

        rect = Rect(self.size)

        fillable = set()
        visited = set()

        to_check = {
            pos,
        }

        while to_check:
            pos = to_check.pop()
            if pos in visited:
                continue
            visited.add(pos)
            if threshold(seed, self.get(pos), pos):
                continue
            fillable.add(pos)
            for direction in Directions:
                target = pos + direction
                if target in rect:
                    to_check.add(target)
        for pos in fillable:
            self.set(pos)
コード例 #15
0
 def owner_coords(self, rect, where=None):
     if not isinstance(rect, Rect):
         rect = Rect(rect)
     if not where:
         where = self.rect
     return Rect(where.c1 + rect.c1, width=rect.width, height=rect.height)
コード例 #16
0
def test_rect_constructor_with_expected_result(args, kwargs, expected):
    r = Rect(*args, **kwargs)
    assert r == Rect(*expected)
コード例 #17
0
def test_rect_constructor(args, kwargs):
    r = Rect(*args, **kwargs)
    assert r == Rect((10, 10), (20, 20))
コード例 #18
0
    a1.b = 5
    assert flag1 and not flag2 and flaguniversal
    flaguniversal = False
    flag1 = False
    b2.b = 23
    assert not flag1 and flag2 and flaguniversal


@pytest.mark.parametrize(["args", "kwargs"], [
    [[(10, 10), (20, 20)], {}],
    [[[10, 10], [20, 20]], {}],
    [[V2(10, 10), V2(20, 20)], {}],
    [[[10, 10, 20, 20]], {}],
    [[(10, 10, 20, 20)], {}],
    [[10, 10, 20, 20], {}],
    [Rect((10, 10), (20, 20)), {}],
    [[Rect((10, 10), (20, 20))], {}],
    [[], {
        "left_or_corner1": (10, 10),
        "top_or_corner2": (20, 20)
    }],
    [[(10, 10)], {
        "width_height": (10, 10)
    }],
    [[(10, 10)], {
        "width": 10,
        "height": 10
    }],
    [[(10, 10)], {
        "right": 20,
        "bottom": 20
コード例 #19
0
 def __init__(self, original, roi):
     self.original = original
     self.roi = Rect(roi)
コード例 #20
0
    def blit(self, pos, data, *, roi=None, color_map=None, erase=False):
        """Blits a blocky image in the associated screen at POS

        Args:
          - pos (Union[2-sequence, Rectangle]): top-left corner of where to blit, or a rectangle with extents to be blitted.
          - shape (Shape/string/list): Shape object or multi-line string or list of strings with shape to be drawn
          - roi (Optional[Rect]): (Region of interest) delimiting rectangle in source image(data) to be blitted.
                                 Defaults to whole image.
          - color_map (Optional mapping): palette mapping chracters in shape to a color
          - erase (bool): if True white-spaces are erased, instead of being ignored. Default is False. FIXME: under the Pixel model, erase is always True.

        Shapes return specialized Pixel classes when iterated upon -
        what is set on the screen depends on the Pixel returned.
        As of version 0.3dev, Shape class returns a pixel
        that has a True or False value and a foreground color (or no color) -
        support for other Pixel capabilities is not yet implemented.

        """
        from terminedia.image import Shape, PalettedShape, SKIP_LINE

        if not hasattr(self.context, "color_stack"):
            self.context.color_stack = []
        if not hasattr(self.context, "background_stack"):
            self.context.background_stack = []

        self.context.color_stack.append(self.context.color)
        self.context.background_stack.append(self.context.background)

        if isinstance(data, (str, list)):
            shape = PalettedShape(data, color_map)
        elif isinstance(data, Shape):
            shape = data
        else:
            raise TypeError(
                f"Unknown data argument passed to blit: {type(data)} instance")

        if isinstance(pos, Rect):
            extent = pos.width_height
            pos = pos.c1
        else:
            extent = None

        if roi is not None:
            roi = Rect(roi)
            shape = shape[roi]

        direct_pix = len(inspect.signature(self.set).parameters) >= 2

        ishape = iter(shape)
        while True:
            pixel_pos, pixel = next(ishape, (None, None))
            if pixel_pos is None:
                break
            target_pos = pos + pixel_pos
            if extent and (target_pos.x >= extent.x
                           or target_pos.y >= extent.y):
                ishape.send(SKIP_LINE)
                continue
            should_set = (
                pixel.capabilities.value_type == str and
                (pixel.value != EMPTY
                 or pixel.value == "." and pixel.capabilities.translate_dots)
                or pixel.capabilities.value_type == bool and pixel.value)
            if not should_set and not erase:
                continue
            if direct_pix:
                if (pixel.capabilities.has_foreground
                        and pixel.foreground == CONTEXT_COLORS
                        or pixel.capabilities.has_background
                        and pixel.background == CONTEXT_COLORS):
                    _cls = pixel.__class__
                    values = [pixel.value]
                    if pixel.capabilities.has_foreground:
                        values.append(pixel.foreground
                                      if pixel.foreground != CONTEXT_COLORS
                                      else self.context.color_stack[-1])
                    if pixel.capabilities.has_background:
                        values.append(pixel.background
                                      if pixel.foreground != CONTEXT_COLORS
                                      else self.context.background_stack[-1])
                    if pixel.capabilities.has_effects:
                        values.append(pixel.effects)

                    pixel = _cls(*values)
                self.set(target_pos, pixel)
            else:
                if pixel.capabilities.has_foreground:
                    if pixel.foreground == CONTEXT_COLORS:
                        self.context.color = self.context.color_stack[-1]
                    else:
                        self.context.color = pixel.foreground
                if pixel.capabilities.has_background:
                    if pixel.background == CONTEXT_COLORS:
                        self.context.background = self.context.background_stack[
                            -1]
                    else:
                        self.context.background = pixel.background

                if should_set:
                    self.set(target_pos)
                else:
                    self.reset(target_pos)

        self.context.color = self.context.color_stack.pop()
        self.context.background = self.context.background_stack.pop()
コード例 #21
0
    def _fast_render(self, data, rects=None, file=None):
        if file is None:
            file = sys.stdout
        if rects is None:
            rects = {Rect((0,0), data.size)}
        CSI = "\x1b["
        SGR = "m"
        MOVE = "H"
        last_pos = self.__class__.last_pos
        last_fg = last_bg = last_tm_effects = last_un_effects = None
        seen = set()
        for rect in sorted(rects):
            if not isinstance(rect, Rect):
                rect = Rect(rect)
            outstr = ""
            for y in range(rect.top, rect.bottom):
                for x in range(rect.left, rect.right):
                    if (x, y) in seen: continue
                    seen.add((x, y))
                    # Fast render just for full-4tuple values.
                    char, fg, bg, effects = data[x, y]
                    if effects != TRANSPARENT:
                        tm_effects = effects & TERMINAL_EFFECTS
                        un_effects = effects & UNICODE_EFFECTS
                    else:
                        tm_effects = un_effects = Effects.none

                    csi = False

                    if fg != last_fg and fg != TRANSPARENT:
                        outstr += CSI
                        csi = True
                        if fg == DEFAULT_FG:
                            outstr += "39"
                        else:
                            outstr += "38;2;{};{};{}".format(*fg)

                    if bg != last_bg and bg != TRANSPARENT:
                        if not csi:
                            outstr += CSI
                            csi = True
                        else:
                            outstr += ";"
                        if bg == DEFAULT_BG:
                            outstr += "49"
                        else:
                            outstr += "48;2;{};{};{}".format(*bg)

                    if tm_effects != last_tm_effects and effects != TRANSPARENT:
                        semic = ";"
                        if not csi:
                            outstr += CSI
                            semic = ""
                            csi = True

                        if last_tm_effects:
                            for effect in last_tm_effects:
                                if effect not in tm_effects:
                                    outstr += f"{semic}{effect_off_map[effect]}"
                                    semic = ";"
                        for effect in tm_effects:
                            outstr += f"{semic}{effect_on_map[effect]}"
                            semic = ";"

                    if csi:
                        outstr += "m"
                        last_fg = fg; last_bg = bg; last_tm_effects = tm_effects
                    if char is CONTINUATION:
                        # ensure two spaces for terminedia double-width chars -
                        # can possibly be made more efficient if run in a terminal
                        # that treat those correctly (not the case in current era konsole)
                        outstr += EMPTY
                    if char not in (TRANSPARENT, CONTINUATION):
                        if (x, y) != last_pos:
                            # TODO: relative movement?
                            outstr += CSI + f"{y + 1};{x + 1}H"
                        final_char = self.apply_unicode_effects(char, un_effects)
                        outstr += final_char

                        last_pos = (x + 1, y)

            # TODO: temporarily disable 'non-blocking' for stdout
            file.write(outstr); file.flush()

            self.__class__.last_pos = last_pos