Skip to main content

Easy to use widgets for pygame

Project description

# Graphalama

## Aim

Pygame is a great and simple way to create basic windowed applications.
It is therfore a privilieged entry point for python learners to get out of the console.
However, people often ask for GUI features in pygame: _"How can I have a simple button ?"_ or _"How can the user enter their name ?"_.

The goal of this librairy is to answer all those questions and provide the easiest possible interface to create, update, control and manipulate what we will call widgets. (Buttons, TextBoxes, Sliders, Switches...)

### Our principles

- KISS: Keep it sweet and simple
- Simple and ugly are not synonyms
- Simple can be powerfull
- Simple should be customisable
- No code is better than ugly code

## Installation

You can install a stable version using pip
```
pip install graphalama
```
Or a developper version with
```
git clone https://gitlab.com/lama-corp/graphalama
cd graphalama
python setup.py install -U
```

## How to use

Most simple pygame applications look like this

```python
import pygame
pygame.init()

# Setup of variables, objects...
stop = False
screen = pygame.display.get((400, 400))

# Main loop
while not stop:

for event in pygame.event.get():
if event.type == pygame.QUIT:
stop = True
else:
# Handle inputs, if the user click on some clickable places, buttons...
# Handle the keys pressed to move players, input text...
# It's the logic of the game that depends of inputs
...

# Then you have an internal logic, applying gravity, running AIs, anything that changes itself every frame

# And finally you render everything with some more or less advanced technics

screen.fill((255, 255, 255))
pygame.display.update()
```

For any widget you need to have at least 3 parts in the code to implement it: creation, handling the input, and rendering it. Some widget can have an internal logic going like clock or timer but we'll come back on that later.

Say you want to add a button in your window. A big green play button. We reduce each step to the minimun.

- Creation :
`play_button = Button("PLAY", bg_color=GREEN, function=play)`
- Input handling (clicks):
`play_button.update(event)`
- Rendering: `play_button.render(screen)`

A minimal hello world would look like this:
```python
import pygame
from graphalama.widgets import *

pygame.init()

screen = pygame.display.set_mode((800, 500))

# creation
hello_text = SimpleText("Hello World!")

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
# a text doesn't change on input so we don't need to update it

screen.fill((255, 255, 255))
hello_text.render(screen)
pygame.display.flip()
```

## Customisation

Before knowing how to cutomise your widgets so they match your style, we have to understand what *is* a widget.
A widget is made of three things:
- a **Shadow**: optional, it provides a nice depth to our application and accentuate our widgets
- a **Shape**: defines the shape of the background. It also defines the borders, the size and where you can click/select the widget. To list a few common: Rectangle, Circle, RoundedRect...
- a **content**: the information of the widget, it can be the widget's own content or a child widget (did you know that we can put any widget inside a Button ?)

Those three layers are mixed together to create the widget. To customise our widgets we can therefore easily change one of those three layers.

### Common parameters

First, let's have a look to the common parameters that aren't part of the shadow, shape or content.

#### Position

With graphalama, as with pygame a position is a couple of integers, (0, 0) being the topleft corner and `(x, y)` being the `x`-st pixel on the `y`-st line of the screen.
Every time we'll need a position as an argument for a function it will be a tuple `(x, y)` of integers.

Graphalama has a powerfull positioning system for the widgets, whose goal is to be able to design resizable applications witout more effort.
A widget's position is defined by two elements: its coordinates `pos` and its `anchor`. The `anchor` defines where the widget is anchored,
that is, where it will stay when the window is resized and where it will be first put on the screen.

For instance, if the anchor ia `TOPLEFT`, the widget will always stay at the same distance of the top and the left of the screen, no matter how the window is resized.
Its `pos` will then be the topleft corner of the widget. This is the default anchor, so we can place our widgets like we would do in most frameworks.

But say, we want a widget to always stay on the right side of the screen (like a "next" button), we just need to set the anchor to `RIGHT`
and our widget will stay at say 5 pixels from the right border even if we resize the window. Here `pos` will be the middle point on the right
of the widget. Here's a small drawing to explain it:

![](assets/right_anchor.png)

This button was created in a 800x500 window with

```python
Button("Next ->", quit, (770, 250), RoundedRect((200, 100)), bg_color=GREEN, anchor=RIGHT)
```

If we resize the window, it will always stay 30px from the right edge.

Two conclude, there are four things to remember:
- We can anchor a widget to one or more side of the window by setting the anchor to `RIGHT`, `LEFT`, `TOP` and `BOTTOM`.
Those constants are defined in `graphalama.constants`.
- We can combine the anchors with the pipe symbol, `|`, so we anchor a widget to the top right corner with `anchor=TOP|RIGHT`
- Not only the position, but also the size can be controled with anchors. If `anchor=LEFT|RIGHT`, the distance from the widget
to both side wil stay constant. Therefore the width will stretch if the window gets bigger and shrink if the window gets smaller.
- The `pos` of a widget always reflect the anchor: if the anchor is `BOTTOM|RIGHT` the `pos` will be teh bottom right of the widget.
If it is `LEFT`, `pos` will be the midleft of the widget. However we can always get the topleft of the widget with `widget.topleft`.
`widget.x` and `widget.y` also work.

That's everything for a very technical part, but it gets very intuitive once we get used to it and saves a lot of time.

#### Color

Graphalama has a great range of options for coloring widgets. No more of the common monochromatic widgets without transparency!

Usually widgets have three colors:
- `color`, the foreground color, ie. the text of a button
- `bg_color`, the background color, that's how the shape is filled
- `border_color`, the color of the border

Those three colors accepts different types, and you can pass any of them at any time:
- a RGB or RGBA tuple of integers between 0 and 255, you can find a lot of pre-defined colors in `graphalama.constants`
- a `Gradient` from one color to another (both can be RGB or RGBA)
- a `MultiGradient` to have a multicolored gradient
- a `ImageBrush` to draw an image
- a `ImageListBrush` to draw multiple images and swap between them.

![Example of differents colors and transparency](assets/color_example.PNG)
You can get the code for this example [here](assets/color_example.py).

What actually is a color ?
It is an object with a `paint` method that takes a `Surface` and colorize it.

##### RGB or RGBA tuples
Tuples like `(R, G, B)` or `(R, G, B, A)` are both valid colors, with `0 <= R, G, B, A <= 255`.
They are automatically converted to a `Color` object for ease of use. And as expected, they fill the whole area with the same color.

##### Gradients
A `Gradient(start, end, horizontal)` will fill a shape with a linear interpolation between the two colors.
`start` and `end` must be two tuples and if `horizontal == False`, the gradient will be drawn verticall,
so `start` will be at the top of the shape and `end` at the bottom.

##### Multigradients
):
A `Multigradient(*colors, positions=None, horizontal=True)` is like a `Gradient` but with more than two colors.

Example for an equaly spaced blue-yellow-orange-red gradient:
```
from graphalama.constants import BLUE, YELLOW, ORANGE, RED
MultiGradient(BLUE, YELLOW, ORANGE, RED)
```

You can also choose where the color points are. Here the orange-red part
will take the left half of the gradient whereas the blue-yellow and yellow-orange
would take only a fourth. The positions are between 0 and
```
MultiGradient(BLUE, YELLOW, ORANGE, RED, positions=(0, 1/4, 1/2, 1))
```
Of course, the number of positions must match the number of colors, but the positions do nat have to start at 0 and
end at 1. For instance is the positions are `(1/2, 2/3, 1)`, the first half of the surface will be exactly of the first color.

If `horizontal == False`, then the gradient is drawn top to bottom and not left to right.


### Custom Shape

Let's start again with our play button: `Button("PLAY", bg_color=GREEN, function=play)`.
The default shape is a `Rectangle`, So that's what it would look like:
![](assets/shape_simplest.PNG)

But you're not limited to rectangles, you can have a rounded rectangle, a circle
or even any parametric shape or any custom shape (we'll come back on creating your custom shapes later)

![](assets/shapes_examples.PNG)

You can get the code for this example [here](assets/shapes_example.py).


There are quite a few things to note here.
First of all, if we don't precise the position of the widgets, the will just stack verticaly n the center of the screen.
Then, we can give any shape to a widget. Every widget can have any shape, we just need to pass it as the `shape` argument in the object initalisation.
Shapes are found in `graphalama.shapes`, they all accept the same parameters :
- First the `size` of the widget, if none is given the widget will just fit its content
- Then the argments for the specific shape, ie. the rounding of the rectangle, the equations of the parametric curve...
- The `border` comes next, which specifies the width of the widget's border
- Finally comes `min_size` and `max_size`, those are the minimum and maximum size the widget can take if the window is resized

Finally, a shape object is a container for all the information about the size, border, shape.
If we want to change the size of a widget at runtime, we need to set ` widget.shape.size`, `widget.shape.height` or `widget.shape.width`.

> Therefore a shape can be set only to ONE widget. Widgets can not share references to the same shape.

### Custom content

There's gonna be some heavy work there, so I wont comment on the current system.

### Custom shadow

Shadows put a highight on our widgets, by creating a deeper contrast between the widget and the background.
Shadows are fond in `graphalama.shadows` and they are optional: you can pass `NoShadow` to any widget and...
it won't show nor calculate any shadow. Otherwise, a shadow accepts 4 optional arguments. Here's the signature

```python
Shadow(dx=2, dy=2, blur=2, strength=100)
```

The two firsts are the offset between the widget and the shadow,
they shift the shadow by `dx` and `dy` to the right and down respectively.

Optionnaly, you can blur the shadow, for a smoother result.
If blur is 0 the shadow will have the same shape as the widget with the same sharp borders.
If blur is positive, then a gaussian blur si applied to the shape. Note that this requires pillow.
If you don't have pillow installed, it won't blur the shadow, without raising any exception.

The strength is an integer between `0` and `255`, it's how dark the shadow is.

Here's an example from [shapes_example](assets/shapes_example.py), where the widgets are

```python
wid = WidgetList([
Button("Random Shadow", change_shadow, (400, 250), RoundedRect((400, 250), 50, 1, 2),
bg_color=(150, 232, 230), border_color=NICE_BLUE, shadow=NoShadow(), anchor=ALLANCHOR),
Widget((55, 35), RoundedRect(), bg_color=Monokai.PINK, anchor=TOP),
Widget((150, 35), RoundedRect(), bg_color=Monokai.BLUE, shadow=Shadow(5, 5, 0), anchor=TOP),
Widget((245, 35), RoundedRect(), bg_color=Monokai.ORANGE, shadow=Shadow(-5, 5, 10, 200), anchor=TOP),
Widget((340, 35), RoundedRect(), bg_color=Monokai.GREEN, shadow=Shadow(5, 5, 5), anchor=TOP),
Widget((435, 35), RoundedRect(), bg_color=Monokai.YELLOW, shadow=Shadow(0, 0, 10), anchor=TOP),
Widget((530, 35), RoundedRect(), bg_color=Monokai.PURPLE, shadow=Shadow(5, 5, 0, 200), anchor=TOP),
Widget((625, 35), RoundedRect(), bg_color=Monokai.BROWN, shadow=Shadow(-2, -2, 5), anchor=TOP),
Widget((720, 35), RoundedRect(), bg_color=Monokai.BLACK, shadow=Shadow(5, 20, 5), anchor=TOP),
SimpleText("Shadow(dx, dy, blur, strength)", (400, 490), anchor=BOTTOM)
])
```

![](assets/shadow_example.PNG)

## Other elements

### Animations

### Drawings

### Font

There's gonna be some heavy work there, so I wont comment on the current system.

## Widgets

### Button

### SimpleText

## Tips

### The `Pos` class

## Creating your own stuff

### Widgets

###




Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

graphalama-0.0.1rc0.tar.gz (31.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

graphalama-0.0.1rc0-py3-none-any.whl (30.6 kB view details)

Uploaded Python 3

File details

Details for the file graphalama-0.0.1rc0.tar.gz.

File metadata

  • Download URL: graphalama-0.0.1rc0.tar.gz
  • Upload date:
  • Size: 31.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.21.0 setuptools/40.6.3 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.2

File hashes

Hashes for graphalama-0.0.1rc0.tar.gz
Algorithm Hash digest
SHA256 2293e2f3a6f8ea15cc4cee4a008119c095af9db43f01f33c55c4c275fed208f7
MD5 7360ee65bc018793ea936159fc75425f
BLAKE2b-256 8b35fc9359176b67d523d3434e317210a7908ee750286bab5816c8f83f1f7b49

See more details on using hashes here.

File details

Details for the file graphalama-0.0.1rc0-py3-none-any.whl.

File metadata

  • Download URL: graphalama-0.0.1rc0-py3-none-any.whl
  • Upload date:
  • Size: 30.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.21.0 setuptools/40.6.3 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.2

File hashes

Hashes for graphalama-0.0.1rc0-py3-none-any.whl
Algorithm Hash digest
SHA256 2a4f277e239eb0807919ef4ec32270e4212b0a8ed8d50beec27b536900d5cdc6
MD5 87dfbe276fd922070216da68d37b82d0
BLAKE2b-256 ce1c953a91ad93aa2f92cafb9cc2547a99774daa7a48fdd9c11d371bc61fac08

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page