Share your repls and programming experiences

← Back to all posts
pyDraw: A New Approach to Learning with Graphics
h
FeaturedSpace (46)

Hello coders and friends.

My name is Noah Coetsee, and I am here today to present you with a new library of which I have been working on for some time now.

After having seen turtle programs (Python's default graphics for learning), I almost immediately determined that it was undesirable (at least in my opinion) and would halt learning with weird convolutions or simply unexplained features/outputs. I set out to design a Python library that would be reminiscent of another library I had redesigned, CoffeeDraw (which was based on objectdraw).

It creates an easy and object-oriented method of graphics and was designed to be easy to learn (or teach).

Installation

This is just a quick aside for installation. Pydraw has two different methods of installation, one being the normal installation, 'pip install pydraw', and the alternative is a file that you can drag and drop into your project's directory. (accessible here). It is important to note that you must install the Pillow/PIL dependency if you intend to modify any images or load images that are not .png, .gif, or .ppm!


Here's a basic program that draws a Rectangle:

from pydraw import *

screen = Screen(500, 500)
box = Rectangle(screen, 200, 200, 50, 50, Color('red'))

fps = 30
running = True
while running:
    screen.update()
    screen.sleep((1000 / fps) / 1000)

But one of the best features of pyDraw, my brainchild, is the input system. If you look at how input has been done in the past, with turtle requiring registration per-key or pygame flushing all input into the state-event loop, neither of these options is desirable for a proper and simple input-system.

So I have devised a reflection-based input-system that will allow the user to simply define methods and call a screen.listen() method beneath those definitions.

Input

def mousedown(button, location):
  print(f'Wow, the {button}-button on the mouse!')

def mouseup(button, location):
  print('How un-impressive...')

def keydown(key):
  print(f'Keyboard input is {key} to creating interactive programs!')

def keyup(key):
  print('For when you really just gotta stop moving, keyup is here to save you.')

# All of the above methods must be defined above this statement (because Python):
screen.listen()

That (in my not so humble opinion), looks much better!

So let's make a basic game, a bouncing ball game/program. We will create a basic ball that bounces on the bottom of the screen like so.

Bouncy Ball Game:

from pydraw import *

screen = Screen(800, 600)

ball = Oval(screen, screen.width() / 2, screen.height() / 2, 25, 25, Color('lightblue'))
ball.move(-ball.width() / 2, -ball.height() / 2)

gravity = 3
bounce = -50
ball_dx = 0
ball_dy = 0

fps = 30
running = True
while running:
    if ball.y() > screen.height():
        ball_dy = bounce
    else:
        ball_dy += gravity
    
    ball.move(dy=ball_dy)

    screen.update()
    screen.sleep((1000 / 30) / 1000)

We can add some fun input like this:

from pydraw import *

screen = Screen(800, 600)

ball = Oval(screen, screen.width() / 2, screen.height() / 2, 25, 25, Color('lightblue'))
ball.move(-ball.width() / 2, -ball.height() / 2)

gravity = 3
bounce = -50
ball_dx = 0
ball_dy = 0

def keydown(key):
    if key == 'left':
        ball.move(dx=-1)
    if key == 'right':
        ball.move(dx=1)

screen.listen()

fps = 30
running = True
while running:
    if ball.y() > screen.height():
        ball_dy = bounce
    else:
        ball_dy += gravity
    
    ball.move(dy=ball_dy)

    screen.update()
    screen.sleep((1000 / 30) / 1000)

As you can see, now the ball will bounce and move left to right based on user input!

There's all sorts of other programs that are made realistically possible with pyDraw, such as this painting program painter.py:

Or this clock: clock.py (only 37 lines):

There's a program linked below that shows off most of what pyDraw has to offer with detailed comments and explanations. (note that you'll have to install pydraw on the shell via 'pip install pydraw').

I hope ya'll will find great uses for this and I look forward to seeing what people can do with it.

See ya'll soon,
Noah

Some Resources:
https://github.com/pydraw/pydraw
https://pypi.org/project/pydraw
https://pydraw.graphics (Docs)

Comments
hotnewtop
amasad (3346)

This is really cool! But the examples didn't work. I forked it and installed the package here but the example code seems to be wrong? https://repl.it/@amasad/pyDraw#main.py

catspython (27)

@amasad I did the same thing before seeing this.

FeaturedSpace (46)

@amasad I am so sorry about that, it seems that in my writing I had miswritten some lines. It's all been corrected now, and I urge you to re-fork and try it out :)

angrydoge (465)

Maybe add a pydraw template to the example GUI? @amasad

FeaturedSpace (46)

If you still get any errors when you run the example/showcase code, make sure your pydraw version is 1.0.3!

amasad (3346)

@FeaturedSpace Can you install it as a package in your repl? You can do it from the package side-bar. That way when someone forks your repl it will be there for them and they don't have to install from the shell.

See this doc https://docs.repl.it/repls/packages

P.s. if you run into an [assertion error] installing the package, that typically happens when the project name is the same as the dependency name -- we're working on a fix for this -- but for now rename the repl to pydraw example or something.

FeaturedSpace (46)

@amasad Seems I did get an [Assertion Error], renaming :)
Thanks, and I do have another question, it seems when I just type import pydraw it doesn't autoinstall the package like others do.

Spotandjake (28)

this is actually really cool. the functions look like they are almost the same as p5 or processing which is nice as well., makes porting between things easier.

FeaturedSpace (46)

@Spotandjake While I have never used p5, I can always appreciate an unexpected benefit to writing .attribute() (get/set combinator) methods.

I love that style :)

DannyIsCoding (695)

This is awesome! Great job! :D 👍

Code1Tech (108)

Poggers, this deserves more upvotes!

FeaturedSpace (46)

@Code1Tech I appreciate that! You can help us spread the word by trying it out in a repl and giving our GitHub a star!

FeaturedSpace (46)

@Code1Tech Much appreciated :) Enjoy the library.

Especially the latest patch (v1.1.2), there's so much to unpack!

CustomPolygon scaling via .width() and .height()
Using hexes in colors: Color('#fff')
Colorizing images: image.color(Color('red'))
Rotating Text, cloning objects using .clone(), copying transforms (width, height, angle) from objects via .transform()!

and more :)

Code1Tech (108)

thanks for the information @FeaturedSpace

FeaturedSpace (46)

@DrakeFletcher2 Absolutely. In fact you could probably rewrite it in at least half the time it took to write originally. You could keep your state code, and your polygons (with some changes to the top-left coordinate plane), and a lot of your terrain generation, etc.

That's a fantastic program btw, I love it :)

DrakeFletcher2 (21)

@FeaturedSpace Thanks, when I get a chance I will rewrite it. Does your program still use Tkinter though?

FeaturedSpace (46)

@DrakeFletcher2 Yessir, fully compatible with both turtle and Tkinter should you choose to get your hands dirty :)

DrakeFletcher2 (21)

@FeaturedSpace How do you make it fullscreen for repl?

DrakeFletcher2 (21)

@FeaturedSpace I made a new pull request in github, idk if it will work but that is what new functions i want.

FeaturedSpace (46)

Hey @DrakeFletcher2 ,
As it’s not meant for you to retrieve the root, it most likely will not become a method, but you can of course always access the protected attributes, “_root”, “_canvas”, or “_screen”. The reasoning behind this is simply that PyDraw, while providing support for turtle and tkinter, is not meant to expose those libraries (as there is usually no need). If there is something you need turtle or tkinter for, I’d love to know if there are other features that you might need 🙂

screen._root
screen._screen
screen._canvas

FeaturedSpace (46)

@DrakeFletcher2 that is correct. You simply retrieve the protected attributes (dangerously, I might note, if one does not know what they're done), and use them as you please. :)

FeaturedSpace (46)

@DrakeFletcher2 As for the fullscreen, that is an excellent feature and I intend to implement that in the next patch. :)

DrakeFletcher2 (21)

@FeaturedSpace Is there a way to scale the poly?

Because I am having to do:

# Ganerating lander poly
unscaled_lander_poly = [(77.0, -74.0), (77.0, -70.0), (69.0, -70.0), (49.0, -27.0), (49.0, -16.0), (56.0, 2.0), (56.0, 10.0), (50.0, 35.0), (33.0, 59.0), (10.0, 70.0), (1.0, 71.0), (0.0, 71.0), (-9.0, 70.0), (-35.0, 57.0), (-35.0, 70.0), (-33.0, 72.0), (-33.0, 73.0), (-33.0, 74.0), (-33.0, 75.0), (-34.0, 76.0),(-34.0, 77.0), (-35.0, 78.0), (-36.0, 78.0), (-37.0, 79.0), (-38.0, 79.0), (-39.0, 79.0), (-40.0, 78.0), (-41.0, 78.0), (-42.0, 77.0), (-43.0, 76.0),(-43.0, 75.0), (-43.0, 74.0), (-43.0, 73.0), (-43.0, 72.0), (-43.0, 71.0), (-42.0, 71.0), (-42.0, 70.0), (-41.0, 70.0), (-41.0, 54.0), (-46.0, 52.0),(-55.0, 10.0), (-55.0, 2.0), (-48.0, -13.0), (-48.0, -27.0), (-68.0, -70.0), (-76.0, -70.0), (-76.0, -74.0), (-55.0, -74.0), (-55.0, -70.0), (-61.0, -70.0), (-50.0, -42.0), (51.0, -42.0), (61.0, -70.0), (56.0, -70.0), (56.0, -74.0)]
scaled_lander_poly = []
for point in unscaled_lander_poly:
  scaled_lander_poly.append((point[0]*lander_scale, point[1]*lander_scale))

# Creating game
lander = pydraw.CustomPolygon(main_screen, scaled_lander_poly, color=pydraw.Color("white"))
lander.rotate(180)
FeaturedSpace (46)

@DrakeFletcher2 There used to be a way to scale polygons, but the issue became maintaining rotation and scale at the same time. Looking into establishing it in the future, I just didn't want to do it wrong.

DrakeFletcher2 (21)

@FeaturedSpace for some reason whenever i try to move the poly it just goes to 0,0. What am i doing wrong?

lander = pydraw.CustomPolygon(main_screen, scaled_lander_poly, color=pydraw.Color("white"))
lander.rotate(180)

while True:
  time.sleep(1)
  lander.move(100, 100)
  main_screen.update()
DrakeFletcher2 (21)

@FeaturedSpace Do you know what I am doing wrong?

FeaturedSpace (46)

@DrakeFletcher2 Reinstall pydraw and ensure your version is the latest version. Movement in the CustomPolygons was a little glitched. So sorry about that! I really shouldn't have let that slip past me. Again, great work from what I can see. Can't wait to see what's to come. Noah