Skip to content

Instantly share code, notes, and snippets.

@Mekire
Last active January 20, 2016 11:11
Show Gist options
  • Save Mekire/344cb434a1936075efed to your computer and use it in GitHub Desktop.
Save Mekire/344cb434a1936075efed to your computer and use it in GitHub Desktop.
Accurate relative mouse movement in a resizable display.
#! /usr/bin/env python
"""
A very simple example showing how to drag an item with the mouse.
Dragging in this example uses relative mouse movement.
-Written by Sean J. McKiernan 'Mekire'
"""
from __future__ import division
import os
import sys
import pygame as pg
CAPTION = "Drag the Red Square"
SCREEN_START_SIZE = (1000, 600)
class Character(object):
"""
A class to represent our lovable red sqaure.
"""
SIZE = (150, 150)
def __init__(self, pos):
"""
The argument pos corresponds to the center of our rectangle.
"""
self.rect = pg.Rect((0,0), Character.SIZE)
self.rect.center = pos
self.true_pos = list(pos)
self.click = False
def check_click(self, pos, scale):
"""
This function is called from the event loop to check if a click
overlaps with the player rect.
pygame.mouse.get_rel must be called on an initial hit so that
subsequent calls give the correct relative offset.
"""
scaled_pos = pos[0]*scale[0], pos[1]*scale[1]
if self.rect.collidepoint(scaled_pos):
self.click = True
pg.mouse.get_rel()
def update(self, bound_rect, scale):
"""
If the square is currently clicked, update its position based on the
relative mouse movement. Clamp the rect to the screen.
"""
if self.click:
rel = pg.mouse.get_rel()
self.true_pos[0] += rel[0]*scale[0]
self.true_pos[1] += rel[1]*scale[1]
self.rect.center = self.true_pos
if not bound_rect.contains(self.rect):
self.rect.clamp_ip(bound_rect)
self.true_pos = list(self.rect.center)
def draw(self, surface):
"""
Basic draw function.
"""
surface.fill(pg.Color("red"), self.rect)
class App(object):
"""
A class to manage our event, game loop, and overall program flow.
"""
def __init__(self):
"""
Get a reference to the screen (created in main); define necessary
attributes; and create our player (draggable rect).
"""
self.screen = pg.display.get_surface()
self.screen_rect = self.screen.get_rect()
self.proxy = self.screen.copy()
self.proxy_rect = self.screen_rect.copy()
self.clock = pg.time.Clock()
self.fps = 60
self.done = False
self.keys = pg.key.get_pressed()
self.scale = (1, 1)
self.player = Character(self.screen_rect.center)
def event_loop(self):
"""
This is the event loop for the whole program.
Regardless of the complexity of a program, there should never be a need
to have more than one event loop.
"""
for event in pg.event.get():
if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]:
self.done = True
elif event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
self.player.check_click(event.pos, self.scale)
elif event.type == pg.MOUSEBUTTONUP and event.button == 1:
self.player.click = False
elif event.type in (pg.KEYUP, pg.KEYDOWN):
self.keys = pg.key.get_pressed()
elif event.type == pg.VIDEORESIZE:
self.screen = pg.display.set_mode(event.size, pg.RESIZABLE)
self.screen_rect = self.screen.get_rect()
self.scale = (self.proxy_rect.w/self.screen_rect.w,
self.proxy_rect.h/self.screen_rect.h)
def render(self):
"""
All drawing should be found here.
This is the only place that pygame.display.update() should be found.
"""
size = self.screen_rect.size
self.proxy.fill(pg.Color("black"))
self.player.draw(self.proxy)
pg.transform.smoothscale(self.proxy, size, self.screen)
pg.display.update()
def main_loop(self):
"""
This is the game loop for the entire program.
Like the event_loop, there should not be more than one game_loop.
"""
while not self.done:
self.event_loop()
self.player.update(self.proxy_rect, self.scale)
self.render()
self.clock.tick(self.fps)
def main():
"""
Prepare our environment, create a display, and start the program.
"""
pg.init()
pg.display.set_caption(CAPTION)
pg.display.set_mode(SCREEN_START_SIZE, pg.RESIZABLE)
App().main_loop()
pg.quit()
sys.exit()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment