import pygame from pygame import gfxdraw import numpy as np class Window: def __init__(self, sim=None, config={}): # Simulation to draw #self.sim = sim # Set default configurations self.set_default_config() # Update configurations for attr, val in config.items(): setattr(self, attr, val) def set_default_config(self): """Set default configuration""" self.width = 1400 self.height = 1000 self.bg_color = (250, 250, 250) self.fps = 60 self.zoom = 5 self.offset = (0, 0) self.mouse_last = (0, 0) self.mouse_down = False def loop(self, loop=None): """Shows a window visualizing the simulation and runs the loop function.""" # Create a pygame window self.screen = pygame.display.set_mode((self.width, self.height)) pygame.display.flip() # Fixed fps clock = pygame.time.Clock() # To draw text pygame.font.init() self.text_font = pygame.font.SysFont('Lucida Console', 16) # Draw loop running = True while running: # Update simulation #if loop: loop(self.sim) # Draw simulation self.draw() # Update window pygame.display.update() clock.tick(self.fps) # Handle all events for event in pygame.event.get(): # Quit program if window is closed if event.type == pygame.QUIT: running = False # Handle mouse events elif event.type == pygame.MOUSEBUTTONDOWN: # If mouse button down if event.button == 1: # Left click x, y = pygame.mouse.get_pos() x0, y0 = self.offset self.mouse_last = (x-x0*self.zoom, y-y0*self.zoom) self.mouse_down = True if event.button == 4: # Mouse wheel up self.zoom *= (self.zoom**2+self.zoom/4+1) / (self.zoom**2+1) if event.button == 5: # Mouse wheel down self.zoom *= (self.zoom**2+1) / (self.zoom**2+self.zoom/4+1) elif event.type == pygame.MOUSEMOTION: # Drag content if self.mouse_down: x1, y1 = self.mouse_last x2, y2 = pygame.mouse.get_pos() self.offset = ((x2-x1)/self.zoom, (y2-y1)/self.zoom) elif event.type == pygame.MOUSEBUTTONUP: self.mouse_down = False def convert(self, x, y=None): """Converts simulation coordinates to screen coordinates""" if isinstance(x, list): return [self.convert(e[0], e[1]) for e in x] if isinstance(x, tuple): return self.convert(*x) return ( int(self.width/2 + (x + self.offset[0])*self.zoom), int(self.height/2 + (y + self.offset[1])*self.zoom) ) def inverse_convert(self, x, y=None): """Converts screen coordinates to simulation coordinates""" if isinstance(x, list): return [self.convert(e[0], e[1]) for e in x] if isinstance(x, tuple): return self.convert(*x) return ( int(-self.offset[0] + (x - self.width/2)/self.zoom), int(-self.offset[1] + (y - self.height/2)/self.zoom) ) def background(self, r, g, b): """Fills screen with one color.""" self.screen.fill((r, g, b)) def line(self, start_pos, end_pos, color): """Draws a line.""" gfxdraw.line( self.screen, *start_pos, *end_pos, color) def draw_axes(self, color=(100, 100, 100)): """Draw x and y axis""" x_start, y_start = self.inverse_convert(0, 0) x_end, y_end = self.inverse_convert(self.width, self.height) self.line( self.convert((0, y_start)), self.convert((0, y_end)), color ) self.line( self.convert((x_start, 0)), self.convert((x_end, 0)), color ) def draw_grid(self, unit=50, color=(150,150,150)): """Draws a grid""" x_start, y_start = self.inverse_convert(0, 0) x_end, y_end = self.inverse_convert(self.width, self.height) n_x = int(x_start / unit) n_y = int(y_start / unit) m_x = int(x_end / unit)+1 m_y = int(y_end / unit)+1 for i in range(n_x, m_x): self.line(self.convert((unit*i, y_start)), self.convert((unit*i, y_end)), color ) for i in range(n_y, m_y): self.line(self.convert((x_start, unit*i)), self.convert((x_end, unit*i)), color ) def draw_roads(self): """Draws every road""" pass def draw_status(self): """Draws status text""" text_fps = self.text_font.render(f't={20.0:.5}', False, (0, 0, 0)) text_frc = self.text_font.render(f'n={1000}', False, (0, 0, 0)) self.screen.blit(text_fps, (0, 0)) self.screen.blit(text_frc, (100, 0)) def draw(self): # Fill background self.background(*self.bg_color) # Major and minor grid and axes self.draw_grid(10, (220,220,220)) self.draw_grid(100, (200,200,200)) self.draw_axes() # Draw status info self.draw_status()