Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
- Remove `Group.draw`, which drew every widget at the same position.
- Replace the eight button classes with four: `Button` (rounded text button), `ImageButton`, `IconButton` and `IconToggle`. All click on a left press and release inside the button, cancel on window focus loss, and expose `pressed`.
- `Button` sizes itself to its text (or a fixed `width` with `align`), derives hover and pressed shades from `color`, picks black or white text for contrast unless `text_color` is given, and defaults to `border_radius=6` and `padding=(16, 8)`. `outline_color` draws a 2 px outline. `button.text` is a property that resizes the button.
- `Label(text, color="white", *, font=None, align="left")` draws exactly at (x, y); `draw(surface, x, y, width=None)` aligns the text within `width`. Setting `text` or `color` re-renders only when the value changes.
- `Label(text, color="black", *, font=None, align="left")` draws exactly at (x, y) (the color is now optional); `draw(surface, x, y, width=None)` aligns the text within `width`. Setting `text` or `color` re-renders only when the value changes.
- `IconButton`'s clickable area and hover backdrop are the icon plus `padding` on each side, starting at (x, y).
- Add `utils.shade(color, amount)`; `utils.best_contrast_color` uses perceived brightness and returns a `pygame.Color`.
- Remove the duplicate root `src/demo.py` and `src/assets/`; the packaged `pygameuikit-demo` is the demo.
Expand All @@ -21,6 +21,8 @@
- `Dropdown(options, *, index=0, on_change=None, width=None, font=None, color, text_color, highlight_color, outline_color, border_radius)` replaces `ComboBox`. It draws itself (no longer a `Button` subclass) with a drawn chevron, highlights the hovered and selected options, and can be driven with Up/Down, Enter/Space and Escape while open. While open it consumes mouse events over itself and its menu; a click elsewhere, Escape or window focus loss closes it. `on_change(value)` runs only when the user picks a different option.
- When a widget consumes a `MOUSEMOTION` (an open dropdown menu under the pointer), `Group` marks the widgets after it as not hovered, so nothing underneath an open menu stays highlighted or sets the cursor.
- `BarChart(values, *, labels=None, max_value=None, colors=None, font=None, label_color="black", speed=200)`: `values` is a property (a tuple; setting it replaces all bars and regenerates automatic colors when the count changes), `set_value(index, value)` updates one bar, `colors` cycle if there are fewer than bars, and `speed` is the animation rate in value units per second. Bars are solid with rounded tops.
- The demo (`pygameuikit-demo`) is rewritten as a showcase: one card per widget family with its main options and API, and an event log that shows each callback as it fires. `--screenshot PATH` saves a frame as a PNG and exits without opening a window.
- The README has a short guide to the main loop and a reference for every widget.

### Upgrading from 0.2

Expand Down
59 changes: 39 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Small UI widgets for Pygame: buttons, sliders, dropdowns, text inputs, labels, and animated bar charts. You control the layout; the kit handles interaction and drawing.

![The PygameUIKit demo: cards showing every widget and an event log](https://raw.githubusercontent.com/Times0/PygameUIKit/master/images/img.png)

Requires Python 3.11+ (tested through 3.15). Uses [pygame-ce](https://pyga.me/), which provides the familiar `import pygame` API. Use a fresh environment when upgrading from classic `pygame`; the two packages share the same import name.

## Install
Expand All @@ -22,51 +24,68 @@ cd PygameUIKit
uv run pygameuikit-demo
```

uv installs Python and dependencies automatically. The demo is also included in the installed package: `pygameuikit-demo` or `python -m PygameUIKit.demo`.
uv installs Python and dependencies automatically. The demo is also included in the installed package: `pygameuikit-demo` or `python -m PygameUIKit.demo`. It shows every widget with its main options, and an event log shows each callback as it fires. Its source, [`demo.py`](https://github.com/Times0/PygameUIKit/blob/master/src/PygameUIKit/demo.py), is meant to be copied from. `--screenshot demo.png` saves one frame without opening a window.

## Use it

```python
import pygame
from PygameUIKit import Button, Group
from PygameUIKit import Button, Dropdown, Group, Label, Slider

pygame.init()
screen = pygame.display.set_mode((640, 360))
clock = pygame.time.Clock()

ui = Group()
hello = Button("Say hello", lambda: print("Hello!"), color=(85, 145, 92), ui_group=ui)
title = Label("Volume")
volume = Slider(0, 100, value=50, show_value=True, on_change=print, ui_group=ui)
quality = Dropdown(["Low", "Medium", "High"], on_change=print, ui_group=ui)


def mute():
volume.value = 0 # setting a value never calls on_change


mute_button = Button("Mute", mute, ui_group=ui)

running = True
while running:
dt = clock.tick(60) / 1000
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
else:
ui.handle_event(event)
if not running:
break
elif not ui.handle_event(event):
pass # no widget used this event: handle your own keys and clicks here
ui.update(dt)
screen.fill((30, 32, 36))
hello.draw(screen, 240, 150)

screen.fill("white")
title.draw(screen, 20, 20)
volume.draw(screen, 20, 60, width=240)
mute_button.draw(screen, 340, 45)
quality.draw(screen, 20, 100) # dropdowns last, so their menu covers other widgets
pygame.display.flip()

pygame.quit()
```

Add widgets with `ui_group=ui`, forward each event once, and call `ui.update(dt)` with seconds. `ui.handle_event(event)` returns `True` when a widget consumed the event (for example a button click or typing into a focused text input), so you can skip your own handling for it. The group also sets the mouse cursor for the hovered widget. Draw each widget at your chosen position; sliders and charts also take `width` and `height`. Draw dropdowns last so their menus appear above other widgets. Callbacks (`on_click`, `on_change`, `on_submit`) run only for user interaction, never when you set `value` or `text` yourself.
- **Group.** Pass `ui_group=ui` to every interactive widget. Forward each event once with `ui.handle_event(event)`. It returns `True` when a widget used the event (a button click, typing into a focused text input), so check it before your own handling. Call `ui.update(dt)` with seconds every frame; it animates charts and repeats held keys. After mouse motion, the group sets the cursor of the hovered widget.
- **Drawing.** There is no layout system. Call `widget.draw(surface, x, y, ...)` every frame. The widget stores the drawn area in `widget.rect`, which is also its clickable area. Draw dropdowns last.
- **Callbacks.** `on_click()`, `on_change(value)` and `on_submit(text)` are keyword arguments, and attributes you can reassign; `None` means no callback. They run only for user interaction, never when you set `value` or `text` yourself.
- **Colors and fonts.** Colors are anything `pygame.Color` accepts. Pass `font=pygame.font.Font(...)` for a particular font or character set; default fonts are created when the widget is.

### Widgets

- `Button("Play", on_click, color="seagreen")`: rounded text button sized to its text; set `text` to relabel it. Options: `width` and `align`, `outline_color`, `border_radius`, `padding`, `text_color` (black or white for contrast by default).
- `ImageButton(image, on_click, hover_image=None, text="")`: an image, optionally with centered text.
- `IconButton(icon, on_click)`: an icon with a translucent backdrop on hover.
- `IconToggle(icon_off, icon_on, on_change)`: switches icons on click and calls `on_change(value)`; read or set `value`.
- `Slider(0, 100, 5, value=50, on_change=callback, show_value=True)`: drag, click or use the arrow keys while hovering; read or set `value`.
- `TextInput(placeholder="Name", width=200, on_change=callback, on_submit=callback)`: Enter calls `on_submit(text)`, Escape unfocuses; read or set `text` and `focused`.
- `Dropdown(["One", "Two"], on_change=callback)`: pick with the mouse or Up/Down and Enter; read or set `value` (the option) or `index`.
- `Label("Hello", "white")`: draws at exactly (x, y); `draw(screen, x, y, width)` aligns it within `width` using `align="left" | "center" | "right"`. Set `text` to change it.
- `BarChart([10, 20, 30], labels=["A", "B", "C"], max_value=50)`: animated bars; `set_value(index, value)` or set `values`. Values must be non-negative.
Options after `*` are keyword-only, and every widget also takes `ui_group`.

Text inputs use Pygame's `TEXTINPUT` events for Unicode text and `KEYDOWN` for editing. Forward both through `Group`; don't also forward the same events separately to the input. Pass a custom `pygame.font.Font` if you need a particular font or character set.
- `Label(text, color="black", *, font=None, align="left")`. Draws at exactly (x, y). `draw(surface, x, y, width=None)` aligns the text within `width` (`"left"`, `"center"` or `"right"`). Properties: `text`, `color`.
- `Button(text, on_click=None, *, color=(59, 130, 246), text_color=None, outline_color=None, border_radius=6, padding=(16, 8), font=None, width=None, align="center")`. Sized to its text unless `width` is set; `align` places the text within `width`. Hover and pressed shades come from `color`, and the text is black or white for contrast unless `text_color` is set. Properties: `text` (resizes the button), `hovered`, `pressed`. Callback: `on_click()`.
- `ImageButton(image, on_click=None, *, hover_image=None, text="", text_color="white", font=None)`. An image, swapped for `hover_image` while hovered or pressed, with optional centered `text`.
- `IconButton(image, on_click=None, *, hover_color=(128, 128, 128), hover_alpha=128, padding=6, border_radius=6)`. An icon with a translucent backdrop while hovered. The clickable area is the icon plus `padding`, starting at (x, y).
- `IconToggle(image_off, image_on, on_change=None, *, value=False, ...)`. Like `IconButton`, but each click switches `value` and the icon. Callback: `on_change(value)` with the new bool.
- `Slider(min, max, step=1, *, value=None, on_change=None, show_value=False, font=None, fill_color, track_color, knob_color, text_color)`. `draw(surface, x, y, width=200, height=6)`. Drag or click the track; Left/Right, Home and End work while it is hovered. `value` is clamped and snapped to `step`, and `show_value` prints it to the right of the track. Callback: `on_change(value)`.
- `TextInput(text="", *, placeholder="", width=200, font=None, text_color, placeholder_color, background_color, border_color, focus_color, border_radius=4, on_change=None, on_submit=None)`. A fixed-width, single-line field. Click it to focus it; Enter calls `on_submit(text)` and Escape unfocuses. Properties: `text`, `focused`. Callbacks: `on_change(text)` after each edit, `on_submit(text)`. It reads `TEXTINPUT` events for Unicode text and `KEYDOWN` for editing, so forward both, once, through the group.
- `Dropdown(options, *, index=0, on_change=None, width=None, font=None, color, text_color, highlight_color, outline_color, border_radius=6)`. Click to open the menu, then pick with the mouse or with Up/Down and Enter. Escape or a click elsewhere closes it. Properties: `value` (the option), `index`, `is_open`. Callback: `on_change(value)` when the user picks a different option.
- `BarChart(values, *, labels=None, max_value=None, colors=None, font=None, label_color="black", speed=200)`. `draw(surface, x, y, width=300, height=100)`. Bars move toward their values at `speed` units per second during `ui.update(dt)`. `set_value(index, value)` changes one bar; setting `values` replaces them all. Values must be non-negative. Without `labels`, each bar shows its value; without `max_value`, the tallest bar fills the height. `colors` cycle.

## Develop

Expand Down
Binary file modified images/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed images/logo.jpeg
Binary file not shown.
Loading
Loading