<filename>lib/ShellGUI_Core.py
"""
How ShellGUI works:
'Canvas' is a class representing the GUI area drawn with ASCII method.
Each GUI element is a class inherited from 'Component' class. They need to be drawn onto a canvas.
X and Y positions are specified inside each GUI element's class;
Z position is specified inside Canvas class, within a dictionary.
"""

if __name__ == "__main__":
    # MAJOR +1 represents an added function.
    __MAJOR = 1
    # MINOR +1 represents a change in existing function(s) within the current MAJOR.
    __MINOR = 0 + 1

    __info = """This file contains the module 'ShellGUI_Core', used to create a shell-based rendering engine.
To use this module in another project, include this file inside the project's directory."""

    print("========================================================")
    print("ShellGUI_Core.py version ", __MAJOR, ".", __MINOR, sep='', end='\n\n')
    print(__info)
    print("========================================================\n")
    input("Press enter to continue...")



#========================Imports========================

# Used by: Canvas.draw()
import time

from lib.Utils import *
from lib.Color import Color

from lib.EventSystem.FocusLost import FocusLostEventListener
from lib.EventSystem.KeyPress import *
from lib.EventSystem.OnFocus import OnFocusEventListener



#========================Borders========================

class BorderTypes:
    """
    An 'enum' containing border types for drawing borders around components.
    """

    NoBorder = ("", "", "", "", "", "", "", "")
    
    BlankBorder = (" ", " ", " ", " ", " ", " ", " ", " ")

    ThinBorder = ("┌", "─", "┐", "│", "│", "└", "─", "┘")

    ThinHorizontalBorder = ("", "─", "", "", "", "", "─", "")

    ThinVerticalBorder = ("", "", "", "│", "│", "", "", "")

    ThinUnderline = ("", "", "", "", "", "", "─", "")

    ThinOverline = ("", "─", "", "", "", "", "", "")

    BlockBorder = ("█", "▀", "█", "█", "█", "▀", "▀", "▀")

    BlockHorizontalBorder = ("", "▀", "", "", "", "", "▀", "")

    BlockVerticalBorder = ("", "", "", "█", "█", "", "", "")

    BlockOverline = ("", "▀", "", "", "", "", "", "")

    BlockUnderline = ("", "", "", "", "", "", "▀", "")


def addborder(value: str, bordertype=BorderTypes.ThinBorder):
    """
    Adds a four-sided ASCII border around a component.

    Args:
        value: The string representation of the component's layout. Use <component>.value() to get.

    Returns:
        The component's value with a border, in string
    """

    # If the text is empty
    if value == "":
        value = " "
    buffer: list = str(value).splitlines(False)
    width: int = len(LongestFromList(buffer))

    # Insert side borders
    for lineindex in range(0, len(buffer)):
        linelength = len(buffer[lineindex])
        if linelength < width:
            # The line is not long enough. Add spaces after the line
            buffer[lineindex] = buffer[lineindex] + " "*(width-linelength)

        buffer[lineindex] = bordertype[3] + buffer[lineindex] + bordertype[4]

    # Insert top-and-bottom borders with corner pieces
    buffer = [bordertype[0] + bordertype[1] * width + bordertype[2]] + \
        buffer + \
        [bordertype[5] + bordertype[6] * width + bordertype[7]]

    # Delete empty horizontal borders
    if buffer[0] == "":
        del buffer[0]
    if buffer[-1] == "":
        del buffer[-1]


    return "\n".join(buffer)



#========================Color list========================

class SystemColors:

    Default = Color(255, 255, 255)
    WindowBorder = Color(96, 96, 96)

    Hyperlink = Color(6, 69, 173)
    VisitedHyperlink = Color(11, 0, 128)



#========================Canvas class========================

class Canvas:

    def __init__(self, width: int, height: int, bordertype=BorderTypes.BlockBorder):

        # Dictionary to store all component
        self.__elem = {}

        # OPTIONS
        self.Width = int(width)
        self.Height = int(height)

        self.Border = bordertype


    def add(self, component, z_pos: int=-1):
        # We must first check if 'component' passes a subclass of class 'Component'
        if not issubclass(type(component), Component):
            raise TypeError("Argument 'component' is not an implementation of class 'Component'.")
        if z_pos == -1:
            self.__elem[len(self.__elem)] = component
        else:
            self.__elem[z_pos] = component


    def setzpos(self, z_pos, new_z_pos):
        if z_pos in self.__elem.keys():
            self.__elem[new_z_pos] = self.__elem[z_pos]
            del self.__elem[z_pos]


    def remove(self, z_pos: int):
        if z_pos in self.__elem.keys():
            del self.__elem[z_pos]


    def draw(self, delay: float=0, hideoverflown: bool=False, color: str='default'):
        """
        Draws/renders the current canvas with its components.

        Args:
            delay: A float indicating the delay, in seconds, before rendering starts.
            hideoverflown: Boolean indicating whether to omit rendering any component that is partially out of bounds.
            color: The color of the canvas. WILL OVERRIDE ANY CUSTOM COMPONENT COLOR (if not set to 'default')!

        """

        # draw elements inside __elem dictionary.
        # __elem dictionary has the following structure: ([z-pos], ([object to draw], [focus order]))

        # Delay parameter
        if delay > 0:
            time.sleep(delay)

        # This is the current ASCII frame to render.
        frame = (" "*self.Width + "\n")*(self.Height-1) + (" "*self.Width)
        # This buffer serves to write to individual lines
        framebuffer = frame.split(sep="\n")

        # Below for block writes individual component to 'framebuffer'
        # We need to write components from the smallest z-pos to the largest, hence the usage of range()
        for index in self.__elem.keys():
            comp: Component = self.__elem[index]
            # To write a component onto the frame, we need to separate each line and store the result in a list.
            compbuffer: list = comp.value().split(sep="\n")

            # Testing if the component overflows for optional parameter 'hideoverflown'
            if hideoverflown:
                if len(LongestFromList(compbuffer)) > (self.Width - comp.X):
                    # Object overflows in the x-axis, discard.
                    # Note: using Utils.py's LongestFromList() will rule out any inconsistent 'Component' class's value() implementation.
                    continue
                if len(compbuffer) > (self.Height - comp.Y):
                    # Object overflows in the y-axis, discard.
                    continue

            # Testing if the current component is outside of canvas area:
            if comp.X >= self.Width and comp.Y >= self.Height:
                # Object entirely outside of canvas, discard.
                continue

            # Drawing 'comp' inside 'framebuffer'
            for lineindex in range(0, len(compbuffer)):
                if comp.Y + lineindex >= len(framebuffer) or comp.Y + lineindex < 0:
                    # This line is out of bounds. Do not draw.
                    continue
                # Convert target 'framebuffer' line into a list of characters with list(str)
                linebuffer: list = list(framebuffer[comp.Y + lineindex])
                # Convert target 'compbuffer' line into a list of characters
                complinebuffer: list = list(compbuffer[lineindex])
                # This will replace the line with the current component's content without overflowing.
                #linebuffer[comp.X :] = complinebuffer[0 : len(linebuffer)-comp.X]      - produces undesirable result
                linebuffer[comp.X :] = complinebuffer
                # Update the target 'framebuffer' line with the new one
                framebuffer[comp.Y + lineindex]: str = "".join(linebuffer)

            for i in range(0, len(framebuffer)):
                # Adds space for lines that do not match canvas's width
                if len(framebuffer[i]) < self.Width:
                    framebuffer[i] += " " * (self.Width - len(framebuffer[i]))
                # Cuts overflown lines that do not match canvas's width
                if len(framebuffer[i]) > self.Width:
                    framebuffer[i] = framebuffer[i][0:self.Width]

        # Reassemble from from framebuffer
        frame = "\n".join(framebuffer)

        # Adding borders to current frame string
        frame = addborder(frame, self.Border)

        WriteShell(frame, end="\n", Color=color)

    def ColorPrinter(self, , delay: float=0, hideoverflown: bool=False, color: str='default'):




# ========================GUI class========================

class GUI:
    """
    Class responsible for the creation and maintenance of a canvas and its components.
    """

    #====================Magic methods====================

    def __init__(self, width: int = 100, height = 25, bordertype = BorderTypes.NoBorder):
        self.width = width
        self.height = height
        self.bordertype = bordertype

        self.Focus = 0 # focuses on first component

        # UX interactions
        self._inputkey = KeyCodes.Null
        self._inputkeystr = KeyCodes.tostring(KeyCodes.Null)
        self._userinput = KeyPressEventHandler(self._keypressdetector)

    #====================Properties====================

    @property
    def active(self):
        """
        Returns the index of the child component currently receiving focus.

        Returns:
            the index of the component to focus in integer
        """
        return self._active
    @active.setter
    def active(self, value: bool):
        """
        Focuses on a child component by its index.

        Args:
            value: the index of the component to focus in integer
        """
        self._active = bool(value)
        if bool(value):
            self._run()

    @property
    def focus(self):
        """
        Returns the index of the child component curently receiving focus.

        Returns:
            the index of the component to focus in integer
        """
        return self._focusindex
    @focus.setter
    def focus(self, value: int):
        """
        Focuses on a child component by its index.

        Args:
            value: the index of the component to focus in integer
        """
        if int(value) != self._focusindex:

            self._focusindex = int(value)

    @property
    def height(self):
        """
        Returns the width, in characters, of the GUI.

        Returns:
            an integer representing the height of the GUI
        """
        return self._height
    @height.setter
    def height(self, value: int):
        """
        Specifies the height, in characters, of the GUI.

        Args:
            value: an integer specifying the height of the GUI
        """
        self._height = value

    @property
    def width(self):
        """
        Returns the width, in characters, of the GUI.

        Returns:
            an integer representing the width of the GUI
        """
        return self._width
    @width.setter
    def width(self, value: int):
        """
        Specifies the width, in characters, of the GUI.

        Args:
            value: an integer specifying the width of the GUI
        """
        self._width = value

    #====================UX====================

    def _keypressdetector(self, args: KeyPressEventArgs):
        """
        Function delegate used to detect user key press input

        Args:
            sender:
            args:
        """
        self._inputkey = args.Key
        self._inputkeystr = KeyCodes.tostring(args.Key, args.Key2)

    def _run(self):
        """
        Begins execution of the GUI thread.
        Controlled internally by the GUI class.
        """



#========================Component class========================

class Component:

    #====================Magic methods====================

    def __init__(self, location, width: int = 0, height: int = 0):
        self.width = width
        self.height = height
        self.location = location

        self._focused = False


        self.handledinput = False       # This variable determines whether the GUI should transmit input to the component

        self.FocusLost = FocusLostEventListener()
        self.KeyPress = KeyPressEventListener()
        self.OnFocus = OnFocusEventListener()

    def __eq__(self, other):
        return str(self) == str(other)

    def __repr__(self):
        return self.value()

    #====================Methods====================

    def value(self):
        """
        Each component should be a string which, when printed, yields a rectangular footprint.

        Returns:
            A string representing the component.
        """
        return ""

    #====================Properties====================

    @property
    def focused(self):
        """
        Whether the component is receiving focus.
        """
        return self._focused
    @focused.setter
    def focused(self, value: bool):
        """
        Notifies this component's OnFocus event listener when it receives focus.
        Notifies this component's FocusLost event listener when focus is lost.
        """
        if value:
            self._focused = True
            self.OnFocus.notify(sender=self)
        else:
            self._focused = False
            self.FocusLost.notify(sender=self)

    @property
    def height(self):
        """
        Returns the width, in characters, of the UI.

        Returns:
            an integer representing the height of the UI
        """
        return self._height
    @height.setter
    def height(self, value: int):
        """
        Specifies the height, in characters, of the UI.

        Args:
            value: an integer specifying the height of the UI
        """
        self._height = int(value)

    @property
    def location(self):
        """
        The location of the component inside the UI.

        Returns:
            a tuple specifying the component's (x, y) location inside the UI
        """
        return self._location
    @location.setter
    def location(self, value):
        """
        Sets the location of the component inside the UI.

        Args:
            value: a tuple specifying the component's (x, y) location inside the UI
        """
        self._location = (value[0], value[1])

    @property
    def width(self):
        """
        Returns the width, in characters, of the UI.

        Returns:
            an integer representing the width of the UI
        """
        return self._width
    @width.setter
    def width(self, value: int):
        """
        Specifies the width, in characters, of the UI.
        Set to 0 for

        Args:
            value: an integer specifying the width of the UI
        """
        self._width = int(value)



#========================Version History========================

# 1.0
"""
    Initial Release

    Additions
    ---------
        -BordeeTypes class
        -addborder(value: str, bordertype=BorderTypes.ThinBorder, vertical=True, horizontal=True)
        -Canvas class
            -__init__(self, width: int, height: int, horizontal_borders=True, vertical_borders=False, bordertype=BorderTypes.BlockBorder)
            -add(self, component, z_pos: int=-1)
            -setzpos(self, z_pos, new_z_pos)
            -remove(self, z_pos: int)
            -draw(self, delay: float=0, hideoverflown: bool=False, color: str='default')
                -Currently the option to color the canvas is limited: can only color the whole canvas+components altogether, instead of simply the canvas's background
            -Canvas is the drawing board on which Components are added
        -Component class
            -__init__(self, x, y)
            -value(self)
        -Label(Component) class
            -__init__(self, x, y, text, showborder=False, bordertype=BorderTypes.ThinBorder, length=-1, overflowindicator="…")
            -value(self)
            -Can act not only as a label for simple lines or paragraphs, but also as a textbox, a checkbox, or a button
        -Illustration(Component) class
            -__init__(self, x, y, ascii, border=False)
            -A dud/foobar for later implementation of a drawing component
            -Will be used to convert external images into colored ASCII drawings
    To-Do
    -----
        -Add a function to superpose two different strings into one, with option to specify coloring for each string component.
        -Implement a GUI system to automatically control canvas drawing and offer additional components that are tied with the event system.
            -Most likely needs to be inside a separate Python file, as this system would require dependencies of EventSystem.py.
                -Will need to rename this file as ShellGUIbase.py, and migrate Label and Illustration classes to the new ShellGUI_Core.py file.
"""

# 1.1
"""
    Additions and changes related to borders

    Additions
    ---------
        -Added Version History at the end of ShellGUI_Core.py
        -Added more border types as tuple constants in BorderTypes class (under the new border format)
            -NoBorder, ThinHorizontalBorder, ThinVerticalBorder, ThinUnderline, ThinOverline, BlockHorizontalBorder, BlockVerticalBorder, BlockOverline, BlockUnderline
    
    Changes
    -------
        -Changed border structure
            -Previously the structure is specified as ([horizontal piece], [vertical piece], [upper left corner], [upper right corner], [lower left corner], [lower right corner])
            -Changed to ([ul corner], [upper horizontal piece], [ur corner], [left vertical piece], [right vertical piece], [ll corner], [lower horizontal piece], [lr corner])
            -This change allows larger diversity in the stylistical types of borders 
        -Changed addborder() mechanism
            -To comply with the new border structure
            -Will now also omit blank horizontal border lines (e.g. for ThinUnderline border type)
        -Signatures for these functions/methods are changed to comply with the new border structure
            -addborder(value: str, bordertype=BorderTypes.ThinBorder, vertical=True, horizontal=True) changed to addborder(value: str, bordertype=BorderTypes.ThinBorder)
            -Canvas.__init__(self, width: int, height: int, horizontal_borders=True, vertical_borders=False, bordertype=BorderTypes.BlockBorder)
                ->Changed to Canvas.__init__(self, width: int, height: int, bordertype=BorderTypes.BlockBorder)
    Bug Fixes
    ---------
        -
"""

# 2.0
"""
    Refactored ShellGUI.py into two separate modules: ShellGUI_Core.py and ShellGUI_Forms.py
    This separation permits better modular forms support without encumbering the core ASCII rendering engine.
    
    Additions
    ---------
        -
    
    Changes
    -------
        -Removed class 
    
    Bug Fixes
    ---------
        -
"""

<filename>lib/ShellGUI_Core.py
"""
How ShellGUI works:
'Canvas' is a class representing the GUI area drawn with ASCII method.
Each GUI element is a class inherited from 'Component' class. They need to be drawn onto a canvas.
X and Y positions are specified inside each GUI element's class;
Z position is specified inside Canvas class, within a dictionary.
"""

if __name__ == "__main__":
    # MAJOR +1 represents an added function.
    __MAJOR = 1
    # MINOR +1 represents a change in existing function(s) within the current MAJOR.
    __MINOR = 0 + 1

    __info = """This file contains the module 'ShellGUI_Core', used to create a shell-based rendering engine.
To use this module in another project, include this file inside the project's directory."""

    print("========================================================")
    print("ShellGUI_Core.py version ", __MAJOR, ".", __MINOR, sep='', end='\n\n')
    print(__info)
    print("========================================================\n")
    input("Press enter to continue...")



#========================Imports========================

# Used by: Canvas.draw()
import time

from lib.Utils import *
from lib.Color import Color

from lib.EventSystem.FocusLost import FocusLostEventListener
from lib.EventSystem.KeyPress import *
from lib.EventSystem.OnFocus import OnFocusEventListener



#========================Borders========================

class BorderTypes:
    """
    An 'enum' containing border types for drawing borders around components.
    """

    NoBorder = ("", "", "", "", "", "", "", "")
    
    BlankBorder = (" ", " ", " ", " ", " ", " ", " ", " ")

    ThinBorder = ("┌", "─", "┐", "│", "│", "└", "─", "┘")

    ThinHorizontalBorder = ("", "─", "", "", "", "", "─", "")

    ThinVerticalBorder = ("", "", "", "│", "│", "", "", "")

    ThinUnderline = ("", "", "", "", "", "", "─", "")

    ThinOverline = ("", "─", "", "", "", "", "", "")

    BlockBorder = ("█", "▀", "█", "█", "█", "▀", "▀", "▀")

    BlockHorizontalBorder = ("", "▀", "", "", "", "", "▀", "")

    BlockVerticalBorder = ("", "", "", "█", "█", "", "", "")

    BlockOverline = ("", "▀", "", "", "", "", "", "")

    BlockUnderline = ("", "", "", "", "", "", "▀", "")


def addborder(value: str, bordertype=BorderTypes.ThinBorder):
    """
    Adds a four-sided ASCII border around a component.

    Args:
        value: The string representation of the component's layout. Use <component>.value() to get.

    Returns:
        The component's value with a border, in string
    """

    # If the text is empty
    if value == "":
        value = " "
    buffer: list = str(value).splitlines(False)
    width: int = len(LongestFromList(buffer))

    # Insert side borders
    for lineindex in range(0, len(buffer)):
        linelength = len(buffer[lineindex])
        if linelength < width:
            # The line is not long enough. Add spaces after the line
            buffer[lineindex] = buffer[lineindex] + " "*(width-linelength)

        buffer[lineindex] = bordertype[3] + buffer[lineindex] + bordertype[4]

    # Insert top-and-bottom borders with corner pieces
    buffer = [bordertype[0] + bordertype[1] * width + bordertype[2]] + \
        buffer + \
        [bordertype[5] + bordertype[6] * width + bordertype[7]]

    # Delete empty horizontal borders
    if buffer[0] == "":
        del buffer[0]
    if buffer[-1] == "":
        del buffer[-1]


    return "\n".join(buffer)



#========================Color list========================

class SystemColors:

    Default = Color(255, 255, 255)
    WindowBorder = Color(96, 96, 96)

    Hyperlink = Color(6, 69, 173)
    VisitedHyperlink = Color(11, 0, 128)



#========================Canvas class========================

class Canvas:

    def __init__(self, width: int, height: int, bordertype=BorderTypes.BlockBorder):

        # Dictionary to store all component
        self.__elem = {}

        # OPTIONS
        self.Width = int(width)
        self.Height = int(height)

        self.Border = bordertype


    def add(self, component, z_pos: int=-1):
        # We must first check if 'component' passes a subclass of class 'Component'
        if not issubclass(type(component), Component):
            raise TypeError("Argument 'component' is not an implementation of class 'Component'.")
        if z_pos == -1:
            self.__elem[len(self.__elem)] = component
        else:
            self.__elem[z_pos] = component


    def setzpos(self, z_pos, new_z_pos):
        if z_pos in self.__elem.keys():
            self.__elem[new_z_pos] = self.__elem[z_pos]
            del self.__elem[z_pos]


    def remove(self, z_pos: int):
        if z_pos in self.__elem.keys():
            del self.__elem[z_pos]


    def draw(self, delay: float=0, hideoverflown: bool=False, color: str='default'):
        """
        Draws/renders the current canvas with its components.

        Args:
            delay: A float indicating the delay, in seconds, before rendering starts.
            hideoverflown: Boolean indicating whether to omit rendering any component that is partially out of bounds.
            color: The color of the canvas. WILL OVERRIDE ANY CUSTOM COMPONENT COLOR (if not set to 'default')!

        """

        # draw elements inside __elem dictionary.
        # __elem dictionary has the following structure: ([z-pos], ([object to draw], [focus order]))

        # Delay parameter
        if delay > 0:
            time.sleep(delay)

        # This is the current ASCII frame to render.
        frame = (" "*self.Width + "\n")*(self.Height-1) + (" "*self.Width)
        # This buffer serves to write to individual lines
        framebuffer = frame.split(sep="\n")

        # Below for block writes individual component to 'framebuffer'
        # We need to write components from the smallest z-pos to the largest, hence the usage of range()
        for index in self.__elem.keys():
            comp: Component = self.__elem[index]
            # To write a component onto the frame, we need to separate each line and store the result in a list.
            compbuffer: list = comp.value().split(sep="\n")

            # Testing if the component overflows for optional parameter 'hideoverflown'
            if hideoverflown:
                if len(LongestFromList(compbuffer)) > (self.Width - comp.X):
                    # Object overflows in the x-axis, discard.
                    # Note: using Utils.py's LongestFromList() will rule out any inconsistent 'Component' class's value() implementation.
                    continue
                if len(compbuffer) > (self.Height - comp.Y):
                    # Object overflows in the y-axis, discard.
                    continue

            # Testing if the current component is outside of canvas area:
            if comp.X >= self.Width and comp.Y >= self.Height:
                # Object entirely outside of canvas, discard.
                continue

            # Drawing 'comp' inside 'framebuffer'
            for lineindex in range(0, len(compbuffer)):
                if comp.Y + lineindex >= len(framebuffer) or comp.Y + lineindex < 0:
                    # This line is out of bounds. Do not draw.
                    continue
                # Convert target 'framebuffer' line into a list of characters with list(str)
                linebuffer: list = list(framebuffer[comp.Y + lineindex])
                # Convert target 'compbuffer' line into a list of characters
                complinebuffer: list = list(compbuffer[lineindex])
                # This will replace the line with the current component's content without overflowing.
                #linebuffer[comp.X :] = complinebuffer[0 : len(linebuffer)-comp.X]      - produces undesirable result
                linebuffer[comp.X :] = complinebuffer
                # Update the target 'framebuffer' line with the new one
                framebuffer[comp.Y + lineindex]: str = "".join(linebuffer)

            for i in range(0, len(framebuffer)):
                # Adds space for lines that do not match canvas's width
                if len(framebuffer[i]) < self.Width:
                    framebuffer[i] += " " * (self.Width - len(framebuffer[i]))
                # Cuts overflown lines that do not match canvas's width
                if len(framebuffer[i]) > self.Width:
                    framebuffer[i] = framebuffer[i][0:self.Width]

        # Reassemble from from framebuffer
        frame = "\n".join(framebuffer)

        # Adding borders to current frame string
        frame = addborder(frame, self.Border)

        WriteShell(frame, end="\n", Color=color)

    def ColorPrinter(self, , delay: float=0, hideoverflown: bool=False, color: str='default'):




# ========================GUI class========================

class GUI:
    """
    Class responsible for the creation and maintenance of a canvas and its components.
    """

    #====================Magic methods====================

    def __init__(self, width: int = 100, height = 25, bordertype = BorderTypes.NoBorder):
        self.width = width
        self.height = height
        self.bordertype = bordertype

        self.Focus = 0 # focuses on first component

        # UX interactions
        self._inputkey = KeyCodes.Null
        self._inputkeystr = KeyCodes.tostring(KeyCodes.Null)
        self._userinput = KeyPressEventHandler(self._keypressdetector)

    #====================Properties====================

    @property
    def active(self):
        """
        Returns the index of the child component currently receiving focus.

        Returns:
            the index of the component to focus in integer
        """
        return self._active
    @active.setter
    def active(self, value: bool):
        """
        Focuses on a child component by its index.

        Args:
            value: the index of the component to focus in integer
        """
        self._active = bool(value)
        if bool(value):
            self._run()

    @property
    def focus(self):
        """
        Returns the index of the child component curently receiving focus.

        Returns:
            the index of the component to focus in integer
        """
        return self._focusindex
    @focus.setter
    def focus(self, value: int):
        """
        Focuses on a child component by its index.

        Args:
            value: the index of the component to focus in integer
        """
        if int(value) != self._focusindex:

            self._focusindex = int(value)

    @property
    def height(self):
        """
        Returns the width, in characters, of the GUI.

        Returns:
            an integer representing the height of the GUI
        """
        return self._height
    @height.setter
    def height(self, value: int):
        """
        Specifies the height, in characters, of the GUI.

        Args:
            value: an integer specifying the height of the GUI
        """
        self._height = value

    @property
    def width(self):
        """
        Returns the width, in characters, of the GUI.

        Returns:
            an integer representing the width of the GUI
        """
        return self._width
    @width.setter
    def width(self, value: int):
        """
        Specifies the width, in characters, of the GUI.

        Args:
            value: an integer specifying the width of the GUI
        """
        self._width = value

    #====================UX====================

    def _keypressdetector(self, args: KeyPressEventArgs):
        """
        Function delegate used to detect user key press input

        Args:
            sender:
            args:
        """
        self._inputkey = args.Key
        self._inputkeystr = KeyCodes.tostring(args.Key, args.Key2)

    def _run(self):
        """
        Begins execution of the GUI thread.
        Controlled internally by the GUI class.
        """



#========================Component class========================

class Component:

    #====================Magic methods====================

    def __init__(self, location, width: int = 0, height: int = 0):
        self.width = width
        self.height = height
        self.location = location

        self._focused = False


        self.handledinput = False       # This variable determines whether the GUI should transmit input to the component

        self.FocusLost = FocusLostEventListener()
        self.KeyPress = KeyPressEventListener()
        self.OnFocus = OnFocusEventListener()

    def __eq__(self, other):
        return str(self) == str(other)

    def __repr__(self):
        return self.value()

    #====================Methods====================

    def value(self):
        """
        Each component should be a string which, when printed, yields a rectangular footprint.

        Returns:
            A string representing the component.
        """
        return ""

    #====================Properties====================

    @property
    def focused(self):
        """
        Whether the component is receiving focus.
        """
        return self._focused
    @focused.setter
    def focused(self, value: bool):
        """
        Notifies this component's OnFocus event listener when it receives focus.
        Notifies this component's FocusLost event listener when focus is lost.
        """
        if value:
            self._focused = True
            self.OnFocus.notify(sender=self)
        else:
            self._focused = False
            self.FocusLost.notify(sender=self)

    @property
    def height(self):
        """
        Returns the width, in characters, of the UI.

        Returns:
            an integer representing the height of the UI
        """
        return self._height
    @height.setter
    def height(self, value: int):
        """
        Specifies the height, in characters, of the UI.

        Args:
            value: an integer specifying the height of the UI
        """
        self._height = int(value)

    @property
    def location(self):
        """
        The location of the component inside the UI.

        Returns:
            a tuple specifying the component's (x, y) location inside the UI
        """
        return self._location
    @location.setter
    def location(self, value):
        """
        Sets the location of the component inside the UI.

        Args:
            value: a tuple specifying the component's (x, y) location inside the UI
        """
        self._location = (value[0], value[1])

    @property
    def width(self):
        """
        Returns the width, in characters, of the UI.

        Returns:
            an integer representing the width of the UI
        """
        return self._width
    @width.setter
    def width(self, value: int):
        """
        Specifies the width, in characters, of the UI.
        Set to 0 for

        Args:
            value: an integer specifying the width of the UI
        """
        self._width = int(value)



#========================Version History========================

# 1.0
"""
    Initial Release

    Additions
    ---------
        -BordeeTypes class
        -addborder(value: str, bordertype=BorderTypes.ThinBorder, vertical=True, horizontal=True)
        -Canvas class
            -__init__(self, width: int, height: int, horizontal_borders=True, vertical_borders=False, bordertype=BorderTypes.BlockBorder)
            -add(self, component, z_pos: int=-1)
            -setzpos(self, z_pos, new_z_pos)
            -remove(self, z_pos: int)
            -draw(self, delay: float=0, hideoverflown: bool=False, color: str='default')
                -Currently the option to color the canvas is limited: can only color the whole canvas+components altogether, instead of simply the canvas's background
            -Canvas is the drawing board on which Components are added
        -Component class
            -__init__(self, x, y)
            -value(self)
        -Label(Component) class
            -__init__(self, x, y, text, showborder=False, bordertype=BorderTypes.ThinBorder, length=-1, overflowindicator="…")
            -value(self)
            -Can act not only as a label for simple lines or paragraphs, but also as a textbox, a checkbox, or a button
        -Illustration(Component) class
            -__init__(self, x, y, ascii, border=False)
            -A dud/foobar for later implementation of a drawing component
            -Will be used to convert external images into colored ASCII drawings
    To-Do
    -----
        -Add a function to superpose two different strings into one, with option to specify coloring for each string component.
        -Implement a GUI system to automatically control canvas drawing and offer additional components that are tied with the event system.
            -Most likely needs to be inside a separate Python file, as this system would require dependencies of EventSystem.py.
                -Will need to rename this file as ShellGUIbase.py, and migrate Label and Illustration classes to the new ShellGUI_Core.py file.
"""

# 1.1
"""
    Additions and changes related to borders

    Additions
    ---------
        -Added Version History at the end of ShellGUI_Core.py
        -Added more border types as tuple constants in BorderTypes class (under the new border format)
            -NoBorder, ThinHorizontalBorder, ThinVerticalBorder, ThinUnderline, ThinOverline, BlockHorizontalBorder, BlockVerticalBorder, BlockOverline, BlockUnderline
    
    Changes
    -------
        -Changed border structure
            -Previously the structure is specified as ([horizontal piece], [vertical piece], [upper left corner], [upper right corner], [lower left corner], [lower right corner])
            -Changed to ([ul corner], [upper horizontal piece], [ur corner], [left vertical piece], [right vertical piece], [ll corner], [lower horizontal piece], [lr corner])
            -This change allows larger diversity in the stylistical types of borders 
        -Changed addborder() mechanism
            -To comply with the new border structure
            -Will now also omit blank horizontal border lines (e.g. for ThinUnderline border type)
        -Signatures for these functions/methods are changed to comply with the new border structure
            -addborder(value: str, bordertype=BorderTypes.ThinBorder, vertical=True, horizontal=True) changed to addborder(value: str, bordertype=BorderTypes.ThinBorder)
            -Canvas.__init__(self, width: int, height: int, horizontal_borders=True, vertical_borders=False, bordertype=BorderTypes.BlockBorder)
                ->Changed to Canvas.__init__(self, width: int, height: int, bordertype=BorderTypes.BlockBorder)
    Bug Fixes
    ---------
        -
"""

# 2.0
"""
    Refactored ShellGUI.py into two separate modules: ShellGUI_Core.py and ShellGUI_Forms.py
    This separation permits better modular forms support without encumbering the core ASCII rendering engine.
    
    Additions
    ---------
        -
    
    Changes
    -------
        -Removed class 
    
    Bug Fixes
    ---------
        -
"""

How ShellGUI works:
 'Canvas' is a class representing the GUI area drawn with ASCII method.
 Each GUI element is a class inherited from 'Component' class. They need to be drawn onto a canvas.
 X and Y positions are specified inside each GUI element's class;
 Z position is specified inside Canvas class, within a dictionary. # MAJOR +1 represents an added function. # MINOR +1 represents a change in existing function(s) within the current MAJOR. This file contains the module 'ShellGUI_Core', used to create a shell-based rendering engine.
 To use this module in another project, include this file inside the project's directory. #========================Imports======================== # Used by: Canvas.draw() #========================Borders======================== An 'enum' containing border types for drawing borders around components. Adds a four-sided ASCII border around a component.
 
     Args:
         value: The string representation of the component's layout. Use <component>.value() to get.
 
     Returns:
         The component's value with a border, in string # If the text is empty # Insert side borders # The line is not long enough. Add spaces after the line # Insert top-and-bottom borders with corner pieces # Delete empty horizontal borders #========================Color list======================== #========================Canvas class======================== # Dictionary to store all component # OPTIONS # We must first check if 'component' passes a subclass of class 'Component' Draws/renders the current canvas with its components.
 
         Args:
             delay: A float indicating the delay, in seconds, before rendering starts.
             hideoverflown: Boolean indicating whether to omit rendering any component that is partially out of bounds.
             color: The color of the canvas. WILL OVERRIDE ANY CUSTOM COMPONENT COLOR (if not set to 'default')! # draw elements inside __elem dictionary. # __elem dictionary has the following structure: ([z-pos], ([object to draw], [focus order])) # Delay parameter # This is the current ASCII frame to render. # This buffer serves to write to individual lines # Below for block writes individual component to 'framebuffer' # We need to write components from the smallest z-pos to the largest, hence the usage of range() # To write a component onto the frame, we need to separate each line and store the result in a list. # Testing if the component overflows for optional parameter 'hideoverflown' # Object overflows in the x-axis, discard. # Note: using Utils.py's LongestFromList() will rule out any inconsistent 'Component' class's value() implementation. # Object overflows in the y-axis, discard. # Testing if the current component is outside of canvas area: # Object entirely outside of canvas, discard. # Drawing 'comp' inside 'framebuffer' # This line is out of bounds. Do not draw. # Convert target 'framebuffer' line into a list of characters with list(str) # Convert target 'compbuffer' line into a list of characters # This will replace the line with the current component's content without overflowing. #linebuffer[comp.X :] = complinebuffer[0 : len(linebuffer)-comp.X]      - produces undesirable result # Update the target 'framebuffer' line with the new one # Adds space for lines that do not match canvas's width # Cuts overflown lines that do not match canvas's width # Reassemble from from framebuffer # Adding borders to current frame string # ========================GUI class======================== Class responsible for the creation and maintenance of a canvas and its components. #====================Magic methods==================== # focuses on first component # UX interactions #====================Properties==================== Returns the index of the child component currently receiving focus.
 
         Returns:
             the index of the component to focus in integer Focuses on a child component by its index.
 
         Args:
             value: the index of the component to focus in integer Returns the index of the child component curently receiving focus.
 
         Returns:
             the index of the component to focus in integer Focuses on a child component by its index.
 
         Args:
             value: the index of the component to focus in integer Returns the width, in characters, of the GUI.
 
         Returns:
             an integer representing the height of the GUI Specifies the height, in characters, of the GUI.
 
         Args:
             value: an integer specifying the height of the GUI Returns the width, in characters, of the GUI.
 
         Returns:
             an integer representing the width of the GUI Specifies the width, in characters, of the GUI.
 
         Args:
             value: an integer specifying the width of the GUI #====================UX==================== Function delegate used to detect user key press input
 
         Args:
             sender:
             args: Begins execution of the GUI thread.
         Controlled internally by the GUI class. #========================Component class======================== #====================Magic methods==================== # This variable determines whether the GUI should transmit input to the component #====================Methods==================== Each component should be a string which, when printed, yields a rectangular footprint.
 
         Returns:
             A string representing the component. #====================Properties==================== Whether the component is receiving focus. Notifies this component's OnFocus event listener when it receives focus.
         Notifies this component's FocusLost event listener when focus is lost. Returns the width, in characters, of the UI.
 
         Returns:
             an integer representing the height of the UI Specifies the height, in characters, of the UI.
 
         Args:
             value: an integer specifying the height of the UI The location of the component inside the UI.
 
         Returns:
             a tuple specifying the component's (x, y) location inside the UI Sets the location of the component inside the UI.
 
         Args:
             value: a tuple specifying the component's (x, y) location inside the UI Returns the width, in characters, of the UI.
 
         Returns:
             an integer representing the width of the UI Specifies the width, in characters, of the UI.
         Set to 0 for
 
         Args:
             value: an integer specifying the width of the UI #========================Version History======================== # 1.0 Initial Release
 
     Additions
     ---------
         -BordeeTypes class
         -addborder(value: str, bordertype=BorderTypes.ThinBorder, vertical=True, horizontal=True)
         -Canvas class
             -__init__(self, width: int, height: int, horizontal_borders=True, vertical_borders=False, bordertype=BorderTypes.BlockBorder)
             -add(self, component, z_pos: int=-1)
             -setzpos(self, z_pos, new_z_pos)
             -remove(self, z_pos: int)
             -draw(self, delay: float=0, hideoverflown: bool=False, color: str='default')
                 -Currently the option to color the canvas is limited: can only color the whole canvas+components altogether, instead of simply the canvas's background
             -Canvas is the drawing board on which Components are added
         -Component class
             -__init__(self, x, y)
             -value(self)
         -Label(Component) class
             -__init__(self, x, y, text, showborder=False, bordertype=BorderTypes.ThinBorder, length=-1, overflowindicator="…")
             -value(self)
             -Can act not only as a label for simple lines or paragraphs, but also as a textbox, a checkbox, or a button
         -Illustration(Component) class
             -__init__(self, x, y, ascii, border=False)
             -A dud/foobar for later implementation of a drawing component
             -Will be used to convert external images into colored ASCII drawings
     To-Do
     -----
         -Add a function to superpose two different strings into one, with option to specify coloring for each string component.
         -Implement a GUI system to automatically control canvas drawing and offer additional components that are tied with the event system.
             -Most likely needs to be inside a separate Python file, as this system would require dependencies of EventSystem.py.
                 -Will need to rename this file as ShellGUIbase.py, and migrate Label and Illustration classes to the new ShellGUI_Core.py file. # 1.1 Additions and changes related to borders
 
     Additions
     ---------
         -Added Version History at the end of ShellGUI_Core.py
         -Added more border types as tuple constants in BorderTypes class (under the new border format)
             -NoBorder, ThinHorizontalBorder, ThinVerticalBorder, ThinUnderline, ThinOverline, BlockHorizontalBorder, BlockVerticalBorder, BlockOverline, BlockUnderline
     
     Changes
     -------
         -Changed border structure
             -Previously the structure is specified as ([horizontal piece], [vertical piece], [upper left corner], [upper right corner], [lower left corner], [lower right corner])
             -Changed to ([ul corner], [upper horizontal piece], [ur corner], [left vertical piece], [right vertical piece], [ll corner], [lower horizontal piece], [lr corner])
             -This change allows larger diversity in the stylistical types of borders 
         -Changed addborder() mechanism
             -To comply with the new border structure
             -Will now also omit blank horizontal border lines (e.g. for ThinUnderline border type)
         -Signatures for these functions/methods are changed to comply with the new border structure
             -addborder(value: str, bordertype=BorderTypes.ThinBorder, vertical=True, horizontal=True) changed to addborder(value: str, bordertype=BorderTypes.ThinBorder)
             -Canvas.__init__(self, width: int, height: int, horizontal_borders=True, vertical_borders=False, bordertype=BorderTypes.BlockBorder)
                 ->Changed to Canvas.__init__(self, width: int, height: int, bordertype=BorderTypes.BlockBorder)
     Bug Fixes
     ---------
         - # 2.0 Refactored ShellGUI.py into two separate modules: ShellGUI_Core.py and ShellGUI_Forms.py
     This separation permits better modular forms support without encumbering the core ASCII rendering engine.
     
     Additions
     ---------
         -
     
     Changes
     -------
         -Removed class 
     
     Bug Fixes
     ---------
         -