Esempio n. 1
0
    def _process_to(self, index):

        if self._last_index_processed is None and index == 0:
            self.current_position = self.starting_point
        elif (self._last_index_processed is None
              or index != self._last_index_processed + 1):
            return self._reprocess_from_start(index)

        if self.locals.on_rendering_skipped_positions:
            skipped = self.locals.on_rendering_skipped_positions[:]
            self.locals.on_rendering_skipped_positions.clear()
        else:
            skipped = ()
        for mark_here, mark_origin in self.marks.get_full(
                index, self.current_position, skipped=skipped):
            mark_here.context = self.context
            mark_here.pos = self.current_position
            if mark_here.attributes or mark_here.pop_attributes:
                self._context_push(mark_here.attributes,
                                   mark_here.pop_attributes, mark_origin,
                                   index)
            if mark_here.moveto:
                mtx = mark_here.moveto[0]
                mty = mark_here.moveto[1]
                mtx = mtx if mtx is not RETAIN_POS else self.current_position.x
                mty = mty if mty is not RETAIN_POS else self.current_position.y
                self.current_position = V2(mtx, mty)
            if mark_here.rmoveto:
                self.current_position += V2(mark_here.rmoveto)
        self._last_index_processed = index
        return self.context
Esempio n. 2
0
    def bezier(self, pos1, pos2, pos3, pos4, *extra):
        """Draws a bezier curve given the control points

        Args:
            pos1 (2-sequence): Fist control point
            pos2 (2-sequence): Second control point
            pos3 (2-sequence): Third control point
            pos4 (2-sequence): Fourth control point
            extra Tuple[2-sequence]: n-sets of 3 more control points to keep drawing.
        """
        pos1 = V2(pos1)
        pos2 = V2(pos2)
        pos3 = V2(pos3)
        pos4 = V2(pos4)
        x, y = pos1

        t = 0
        step = 1 / (abs(pos4 - pos3) + abs(pos3 - pos2) + abs(pos2 - pos1))
        self.set((x, y))
        while t <= 1.0:

            x, y = (pos1 * (1 - t)**3 + pos2 * 3 * (1 - t)**2 * t + pos3 * 3 *
                    (1 - t) * t**2 + pos4 * t**3)

            self.set((round(x), round(y)))
            t += step
        if len(extra) >= 3:
            self.bezier(pos4, extra[0], extra[1], extra[2], *extra[3:])
Esempio n. 3
0
    def __init__(
        self, text, mark_sequence, text_plane=None, context=None, starting_point=None
    ):
        """
        Args:
          text (Sequence): the stream of characters to be rendered  - it can be a string or a list of 1-grapheme strings.
          mark_sequence (Mapping): A mappign with Mark objects. The keys either represent index positions on the text
            where the mark will be processed, or they can be at the special index "config" denoting marks
            that are to have their indexes processed according to other enviroment circunstances
            (like the current "tick" - and possibly 'current position')
            The value at each item can contain a single Mark or a of Markers.
          text_plane (terminedia.text.planes.TextPlane): area where the output is to be rendered
            on iterating. The Text object will be searched for aditional "Mark" objects that
            will compose the syle and position when encountered (they are less
            prioritary than the Marks passed in mark_sequence)
            If no Text object is given, the instance may still be iterated to retrieve
            a sequence of char, context and position - for example, when generating
            output directly to a tty.
          context (terminedia.Context): parent context. By default the context
          attached to the given text_plane is used
          starting_point: first position to be yielded when iteration starts (from which
            rules apply according to context.direction and others given by the matched
            "Mark" objects. Defaults to (0, 0)



        Helper class to render text that will both hold embedded style information,
        conveyed in "Mark" objects (with information like "at position 10, push foreground color 'red'"),
        and respect Mark objects embedded in the "text_plane" associanted rendering space.

        Style changes are all on top of a given "parent context"
        if any (otherwise, the text_plane context is used, or None)

        The rendering part include yielding the proper position of each
        rendering character,as contexts convey also
        text printing direction and marks can not only
        push a new printing direction, but also "teleport" the
        rendering point for the next character altogether.

        """
        self.text = text
        self.mark_sequence = mark_sequence
        self.parent_context = context
        self._last_index_processed = None
        self.context = Context()
        self.text_plane = text_plane
        self.starting_point = V2(starting_point) if starting_point else V2(0, 0)
        self.current_position = self.starting_point
        self._sanity_counter = 0
        self.locals = threading.local()
        if isinstance(text, GraphemeIter):
            self.cooked_text = text
        else:
            new_text = self.cooked_text = GraphemeIter(text)
            # adjust mark items to match graphemes instead of characters:
            sorted_old_keys = sorted(key for key in mark_sequence.keys() if isinstance(key, int))
            new_keys = {old_key: new_key for old_key, new_key in zip(
                sorted_old_keys, new_text.iter_cooked_indexes(sorted_old_keys)
            )}
            self.mark_sequence = {new_keys.get(old_key, old_key): value for old_key, value in mark_sequence.items()}
Esempio n. 4
0
    def load_data(self, file_or_path, size=None):
        """Will load data from an image file.
        Size parameter is ignored
        """
        if isinstance(file_or_path, PILImage.Image):
            img = file_or_path
        else:
            img = PILImage.open(file_or_path)
        if self.kwargs.get("auto_scale", True):
            scr = self.kwargs.get("screen", None)
            pixel_ratio = self.kwargs.get("pixel_ratio", 2)

            size = V2(scr.get_size() - (1, 1) if scr else (80, 12))
            img_size = V2(img.width, img.height)
            if size.x < img_size.x or size.y < img_size.y:
                ratio_x = size.x / img_size.x
                ratio_y = (size.y / img_size.y) * pixel_ratio
                if ratio_x > ratio_y:
                    size = V2(
                        size.x,
                        min((img_size.y * ratio_x / pixel_ratio), size.y - 1))
                else:
                    size = V2(img_size * ratio_y, size.y / pixel_ratio)

                img = img.resize(size.as_int, PILImage.BICUBIC)

        self.width, self.height = img.width, img.height

        if img.mode in ("L", "P", "I"):
            img = img.convert("RGB")
        elif img.mode in ("LA", "PA"):
            img = img.convert("RGBA")
        self.data = img
Esempio n. 5
0
    def load_data(self, file_or_path, size=None):
        """Will load data from an image file using PIL,

        Image is re-scaled to self.size if that is not None.
        """
        if isinstance(file_or_path, PILImage.Image):
            img = file_or_path
        else:
            img = PILImage.open(file_or_path)
        if size is not None:
            pixel_ratio = 1
            size = V2(size) - (1, 1)
            img_size = V2(img.width, img.height)
            if size.x < img_size.x or size.y < img_size.y:
                ratio_x = size.x / img_size.x
                ratio_y = (size.y / img_size.y) * pixel_ratio
                if ratio_x > ratio_y:
                    size = V2(
                        size.x,
                        min((img_size.y * ratio_x / pixel_ratio), size.y - 1))
                else:
                    size = V2(img_size * ratio_y, size.y / pixel_ratio)

                img = img.resize(size.as_int, PILImage.BICUBIC)

        self.width, self.height = img.width, img.height

        if img.mode in ("L", "P", "I"):
            img = img.convert("RGB")
        elif img.mode in ("LA", "PA"):
            img = img.convert("RGBA")
        self.data = img
Esempio n. 6
0
    def moveto(self, pos, file=None):
        """Writes ANSI Sequence to position the text cursor

        Args:
          - pos (2-sequence): screen coordinates, (0, 0) being the top-left corner.

        Please note that ANSI commands count screen coordinates from 1,
        while in this project, coordinates start at 0 to conform
        to graphic display expected behaviors
        """
        pos = V2(pos)
        if pos != (0, 0) and pos == self.__class__.last_pos:
            return
        if self.absolute_movement:
            self.CSI(f"{pos.y + 1};{pos.x + 1}H", file=file)
        else:
            if self.__class__.last_pos and pos.x == 0 and pos.y == self.__class__.last_pos.y + 1:
                self._print("\n", file=file)
            else:
                if not self.__class__.last_pos:
                    self.__class__.last_pos = V2(0,0)
                delta_x = pos.x - self.__class__.last_pos.x
                delta_y = pos.y - self.__class__.last_pos.y
                if delta_x > 0:
                    self.right(delta_x, file=file)
                elif delta_x < 0:
                    self.left(-delta_x, file=file)

                if delta_y > 0:
                    self.down(delta_y, file=file)
                elif delta_y < 0:
                    self.up(-delta_y, file=file)

        self.__class__.last_pos = pos
Esempio n. 7
0
    def bezier(self, pos1, pos2, pos3, pos4):
        """Draws a bezier curve given the control points

        Args:
            pos1 (2-sequence): Fist control point
            pos2 (2-sequence): Second control point
            pos3 (2-sequence): Third control point
            pos4 (2-sequence): Fourth control point
        """
        pos1 = V2(pos1)
        pos2 = V2(pos2)
        pos3 = V2(pos3)
        pos4 = V2(pos4)
        x, y = pos1

        t = 0
        step = 1 / (abs(pos4 - pos3) + abs(pos3 - pos2) + abs(pos2 - pos1))
        self.set((x, y))
        while t <= 1.0:

            x, y = pos1 * (1 - t)**3 + pos2 * 3 * (1 - t)**2 * t + pos3 * 3 * (
                1 - t) * t**2 + pos4 * t**3

            self.set((round(x), round(y)))
            t += step
Esempio n. 8
0
    def line(self, pos1, pos2, erase=False):
        """Draws a straight line connecting both coordinates.

        Args:
          - pos1 (2-tuple): starting coordinates
          - pos2 (2-tuple): ending coordinates
          - erase (bool): Whether to draw (set) or erase (reset) pixels.

        Public call to draw an arbitrary line using character blocks
        on the terminal.
        The color line is defined in the passed parameter or from the context.
        """

        op = self.reset if erase else self.set
        pos1 = V2(pos1)
        pos2 = V2(pos2)

        op(pos1)

        max_manh = max(abs(pos2.x - pos1.x), abs(pos2.y - pos1.y))
        if max_manh == 0:
            return
        step_x = (pos2.x - pos1.x) / max_manh
        step_y = (pos2.y - pos1.y) / max_manh
        total_manh = 0
        while total_manh < max_manh:
            pos1 += (step_x, step_y)
            total_manh += max(abs(step_x), abs(step_y))
            op(pos1.as_int)
Esempio n. 9
0
    def match(self, sequence):
        # The ANSI sequence for a mouse event in mode 1006 is '<ESC>[B;Col;RowM' (last char is 'm' if button-release)
        m = re.match(
            r"\x1b\[\<(?P<button>\d+);(?P<column>\d+);(?P<row>\d+)(?P<press>[Mm])",
            sequence)
        if not m:
            return None
        params = m.groupdict()
        pressed = params["press"] == "M"
        button = _button_map.get(int(params["button"]) & (~0x20), None)
        moving = bool(int(params["button"]) & 0x20)

        col = int(params["column"]) - 1
        row = int(params["row"]) - 1

        click_event = event = None

        # TBD: check for different buttons in press events and send combined button events
        if moving:
            event = Event(EventTypes.MouseMove,
                          pos=V2(col, row),
                          buttons=button)
        elif pressed:
            ts = time.time()
            event = Event(EventTypes.MousePress,
                          pos=V2(col, row),
                          buttons=button,
                          time=ts)
            self.last_press = (
                ts,
                button,
            )
        else:
            ts = time.time()
            event = Event(EventTypes.MouseRelease,
                          pos=V2(col, row),
                          buttons=button)
            if ts - self.last_press[
                    0] < self.CLICK_THRESHOLD and button == self.last_press[1]:
                Event(EventTypes.MouseClick,
                      pos=V2(col, row),
                      buttons=button,
                      time=ts)
                if ts - self.last_click[
                        0] < self.DOUBLE_CLICK_THRESHOLD and button == self.last_click[
                            1]:
                    Event(EventTypes.MouseDoubleClick,
                          pos=V2(col, row),
                          buttons=button,
                          time=ts)
                self.last_click = (
                    ts,
                    button,
                )

        return event
Esempio n. 10
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]
Esempio n. 11
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
Esempio n. 12
0
    def __init__(self):
        self.active_unicode_effects = Effects.none
        self.__class__.last_pos = V2(0, 0)
        self.next_pos = V2(0, 0)

        self.current_foreground = None
        self.current_background = None
        self.current_effects = None
        self.next_foreground = Color((0, 0, 0))
        self.next_background = Color((255, 255, 255))
        self.next_effects = Effects.none

        self.tag_is_open = False
Esempio n. 13
0
    def clear(self, wet_run=True):
        """Resets internal data, context parameters and clears the screen

        Args:
          - wet_run (bool): Whether to physically clear the screen or not

        Resets internal data and context parameters. The "self.data" and "self.color_data"
        structures are where the current character and attributes for each position are kept
        as characters actually just printed on the terminal can't be "read" back.
        Context foreground, background and direction are reset.

        In default operation, commands to clear the actual terminal and hide the cursor
        is also issued - the ``.clear_screen`` attribute controls that if ``.clear`` is being
        called as part of entering the screen context.

        """
        self.context.last_pos = V2(0, 0)
        self.__class__.last_color = None
        self.__class__.last_background = None
        with self.lock:
            if wet_run:
                self.commands.clear()
                self.data.clear()
            else:
                self.data.clear(transparent=True)
            self.data.dirty_set()
            self.commands.cursor_hide()
Esempio n. 14
0
    def __init__(self, attributes=None, pop_attributes=None, moveto=None, rmoveto=None, color=None, foreground=None, background=None, effects=None, direction=None, transformer=None, new_line=None):
        self.attributes = attributes or {}
        if isinstance(pop_attributes, (set, Sequence)):
            pop_attributes = {name: None for name in pop_attributes}
        self.pop_attributes = pop_attributes
        self.moveto = moveto
        self.rmoveto = rmoveto
        for parameter in "color foreground background effects direction transformer new_line".split():
            if locals()[parameter] is None:
                continue
            value = locals()[parameter]
            if parameter == "color": parameter = "foreground"
            if parameter == "transformer": parameter = "pretransformer"
            if parameter in ("foreground", "background") and not isinstance(value, Color):
                value = Color(value)
            if parameter == "effects" and not (isinstance(value, Effects) or value is TRANSPARENT):
                sep = "," if "," in value else "|" if "|" in value else " "
                effects = [e.strip() for e in value.split(sep) if e]
                value = Effects.none
                for effect in effects:
                    value |= Effects.__members__[effect.lower()]
            if parameter == "direction" and isinstance(value, str):
                value = V2(value)

            self.attributes[parameter] = value
        self.affects_text_flow = bool(self.moveto or self.rmoveto or self.attributes.get("direction") or new_line)
Esempio n. 15
0
    def draw_border(self,
                    transform=_bordersentinel,
                    context=None,
                    pad_level=1):
        """Draws an existing border, without changing the shape pattern
        call this just to redraw the border; A new border should be created by
        calling "add_border"
        """
        if transform is _bordersentinel:
            transform = getattr(self, "_last_border_transform", None)
        elif transform != None:
            self._last_border_transform = transform

        size = self.size + (1, 1) * pad_level * 2 + (1, 1)

        border_shape = shape(size)

        if context:
            border_shape.context = context
        else:
            border_shape.context = self.owner.context

        with border_shape.context as context:
            border_shape.draw.rect((0, 0), (size))
            if transform:
                context.transformers.append(transform)
            self.owner.draw.blit(
                V2(self.pad_left, self.pad_top) - (pad_level, pad_level),
                border_shape)
Esempio n. 16
0
    def bake(self, shape, target=None, offset=(0, 0)):
        """Apply the transformation stack for each pixel in the given shape

        Args:
          - shape: Source shape object to be processed
          - target [Optional]: optional target where final pixels are blitted into.
                If target is not given, 'shape' is modified inplace. Defaults to None.
          - offset: pixel-offset to blit the data to. Most useful with the target
          option.

        Returns:
          the affected Shape object
        """
        from terminedia.image import FullShape

        if target:
            source = shape
        else:
            # Creates a copy of all data channels, sans sprites neither transformers:
            source = FullShape.promote(shape)
            target = shape

        # if target is shape, bad things will happen for some transformers - specially Kernel based transforms

        offset = V2(offset)
        for pos, pixel in source:
            target[pos + offset] = self.process(source, pos, pixel)
        return target
Esempio n. 17
0
 def build_args(channel, signature):
     nonlocal transformer, pixel, values, ch_num
     args = {}
     for parameter in signature:
         if parameter == "self":
             args["self"] = transformer
         elif parameter == "value":
             args["value"] = values[ch_num]
         elif parameter in Transformer.channels and parameter != "pixel":
             args[parameter] = getattr(pixel, parameter if parameter != "char" else "value")
         elif parameter == "pos":
             args["pos"] = V2(pos)
         elif parameter == "pixel":
             args["pixel"] = pixel
         elif parameter == "source":
             args["source"] = source
         elif parameter == "tick":
             args["tick"] = get_current_tick()
         elif parameter == "context":
             args["context"] = source.context
         elif hasattr(transformer, parameter):
             # Allows for custom parameters that can be made available
             # for specific uses of transformers.
             # (ex.: 'sequence_index' for transformers inlined in rich-text rendering)
             args[parameter] = getattr(transformer, parameter)
     return args
Esempio n. 18
0
    def moveto(self, pos, file=None):
        """Writes ANSI Sequence to position the text cursor

        Args:
          - pos (2-sequence): screen coordinates, (0, 0) being the top-left corner.

        Please note that ANSI commands count screen coordinates from 1,
        while in this project, coordinates start at 0 to conform
        to graphic display expected behaviors
        """
        pos = V2(pos)
        if pos != (0, 0) and pos == self.__class__.last_pos:
            return
        # x, y = pos
        self.CSI(f'{pos.y + 1};{pos.x + 1}H', file=file)
        self.__class__.last_pos = V2(pos)
Esempio n. 19
0
    def __init__(self, size=(), clear_screen=True):
        if not size:
            #: Set in runtime to a method to retrieve the screen width, height.
            #: The class is **not** aware of terminal resizings while running, though.
            self.get_size = lambda: V2(os.get_terminal_size())
            try:
                size = self.get_size()
            except OSError as error:
                if error.errno == 25:
                    logger.error(
                        "This terminal type does not allow guessing screen size."
                        "Pass an explicit (cols, rows) size when instantiating {self.__class__}"
                    )
                raise
        else:
            self.get_size = lambda: V2(size)

        #: Namespace to configure drawing and printing color and other parameters.
        #: Currently, the attributes that are used from here are
        #: ``color``, ``background``, ``direction``, ``effects`` and ``char``.
        self.context = Context()

        #: Namespace for drawing methods, containing an instance of the :any:`Drawing` class
        self.draw = Drawing(self.set_at, self.reset_at, self.get_size,
                            self.context)
        self.width, self.height = self.size = size

        #: Namespace to allow high-resolution drawing using a :any:`HighRes` instance
        #: One should either use the public methods in HighRes or the methods on the
        #: :any:`Drawing` instance at ``Screen.high.draw`` to do 1/4 block pixel
        #: manipulation.
        self.high = HighRes(self)
        self.braille = HighRes(self,
                               block_class=BrailleChars,
                               block_width=2,
                               block_height=4)

        self.text = terminedia.text.Text(self)

        #: Namespace for low-level Terminal commands, an instance of :any:`JournalingScreenCommands`.
        #: This attribute can be used as a context manager to group
        #: various screen operations in a single block that is rendered at once.
        self.commands = JournalingScreenCommands()
        self.clear_screen = clear_screen
        self.data = FullShape.new((self.width, self.height))
        # Synchronize context for data and screen painting.
        self.data.context = self.context
Esempio n. 20
0
    def moveto(self, pos, file=None):
        """Set internal state so that next character rendering is at the new coordinates;

        Args:
          - pos (2-sequence): screen coordinates, (0, 0) being the top-left corner.

        """
        self.next_pos = V2(pos)
Esempio n. 21
0
    def __setitem__(self, pos, value):
        """Writes character data at pos

        Args:
          - pos (2-sequence): coordinate where to set character
          - value (length 1 string): Character to set.

        This is mostly used internally by all other drawing and printing methods, although
        it can be used directly, by using Python's object-key notation with ``[ ]`` and assignment.
        All text or graphics that go to the terminal *are directed through this method*
        - it is a "single point" where all data is sent - and any user code that writes to
        the terminal with a Screen class should use this method.
        Valus set on Screen are imediately updated on the screen. To issue a command
        batch that should be updated at once, use the Screen.commands attribute as
        a context manager:  (`with sc.commands: ...code with lots of drawing calls ... `)

        If it is desired to  draw/write in an in-memory buffer in order
        to update everything at once, one can issue the drawing class to affect
        the Screen.data attribute instead of Screen directly. The Screen contents
        can be updated by calling Screen.update afterwards.  `Screen.data` is a
        terminedia.FullShape object with a .draw, .high and .text interfaces
        offering the same APIs available to Screen.

        """

        if isinstance(value, str) and len(value) > 1:
            # Redirect strings through the text machinery.
            # it will separate each char in a cell, take care
            # of double chars, embedded attributes and so on
            self.text[1][pos] = value
            return

        cls = self.__class__
        with self.lock:
            # Force underlying shape machinnery to apply context attributes and transformations:
            if value != _REPLAY:
                self.data[pos] = value
            pixel = self.data[pos]

            update_colors = (cls.last_color != pixel.foreground
                             or cls.last_background != pixel.background
                             or cls.last_effects != pixel.effects)

            if self.root_context.interactive_mode and time.time(
            ) - self._last_setitem > 0.1:
                update_colors = True
                self.commands.__class__.last_pos = None
                self._last_setitem = time.time()

            if update_colors:
                colors = pixel.foreground, pixel.background, pixel.effects
                self.commands.set_colors(*colors)
                cls.last_color = pixel.foreground
                cls.last_background = pixel.background
                cls.last_effects = pixel.effects
            if pixel.value not in (CONTINUATION, TRANSPARENT):
                self.commands.print_at(pos, pixel.value)
                self.context.last_pos = V2(pos)
Esempio n. 22
0
 def __setitem__(self, pos, value):
     """
     Values set for each pixel are: character - only spaces (0x20) or "non-spaces" are
     taken into account for PalettedShape
     """
     pos = V2(pos)
     self.dirty_mark_pixel(pos)
     type_ = self.PixelCls.capabilities.value_type
     self.raw_setitem(pos, type_(value))
Esempio n. 23
0
    def _clear_owner(self):
        # clear the inner contents of the owner when reflowing text, respecting padding
        size = self.size

        corner1 = V2(self.pad_left, self.pad_top)
        corner2 = corner1 + size

        self.owner[corner1.x:corner2.x,
                   corner1.y:corner2.y].draw.fill(char=" ")
Esempio n. 24
0
    def concat(self, *others, direction=Directions.RIGHT, **kwargs):
        """Concatenates two given shapes side by side into a larger shape.

        Args:
          - other (Shape): Other shape to be concatenated.
          - direction (V2): Side which will be "enlarged" and on which the other shape
                will be placed. Most usefull values are Directions.RIGHT and Directions.DOWN
          - **kwargs: are passed down to the "new" constructor of the resulting shape.

        Creates a new shape combining two or more other shapes. If Shape _allowed_types differ,
        the logic in Drawing.blit will try to cast pixels to the one used in self.
        """
        shapes = (self, ) + others

        direction = V2(direction)

        h_size = abs(direction.x) * sum(s.width for s in shapes)
        v_size = abs(direction.y) * sum(s.height for s in shapes)
        new_size = V2(
            max(h_size, max(s.width for s in shapes)),
            max(v_size, max(s.height for s in shapes)),
        )

        new_shape = self.__class__.new(new_size, **kwargs)

        d = direction
        offset = V2(0 if d.x >= 0 else new_size.x,
                    0 if d.y >= 0 else new_size.y)

        # blit always take the top-left offset corner
        # so, depending on direction of concatenation,
        # offset have to be computed before or after blitting.
        for s in shapes:
            offset += (
                int(s.width * d.x if d.x < 0 else 0),
                int(s.height * d.y if d.y < 0 else 0),
            )
            new_shape.draw.blit(offset, s)
            offset += (
                int(s.width * d.x if d.x >= 0 else 0),
                int(s.height * d.y if d.y >= 0 else 0),
            )

        return new_shape
Esempio n. 25
0
    def _process_to(self, index):

        if self._last_index_processed is None and index == 0:
            self.current_position = self.starting_point
        elif (
            self._last_index_processed is None
            or index != self._last_index_processed + 1
        ):
            return self._reprocess_from_start(index)

        if self.locals.on_rendering_skipped_positions:
            skipped = self.locals.on_rendering_skipped_positions[:]
            self.locals.on_rendering_skipped_positions.clear()
        else:
            skipped = ()
        for mark_here, mark_origin in self.marks.get_full(index, self.current_position, skipped=skipped):
            mark_here.context = self.context
            mark_here.pos = self.current_position
            if mark_here.attributes or mark_here.pop_attributes:
                self._context_push(mark_here.attributes, mark_here.pop_attributes, mark_origin, index)
            if mark_here.moveto:
                mtx = mark_here.moveto[0]
                mty = mark_here.moveto[1]
                mtx = (
                    self.current_position.x
                        if mtx is RETAIN_POS else
                    mtx.evaluate(self.text_plane.size)
                        if isinstance(mtx, RelativeMarkIndex) else
                    mtx
                )
                mty = (
                    self.current_position.y
                        if mty is RETAIN_POS else
                    mty.evaluate(self.text_plane.size)
                        if isinstance(mty, RelativeMarkIndex) else
                    mty
                )
                self.current_position = V2(mtx, mty)
            if mark_here.attributes.get("new_line", None):
                self.current_position, self.context.direction = self.marks.new_line_start(self.current_position, self.context.direction)
            if mark_here.rmoveto:
                self.current_position += V2(mark_here.rmoveto)
        self._last_index_processed = index
        return self.context
Esempio n. 26
0
    def at_parent(self, pos):
        """Get the equivalent, rounded down, coordinates, at the parent object.

        Args:
          - pos (2-sequence): screen coordinates, (0, 0) being the top-left corner.

        Returns:
          - V2 object with the equivalent object at the parent space.
        """
        return V2(pos[0] // self.block_width, pos[1] // self.block_height)
Esempio n. 27
0
def window_change_handler(signal_number, frame):
    """Called as a signal to terminal-window resize

    It is set as a handler on terminedia.Screen instantiation,
    and will automatically add window-resize events on
    terminedia event system.
    """

    new_size = V2(os.get_terminal_size())
    Event(EventTypes.TerminalSizeChange, size=new_size, dispatch=True)
Esempio n. 28
0
 def _get_drawing(self):
     from terminedia.drawing import Drawing
     # The 'type(self).__setitem__` pattern ensures __setitem__ is called on the proxy,
     # not on the proxied object.
     return Drawing(
         set_fn=lambda pos: type(self).__setitem__(self, pos, self.context.
                                                   char),
         reset_fn=lambda pos: type(self).__setitem__(self, pos, EMPTY),
         size_fn=lambda: V2(self.width, self.height),
         context=self.context)
Esempio n. 29
0
    def abs_get(self, index, default=None):
        """Usually negative values for indexes coordinates will subtract those
        values from the width or height of the textplane, so "-1" is actually
        at "WIDTH - 1" == last column.

        But sometimes actually need to get the mark
        at the [-1] hard index. When that is needed, the mark should
        be retrieved through here, rather than through __getitem__ directly.

        For setting, since negative indexes will not depend on the
        width and height of the text plane (they are on the opposite
        side of these dynamic indexes: column -1 is the equivalent on the left-side
        of column WIDTH + 1 on the right side), assignment of marks
        can be done directly on the "self.data" attribute
        """
        if index[0] < 0:
            index = V2(index[0] - self.text_plane.width, index[1])
        if index[1] < 0:
            index = V2(index[0], index[1] - self.text_plane.height)
        return self.get(index, default)
Esempio n. 30
0
    def _render_using_screen(self, output, backend):
        from terminedia.screen import Screen

        if output is None:
            file = StringIO()
        else:
            file = output
        sc = Screen(size=V2(self.width, self.height), backend=backend)
        if backend == "ANSI":
            # generate a relocatable image
            sc.commands.__class__.last_pos = V2(0, 0)
            sc.commands.absolute_movement = False
        # Starts recording all image operations on the internal journal
        sc.commands.__enter__()
        sc.blit((0, 0), self)
        # Ends journal-recording, but without calling __exit__
        # which does not allow passing an external file.
        sc.commands.stop_journal()
        # Renders all graphic ops as ANSI sequences + unicode into file:
        sc.commands.replay(output)