Skip to content

Commit

Permalink
Merge pull request #119 from martinohanlon/dev
Browse files Browse the repository at this point in the history
v1.3
  • Loading branch information
Martin O'Hanlon authored Dec 30, 2018
2 parents 0ca40cc + 274830d commit fe8b255
Show file tree
Hide file tree
Showing 46 changed files with 4,134 additions and 364 deletions.
2 changes: 2 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Why be restricted by such vague positions like top and bottom though: you can ge

Its not all about when the button was pressed either - pressed, released or moved they all work.

The dot doesn't have to be blue, or a dot, you can change its colour, make it square or give it a border.

You can press it, `slide it`_, `swipe it`_, `rotate it`_ - one blue circle can do a lot!

Even more
Expand Down
1 change: 1 addition & 0 deletions bluedot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .dot import BlueDot, BlueDotPosition, BlueDotInteraction, BlueDotSwipe, BlueDotRotation
from .colors import COLORS
from .mock import MockBlueDot
191 changes: 138 additions & 53 deletions bluedot/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,30 @@
import pygame
import sys
from .btcomm import BluetoothAdapter, BluetoothClient

#colours
BLUE = (0, 0, 255)
DARKBLUE = (0, 0, 200)
GREY = (220, 220, 220)
RED = (255, 0, 0)
from .constants import PROTOCOL_VERSION
from .colors import BLUE, GRAY43, GRAY86, RED, parse_color

DEFAULTSIZE = (320, 240)
BORDER = 7
FONT = "monospace"
FONTSIZE = 18
FONTPAD = 3

CLIENT_NAME = "Blue Dot Python app"
BORDER_THICKNESS = 0.025

class BlueDotClient():
class BlueDotClient(object):
def __init__(self, device, server, fullscreen, width, height):

self._device = device
self._server = server
self._fullscreen = fullscreen

#init pygame
pygame.init()

#load font
font = pygame.font.SysFont(FONT, FONTSIZE)
self._font = pygame.font.SysFont(FONT, FONTSIZE)

#setup the screen
#set the screen caption
Expand All @@ -42,19 +44,25 @@ def __init__(self, device, server, fullscreen, width, height):
if width == None: width = DEFAULTSIZE[0]
if height == None: height = DEFAULTSIZE[1]

screen = pygame.display.set_mode((width, height), screenflags)
self._screen = pygame.display.set_mode((width, height), screenflags)

self._width = width
self._height = height

self._run()

pygame.quit()

def _run(self):
#has a server been specified? If so connected directly
if server:
button_screen = ButtonScreen(screen, font, device, server, width, height)
if self._server:
button_screen = ButtonScreen(self._screen, self._font, self._device, self._server, self._width, self._height)
button_screen.run()
else:
#start the devices screen
devices_screen = DevicesScreen(screen, font, device, width, height)
devices_screen = DevicesScreen(self._screen, self._font, self._device, self._width, self._height)
devices_screen.run()

pygame.quit()


class BlueDotScreen(object):
def __init__(self, screen, font, width, height):
Expand All @@ -64,36 +72,36 @@ def __init__(self, screen, font, width, height):
self.height = height

#setup screen attributes
self.frame_rect = pygame.Rect(BORDER, BORDER, self.width - (BORDER * 2), self.height - (BORDER * 2))
self.frame_rect = pygame.Rect(BORDER, BORDER, self.width - (BORDER * 2) - FONTSIZE - FONTPAD, self.height - (BORDER * 2))
self.close_rect = pygame.Rect(self.width - FONTSIZE - FONTPAD - BORDER, BORDER, FONTSIZE + FONTPAD, FONTSIZE + FONTPAD)

self.draw_screen()

def draw_screen(self):
#set the screen background
self.screen.fill(GREY)
self.screen.fill(GRAY86.rgb)

self.draw_close_button()

def draw_close_button(self):
#draw close button
pygame.draw.rect(self.screen, BLUE, self.close_rect, 2)
pygame.draw.line(self.screen, BLUE,
pygame.draw.rect(self.screen, BLUE.rgb, self.close_rect, 2)
pygame.draw.line(self.screen, BLUE.rgb,
(self.close_rect[0], self.close_rect[1]),
(self.close_rect[0] + self.close_rect[2], self.close_rect[1] + self.close_rect[3]),
1)
pygame.draw.line(self.screen, BLUE,
pygame.draw.line(self.screen, BLUE.rgb,
(self.close_rect[0], self.close_rect[1] + self.close_rect[3]),
(self.close_rect[0] + self.close_rect[2], self.close_rect[1]),
1)

def draw_error(self, e):
message = "Error: {}".format(e)
print(message)
self.draw_status_message(message, colour = RED)
self.draw_status_message(message, colour = RED.rgb)

def draw_status_message(self, message, colour = BLUE):
self.screen.fill(GREY, self.frame_rect)
def draw_status_message(self, message, colour = BLUE.rgb):
self.screen.fill(GRAY86.rgb, self.frame_rect)
self.draw_close_button()
self.draw_text(message, colour, self.frame_rect.height / 2, border = True, border_pad = FONTPAD)
pygame.display.update()
Expand Down Expand Up @@ -157,11 +165,11 @@ def draw_screen(self):
super(DevicesScreen, self).draw_screen()

#title
title_rect = self.draw_text("Connect", RED, 0)
title_rect = self.draw_text("Connect", RED.rgb, 0)

y = title_rect.bottom
for d in self.bt_adapter.paired_devices:
device_rect = self.draw_text("{} ({})".format(d[1], d[0]), BLUE, y, border = True, border_pad = FONTPAD)
device_rect = self.draw_text("{} ({})".format(d[1], d[0]), BLUE.rgb, y, border = True, border_pad = FONTPAD)

self.device_rects.append(pygame.Rect(device_rect))

Expand Down Expand Up @@ -205,38 +213,76 @@ def run(self):


class ButtonScreen(BlueDotScreen):
def __init__(self, screen, font, device, server, width, height):
super(ButtonScreen, self).__init__(screen, font, width, height)

def __init__(self, screen, font, device, server, width, height):
self.device = device
self.server = server
self._data_buffer = ""

self.last_x = 0
self.last_y = 0

self._colour = BLUE
self._border = False
self._square = False
self._visible = True
self._pressed = False

super(ButtonScreen, self).__init__(screen, font, width, height)

def draw_screen(self):
super(ButtonScreen, self).draw_screen()

self._draw_circle(BLUE)

def _draw_circle(self, colour):
#draw the circle
self.circle_centre = (int(self.frame_rect.top + (self.frame_rect.width / 2)), int(self.frame_rect.left + (self.frame_rect.height / 2)))
# work out dot position
self.dot_centre = (int(self.frame_rect.top + (self.frame_rect.width / 2)), int(self.frame_rect.left + (self.frame_rect.height / 2)))

if self.frame_rect.width > self.frame_rect.height:
self.circle_radius = int(self.frame_rect.height / 2)
self.dot_rect = pygame.Rect(self.frame_rect.left + int((self.frame_rect.width - self.frame_rect.height) / 2), self.frame_rect.top, self.frame_rect.height, self.frame_rect.height)
self.dot_radius = int(self.dot_rect.height / 2)
else:
self.circle_radius = int(self.frame_rect.width / 2)

self.circle_rect = pygame.draw.circle(self.screen, colour, self.circle_centre, self.circle_radius, 0)
self.dot_rect = pygame.Rect(self.frame_rect.left, self.frame_rect.top + int((self.frame_rect.height - self.frame_rect.width) / 2), self.frame_rect.width, self.frame_rect.width)
self.dot_radius = int(self.dot_rect.width / 2)

self.border_width = max(int(self.dot_rect.width * BORDER_THICKNESS), 1)
self.border_height = max(int(self.dot_rect.height * BORDER_THICKNESS), 1)

self._draw_dot()

def _draw_dot(self):

# clear the dot

pygame.draw.rect(
self.screen,
GRAY86.rgb,
(
self.dot_rect.left - self.border_width,
self.dot_rect.top - self.border_height,
self.dot_rect.width + (self.border_width * 2),
self.dot_rect.height + (self.border_height * 2),
)
)
colour = self._colour if not self._pressed else self._colour.get_adjusted_color(0.85)

# draw the dot
if self._square:
if self._visible:
pygame.draw.rect(self.screen, colour.rgb, self.dot_rect)
if self._border:
pygame.draw.rect(self.screen, GRAY43.rgb, self.dot_rect, max(int(self.dot_radius * BORDER_THICKNESS), 1))
else:
if self._visible:
pygame.draw.ellipse(self.screen, colour.rgb, self.dot_rect)
if self._border:
pygame.draw.ellipse(self.screen, GRAY43.rgb, self.dot_rect, max(int(self.dot_radius * BORDER_THICKNESS), 1))

def _process(self, op, pos):
if self.bt_client.connected:
x = (pos[0] - self.circle_centre[0]) / float(self.circle_radius)
x = (pos[0] - self.dot_centre[0]) / float(self.dot_radius)
x = round(x, 4)
y = ((pos[1] - self.circle_centre[1]) / float(self.circle_radius)) * -1
y = ((pos[1] - self.dot_centre[1]) / float(self.dot_radius)) * -1
y = round(y, 4)
message = "{},{},{}\n".format(op, x, y)
if message == 2:
if op == 2:
if x != self.last_x or y != self.last_y:
self._send_message(message)
else:
Expand All @@ -246,26 +292,57 @@ def _process(self, op, pos):
else:
self.draw_error("Blue Dot not connected")

def _send_protocol_version(self):
if self.bt_client.connected:
self._send_message("3,{},{}\n".format(PROTOCOL_VERSION, CLIENT_NAME))

def _send_message(self, message):
try:
self.bt_client.send(message)
except:
e = str(sys.exc_info()[1])
self.draw_error(e)

def run(self):
def _data_received(self, data):
#add the data received to the buffer
self._data_buffer += data

#get any full commands ended by \n
last_command = self._data_buffer.rfind("\n")
if last_command != -1:
commands = self._data_buffer[:last_command].split("\n")
#remove the processed commands from the buffer
self._data_buffer = self._data_buffer[last_command + 1:]
self._process_commands(commands)

def _process_commands(self, commands):
for command in commands:
params = command.split(",")
invalid_command = False
if len(params) == 5:
if params[0] == "4":
self._colour = parse_color(params[1])
self._square = True if params[2] == "1" else False
self._border = True if params[3] == "1" else False
self._visible = True if params[4] == "1" else False

self._draw_dot()

else:
invalid_command = True

self.bt_client = BluetoothClient(self.server, None, device = self.device, auto_connect = False)
try:
self.bt_client.connect()
except:
e = str(sys.exc_info()[1])
self.draw_error(e)
if invalid_command:
print("Error - Invalid message received '{}'".format(command))

def run(self):

self._connect()
self._send_protocol_version()

clock = pygame.time.Clock()
pygame.event.clear()

mouse_pressed = False
self._pressed = False
running = True

while running:
Expand All @@ -276,20 +353,20 @@ def run(self):
for event in ev:

# handle mouse
if event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.MOUSEBUTTONUP or (event.type == pygame.MOUSEMOTION and mouse_pressed):
if event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.MOUSEBUTTONUP or (event.type == pygame.MOUSEMOTION and self._pressed):
pos = pygame.mouse.get_pos()

#circle clicked?
if self.circle_rect.collidepoint(pos):
if self.dot_rect.collidepoint(pos):

if event.type == pygame.MOUSEBUTTONDOWN:
mouse_pressed = True
self._draw_circle(DARKBLUE)
self._pressed = True
self._draw_dot()
self._process(1, pos)

elif event.type == pygame.MOUSEBUTTONUP:
mouse_pressed = False
self._draw_circle(BLUE)
self._pressed = False
self._draw_dot()
self._process(0, pos)

elif event.type == pygame.MOUSEMOTION:
Expand All @@ -310,6 +387,14 @@ def run(self):

self.bt_client.disconnect()

def _connect(self):
self.bt_client = BluetoothClient(self.server, self._data_received, device = self.device, auto_connect = False)
try:
self.bt_client.connect()
except:
e = str(sys.exc_info()[1])
self.draw_error(e)

def main():
#read command line options
parser = ArgumentParser(description="Blue Dot Python App")
Expand Down
Loading

0 comments on commit fe8b255

Please sign in to comment.