# pseudo_ogl_demo.py
# Ecco

"""

Try the simple "pseudo OGL" demo

"""

# class BufferedPanel
# class Square
# class Panel
# class Frame


import wx

class BufferedPanel(wx.Panel):
    def __init__(self, parent):
        super(BufferedPanel, self).__init__(parent, style=wx.TAB_TRAVERSAL | wx.WANTS_CHARS)

        if wx.Platform == "__WXMSW__":
            if not self.IsDoubleBuffered():
                self.SetDoubleBuffered(True)

class Square(object):
    def __init__(self, position, size, text=""):
        self.position = position  # [x, y]
        self.size = size  
        self.connected = False  # Connection state  
        self.locked = False  # Lock state  
        self.text = text  # Text to display
        
    def toggle_connection(self):
        self.connected = not self.connected

    def toggle_lock(self):
        self.locked = not self.locked

class Panel(BufferedPanel):
    def __init__(self, parent):
        super().__init__(parent)

        # Initialize squares with text  
        self.square1 = Square([100, 100], 60, "Square 1")
        self.square2 = Square([200, 100], 60, "Square 2")

        # Variables for mouse dragging  
        self.dragging_square = None  
        self.drag_offset = (0, 0)

        # Connect events  
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPress)
        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseClick)  # Left mouse click event  
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightClick)  # Right mouse click event  
        self.Bind(wx.EVT_LEFT_UP, self.OnMouseUp)  # Mouse button release event  
        self.Bind(wx.EVT_MOTION, self.OnMouseMove)  # Mouse move event  
        self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)  # Double-click event

        self.SetFocus()

    def OnSetFocus(self, event):
        self.SetFocus()
        event.Skip()

    def OnPaint(self, event):
        # Use wx.GCDC for anti-aliased rendering  
        dc = wx.PaintDC(self)
        dc = wx.GCDC(dc)
        dc.Clear()
        
        # Draw lines behind the squares  
        if self.square1.connected:
            self.draw_line(dc, self.square1, self.square2)

        # Draw squares  
        self.draw_square(dc, self.square1, wx.Colour(255, 105, 180))  # Pink  
        self.draw_square(dc, self.square2, wx.Colour(0, 150, 255))  # Blue

        # Draw circles  
        self.draw_circles(dc, self.square1)
        self.draw_circles(dc, self.square2)

        # Draw a white circle in the middle of locked squares  
        self.draw_locked_line(dc, self.square1)
        self.draw_locked_line(dc, self.square2)

        # Draw text inside the squares  
        self.draw_text(dc, self.square1)
        self.draw_text(dc, self.square2)
        
    def draw_square(self, dc, square, color):
        dc.SetBrush(wx.Brush(color))
        dc.DrawRectangle(square.position[0], square.position[1], square.size, square.size)

    def draw_circles(self, dc, square):
        radius = 5  
        circle_positions = [
            (square.position[0] + square.size // 2, square.position[1] - radius),  # Top  
            (square.position[0] + square.size // 2, square.position[1] + square.size + radius),  # Bottom  
            (square.position[0] - radius, square.position[1] + square.size // 2),  # Left  
            (square.position[0] + square.size + radius, square.position[1] + square.size // 2)  # Right  
        ]

        for pos in circle_positions:
            dc.SetBrush(wx.Brush(wx.Colour(255, 255, 0)))  # Yellow  
            dc.DrawCircle(pos[0], pos[1], radius)

    def draw_locked_line(self, dc, square):
        if square.locked:
            # Calculate the coordinates of the center of the square  
            center_x = square.position[0] + square.size // 2  
            top_y = square.position[1]  # Y position of the top of the square
        
            # Define the color of the line  
            dc.SetBrush(wx.Brush(wx.Colour(0, 255, 0)))  # Green color
        
            # Line thickness  
            line_thickness = 4
        
            # Draw a rectangle that mimics a line centered at the top of the square  
            start_x = center_x - (square.size // 2)  # Start position to the left  
            end_x = center_x + (square.size // 2)    # End position to the right
        
            # Draw the rectangle (line)  
            dc.DrawRectangle(start_x, top_y, end_x - start_x, line_thickness)
        
    def draw_line(self, dc, square1, square2):
        # Positions of the circles  
        circle1_pos = (square1.position[0] + square1.size // 2, square1.position[1] + square1.size // 2)
        circle2_pos = (square2.position[0] + square2.size // 2, square2.position[1] + square2.size // 2)

        # Check if the Alt key is pressed  
        alt_pressed = wx.GetKeyState(wx.WXK_ALT)

        dc.SetPen(wx.Pen(wx.Colour(0, 0, 255), 2))  # Blue  
        if alt_pressed:
            # Draw a cornered line  
            dc.DrawLine(circle1_pos[0], circle1_pos[1], circle1_pos[0], circle2_pos[1])  # Vertical  
            dc.DrawLine(circle1_pos[0], circle2_pos[1], circle2_pos[0], circle2_pos[1])  # Horizontal  
        else:
            # Straight line  
            dc.DrawLine(circle1_pos[0], circle1_pos[1], circle2_pos[0], circle2_pos[1])  # Straight line

    def draw_text(self, dc, square):
        # Define the text to draw  
        font = wx.Font(8, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
        dc.SetFont(font)
        text = square.text

        text_size = dc.GetTextExtent(text)  # Get the size of the text  
        text_x = square.position[0] + (square.size - text_size[0]) // 2  # Center horizontally  
        text_y = square.position[1] + (square.size - text_size[1]) // 2  # Center vertically

        dc.SetTextForeground(wx.WHITE)
        dc.DrawText(text, text_x, text_y)
        
    def OnMouseClick(self, event):
        # Check if a circle was clicked  
        click_pos = (event.GetX(), event.GetY())

        # Check the circles of square 1  
        if self.check_circle_click(self.square1, click_pos):
            # Toggle connection between square1 and square2  
            if self.square1.connected and self.square2.connected:
                self.square1.toggle_connection()
                self.square2.toggle_connection()
            else:
                self.square1.toggle_connection()
                self.square2.connected = self.square1.connected  # Connect square2 to square1  
            self.Refresh()  # Redraw to see the connection change  
            return

        # Check the circles of square 2  
        if self.check_circle_click(self.square2, click_pos):
            # Toggle connection between square1 and square2  
            if self.square2.connected and self.square1.connected:
                self.square2.toggle_connection()
                self.square1.toggle_connection()
            else:
                self.square2.toggle_connection()
                self.square1.connected = self.square2.connected  # Connect square1 to square2  
            self.Refresh()  # Redraw to see the connection change  
            return

        # Check if a square was clicked to start dragging  
        if self.check_square_click(self.square1, click_pos):
            self.dragging_square = self.square1  
            self.drag_offset = (click_pos[0] - self.square1.position[0], click_pos[1] - self.square1.position[1])
        elif self.check_square_click(self.square2, click_pos):
            self.dragging_square = self.square2  
            self.drag_offset = (click_pos[0] - self.square2.position[0], click_pos[1] - self.square2.position[1])

    def OnMouseUp(self, event):
        # Stop dragging  
        self.dragging_square = None

    def OnMouseMove(self, event):
        # Move the square if the mouse is being dragged with the left button down  
        if self.dragging_square is not None and event.Dragging() and event.LeftIsDown():
            new_x = event.GetX() - self.drag_offset[0]
            new_y = event.GetY() - self.drag_offset[1]

            # Check panel boundaries  
            if new_x < 0:
                new_x = 0  
            elif new_x + self.dragging_square.size > self.GetSize().width:
                new_x = self.GetSize().width - self.dragging_square.size

            if new_y < 0:
                new_y = 0  
            elif new_y + self.dragging_square.size > self.GetSize().height:
                new_y = self.GetSize().height - self.dragging_square.size

            self.dragging_square.position = [new_x, new_y]
            self.Refresh()

    def OnRightClick(self, event):
        # Check if a square was clicked to change its lock state  
        click_pos = (event.GetX(), event.GetY())

        if self.check_square_click(self.square1, click_pos):
            self.square1.toggle_lock()
            self.Refresh()  # Redraw to see the lock change  
            return

        if self.check_square_click(self.square2, click_pos):
            self.square2.toggle_lock()
            self.Refresh()  # Redraw to see the lock change  
            return

    def check_circle_click(self, square, click_pos):
        radius = 5  
        circle_positions = [
            (square.position[0] + square.size // 2, square.position[1] - radius),  # Top  
            (square.position[0] + square.size // 2, square.position[1] + square.size + radius),  # Bottom  
            (square.position[0] - radius, square.position[1] + square.size // 2),  # Left  
            (square.position[0] + square.size + radius, square.position[1] + square.size // 2)  # Right  
        ]

        for pos in circle_positions:
            if (click_pos[0] - pos[0]) ** 2 + (click_pos[1] - pos[1]) ** 2 <= radius ** 2:
                return True  
        return False

    def OnDoubleClick(self, event):
        click_pos = (event.GetX(), event.GetY())

        # Check if a square was double-clicked  
        if self.check_square_click(self.square1, click_pos):
            self.square1.toggle_lock()
            self.Refresh()  # Redraw to see the lock change  
            return

        if self.check_square_click(self.square2, click_pos):
            self.square2.toggle_lock()
            self.Refresh()  # Redraw to see the lock change  
            return

    def check_square_click(self, square, click_pos):
        if (square.position[0] <= click_pos[0] <= square.position[0] + square.size and  
            square.position[1] <= click_pos[1] <= square.position[1] + square.size):
            return True  
        return False

    def OnKeyPress(self, event):
        keycode = event.GetKeyCode()
        shift_pressed = event.ShiftDown()
        move_distance = 5 if not shift_pressed else 50

        # Get the direction of movement  
        dx = 0  
        dy = 0
        
        if keycode == wx.WXK_LEFT or keycode == ord('a'):
            dx = -move_distance  
        elif keycode == wx.WXK_RIGHT or keycode == ord('d'):
            dx = move_distance  
        elif keycode == wx.WXK_DOWN or keycode == ord('s'):
            dy = move_distance  
        elif keycode == wx.WXK_UP or keycode == ord('w'):
            dy = -move_distance  

        # Move squares only if they are not locked  
        if not self.square1.locked:
            self.move_square(self.square1, dx, dy)

        if not self.square2.locked:
            self.move_square(self.square2, dx, dy)

        self.Refresh()

    def move_square(self, square, dx, dy):
        new_x = square.position[0] + dx  
        new_y = square.position[1] + dy

        # Check panel boundaries  
        if new_x < 0:
            new_x = 0  
        elif new_x + square.size > self.GetSize().width:
            new_x = self.GetSize().width - square.size

        if new_y < 0:
            new_y = 0  
        elif new_y + square.size > self.GetSize().height:
            new_y = self.GetSize().height - square.size

        square.position = [new_x, new_y]


class Frame(wx.Frame):
    def __init__(self):
        super().__init__(parent=None, title='Move and link squares with keyboard and mouse',
                         style=wx.DEFAULT_FRAME_STYLE | wx.WANTS_CHARS)

        # Create a menu bar  
        self.menu_bar = wx.MenuBar()

        # Create menus  
        file_menu = wx.Menu()
        help_menu = wx.Menu()
        
        # Create items 
        quit_item = file_menu.Append(wx.ID_EXIT, '&Quit\tCtrl+Q', 'Quit')
        help_item = help_menu.Append(wx.ID_HELP, '&Help\tCtrl+H', 'Help')

        # Add items to the menu bar  
        self.menu_bar.Append(file_menu, '&File')
        self.menu_bar.Append(help_menu, '&Help')

        self.SetMenuBar(self.menu_bar)
        
        # Connect menu events to their methods  
        self.Bind(wx.EVT_MENU, self.OnQuit, quit_item)
        self.Bind(wx.EVT_MENU, self.OnHelp, help_item)
        
        # Create an instance of Panel  
        self.panel = Panel(self)
        
        # Adjust the size of the Frame  
        self.SetSize((500, 300))
        self.Centre()
        self.Show()

    def OnHelp(self, event):
        wx.MessageBox("Pseudo OGL demo\n\n"\
                      "- Lock* and unlock movement with the arrow keys by double-clicking on a square (*green),\n" \
                      "- Move locked objects with mouse only,\n" \
                      "- Move unlocked squares individually or in pairs with keyboard arrow keys,\n" \
                      "- Link squares by clicking on a yellow circle,\n" \
                      "- Create a cornered link by clicking the Alt key,\n" \
                      "- Use Shift or Alt+Shift to speed up moving unlocked objects using the arrow keys,\n" \
                      "- Hold down the Alt key to maintain angled lines.\n" \
                      "\nVersion 1.0",
                      "About...", wx.OK | wx.ICON_INFORMATION)
        
    def OnQuit(self, event):
        self.Close()

if __name__ == '__main__':
    app = wx.App(False)
    frame = Frame()
    app.MainLoop()

