Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ボタンやラベル、ダイアログ等のUI部品の実装例を追加 #7

Merged
merged 4 commits into from
Sep 30, 2024
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
6 changes: 3 additions & 3 deletions examples/01_event_handling.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import pygame
import asyncpygame as ap
import asyncpygame as apg


async def main(*, sdlevent: ap.SDLEvent, **kwargs):
async def main(*, sdlevent: apg.SDLEvent, **kwargs):
pygame.init()
pygame.display.set_caption("Event Handling")
pygame.display.set_mode((400, 400))
Expand All @@ -13,4 +13,4 @@ async def main(*, sdlevent: ap.SDLEvent, **kwargs):


if __name__ == "__main__":
ap.run(main)
apg.run(main)
8 changes: 4 additions & 4 deletions examples/02_countdown_on_cui.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import pygame
import asyncpygame as ap
import asyncpygame as apg


async def main(*, clock: ap.Clock, **kwargs):
async def main(*, clock: apg.Clock, **kwargs):
pygame.init()

count_from = 3
for i in range(count_from, -1, -1):
print(i)
await clock.sleep(1000)
ap.quit()
apg.quit()


if __name__ == "__main__":
ap.run(main)
apg.run(main)
6 changes: 3 additions & 3 deletions examples/03_countdown_on_gui.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import pygame
import pygame.font
from pygame.colordict import THECOLORS as COLORS
import asyncpygame as ap
import asyncpygame as apg


async def main(*, clock: ap.Clock, **kwargs):
async def main(*, clock: apg.Clock, **kwargs):
pygame.init()
pygame.display.set_caption("Countdown on GUI")
screen = pygame.display.set_mode((400, 400))
Expand All @@ -23,4 +23,4 @@ async def main(*, clock: ap.Clock, **kwargs):


if __name__ == "__main__":
ap.run(main)
apg.run(main)
6 changes: 3 additions & 3 deletions examples/04_countdown_in_a_more_practical_way.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import pygame
import pygame.font
from pygame.colordict import THECOLORS as COLORS
import asyncpygame as ap
import asyncpygame as apg


async def main(*, clock: ap.Clock, executor: ap.PriorityExecutor, **kwargs):
async def main(*, clock: apg.Clock, executor: apg.PriorityExecutor, **kwargs):
pygame.init()
pygame.display.set_caption("Countdown")
screen = pygame.display.set_mode((400, 400))
Expand All @@ -26,4 +26,4 @@ async def main(*, clock: ap.Clock, executor: ap.PriorityExecutor, **kwargs):


if __name__ == "__main__":
ap.run(main)
apg.run(main)
15 changes: 8 additions & 7 deletions examples/05_countdown_in_an_even_more_practical_way.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import pygame
import pygame.font
from pygame.colordict import THECOLORS as COLORS
import asyncpygame as ap
import asyncpygame as apg


async def countdown(*, count_from: int, draw_target: pygame.Surface, clock: ap.Clock, executor: ap.PriorityExecutor, priority, **kwargs):
async def countdown(*, count_from: int, draw_target: pygame.Surface, clock: apg.Clock, executor: apg.PriorityExecutor, priority, **kwargs):
center = draw_target.get_rect().center
font = pygame.font.SysFont(None, 400)
fgcolor = COLORS["black"]
Expand All @@ -17,16 +17,17 @@ async def countdown(*, count_from: int, draw_target: pygame.Surface, clock: ap.C
await clock.sleep(1000)


async def main(*, clock: ap.Clock, executor: ap.PriorityExecutor, **kwargs):
async def main(**kwargs):
pygame.init()
pygame.display.set_caption("Countdown")
screen = pygame.display.set_mode((400, 400))

executor.register(partial(screen.fill, COLORS["white"]), priority=0)
executor.register(pygame.display.flip, priority=0xFFFFFF00)
r = kwargs["executor"].register
r(partial(screen.fill, COLORS["white"]), priority=0)
r(pygame.display.flip, priority=0xFFFFFF00)

await countdown(count_from=3, draw_target=screen, clock=clock, executor=executor, priority=0x100)
await countdown(count_from=3, draw_target=screen, priority=0x100, **kwargs)


if __name__ == "__main__":
ap.run(main)
apg.run(main)
5 changes: 5 additions & 0 deletions examples/_uix/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ここには汎用性の低い部品達が置いてあります。
自分のアプリ用の部品を作る際はこれらを参考にしてください。

Modules in this directory are not flexible.
Use them as examples when creating your own ones.
3 changes: 3 additions & 0 deletions examples/_uix/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'''
examples内で使うためのwidget群。本格的な物になる予定は無い。
'''
65 changes: 65 additions & 0 deletions examples/_uix/anchor_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
__all__ = ('AnchorLayout', )

from typing import Self
from functools import partial

from asyncgui import Nursery, sleep_forever
from pygame import Surface, Rect


class AnchorLayout:
'''
.. code-block::

async with asyncpygame.open_nursery() as nursery:
image = Surface(...)
dest = Rect(...)
layout = AnchorLayout(nursery, image, dest, **common_params)

# Aligns the right edge of the image with the right edge of the layout.
layout.anchor_src = "right"
layout.anchor_dest = "right"

# Aligns the center of the image with the midtop of the layout.
layout.anchor_src = "center"
layout.anchor_dest = "midtop"

# You can change its image anytime.
layout.image = another_image

# You can move or resize the layout by updating the ``dest``.
dest.right = ...
dest.width = ...

# but you cannot assign another Rect instance to the layout.
layout.dest = another_rect # NOT ALLOWED
'''
def __init__(self, owner: Nursery, image: Surface, dest: Rect,
*, anchor_src="center", anchor_dest="center", **common_params):
'''
:param owner: AnchorLayout cannot outlive its owner. When the owner is closed, the sprite is destroyed.
:param anchor_src: This must be any of the ``Rect``s positional attribute names. (e.g. "topleft", "bottomleft", ...)
:param anchor_dest: Same as ``anchor_src``.
'''
self._dest = dest
self.image = image
self.anchor_src = anchor_src
self.anchor_dest = anchor_dest
self._main_task = owner.start(self._main(**common_params), daemon=True)

def kill(self):
self._main_task.cancel()

@property
def dest(self) -> Rect:
return self._dest

async def _main(self, *, priority, draw_target, executor, **unused):
with executor.register(partial(self._draw, draw_target.blit, self._dest, self), priority=priority):
await sleep_forever()

def _draw(getattr, blit, dest, self: Self):
image = self.image
blit(image, image.get_rect(**{self.anchor_src: getattr(dest, self.anchor_dest)}))

_draw = partial(_draw, getattr)
82 changes: 82 additions & 0 deletions examples/_uix/modal_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
__all__ = ('ask_yes_no_question', )

from typing import Unpack
from contextlib import asynccontextmanager
from functools import partial

import asyncgui
from pygame import Rect
from pygame.font import SysFont

from asyncpygame import CommonParams, block_input_events, Clock
from _uix.ripple_button import RippleButton
from _uix.anchor_layout import AnchorLayout


@asynccontextmanager
async def darken(**kwargs: Unpack[CommonParams]):
interpolate = kwargs["clock"].interpolate_scalar
draw_target = kwargs["draw_target"]
overlay_surface = draw_target.copy()
overlay_surface.fill("black")
set_alpha = overlay_surface.set_alpha
with kwargs["executor"].register(partial(draw_target.blit, overlay_surface), kwargs["priority"]):
async for v in interpolate(0, 180, duration=200):
set_alpha(v)
yield
async for v in interpolate(180, 0, duration=300):
set_alpha(v)


async def translate_rects_vertically(clock: Clock, rects, movement, duration):
org_ys = tuple(rect.y for rect in rects)
async for v in clock.interpolate_scalar(0, movement, duration=duration):
for rect, org_y in zip(rects, org_ys):
rect.y = org_y + v


async def ask_yes_no_question(
question, *, dialog_size: Rect=None, font=None, text_yes='Yes', text_no='No',
priority, **kwargs: Unpack[CommonParams]) -> bool:
'''
.. code-block::

result = await ask_yes_no_question("Do you like PyGame?", priority=0xFFFFFA00, **kwargs)
'''
bgcolor = "grey90"
executor = kwargs["executor"]
sdlevent = kwargs["sdlevent"]
clock = kwargs["clock"]
draw_target = kwargs["draw_target"]
if font is None:
font = SysFont(None, 40)

with block_input_events(sdlevent, priority):
async with darken(priority=priority, **kwargs), asyncgui.open_nursery() as nursery:
target_rect = draw_target.get_rect()
if dialog_size is None:
dialog_size = target_rect.inflate(-100, 0)
dialog_size.height = dialog_size.width // 2
dialog_dest = dialog_size.move_to(bottom=target_rect.top)
with executor.register(partial(draw_target.fill, bgcolor, dialog_dest), priority=priority + 1):
label = AnchorLayout(
nursery,
font.render(question, True, "black", bgcolor).convert(draw_target),
dialog_dest.scale_by(1.0, 0.5).move_to(top=dialog_dest.top).inflate(-10, -10),
priority=priority + 2, **kwargs)
yes_button = RippleButton(
nursery,
font.render(text_yes, True, "white"),
dialog_dest.scale_by(0.5, 0.5).move_to(bottomright=dialog_dest.bottomright).inflate(-20, -20),
priority=priority + 2, **kwargs)
no_button = RippleButton(
nursery,
font.render(text_no, True, "white"),
dialog_dest.scale_by(0.5, 0.5).move_to(bottomleft=dialog_dest.bottomleft).inflate(-20, -20),
priority=priority + 2, **kwargs)
rects = (dialog_dest, label.dest, yes_button.dest, no_button.dest, )
y_movement = target_rect.centery - dialog_dest.centery
await translate_rects_vertically(clock, rects, y_movement, duration=200)
tasks = await asyncgui.wait_any(yes_button.to_be_clicked(), no_button.to_be_clicked())
await translate_rects_vertically(clock, rects, -y_movement, duration=200)
return tasks[0].finished
37 changes: 37 additions & 0 deletions examples/_uix/progress_spinner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
__all__ = ('progress_spinner', )

from typing import Self
import math
import itertools
from functools import partial

import pygame
from pygame import Color, Rect


class Arc:
__slots__ = ('draw', 'start_angle', 'stop_angle', )

def __init__(self, draw_target, color, rect, start_angle, stop_angle, line_width):
self.draw = partial(self._draw, draw_target, color, rect, line_width, self)
self.start_angle = start_angle
self.stop_angle = stop_angle

def _draw(pygame_draw_arc, draw_target, color, rect, line_width, self: Self):
pygame_draw_arc(draw_target, color, rect, self.start_angle, self.stop_angle, line_width)

_draw = partial(_draw, pygame.draw.arc)


async def progress_spinner(dest: Rect, *, color="black", line_width=20, min_arc_angle=0.3, speed=1.0, **kwargs):
R1 = 0.4
R2 = math.tau - min_arc_angle * 2
next_start = itertools.accumulate(itertools.cycle((R1, R1, R1 + R2, R1, )), initial=0).__next__
next_stop = itertools.accumulate(itertools.cycle((R1 + R2, R1, R1, R1, )), initial=min_arc_angle).__next__
d = speed * 400

anim_attrs = kwargs["clock"].anim_attrs
arc = Arc(kwargs["draw_target"], Color(color), dest, next_start(), next_stop(), line_width)
with kwargs["executor"].register(arc.draw, kwargs["priority"]):
while True:
await anim_attrs(arc, start_angle=next_start(), stop_angle=next_stop(), duration=d)
Loading
Loading