드래그 앤 드롭

Ren'Py에는 마우스로 화면의 요소를 움직일 수 있는 끌어 놓기 디스플레이어블이 있습니다. 다음과 같은 용도로 사용할 수 있습니다.

  • 사용자가 창을 옮기고 그 위치를 저장하도록 하기.

  • 화면에서 카드를 끌어야 하는 카드 게임. 예: 솔리테어.

  • 인벤토리 시스템.

  • 끌어서 순서를 바꾸는 시스템.

끌어 놓기 디스플레이어블로 이러한 기능과 그 밖의 끌어 놓기 기능을 구현할 수 있습니다. 관련 클래스는 두 개입니다. Drag 클래스는 화면에서 끌 수 있는 요소, 다른 요소를 놓을 수 있는 대상, 또는 두 기능을 모두 하는 요소를 나타냅니다. DragGroup 클래스는 Drag의 그룹을 나타냅니다. 끌어 놓기가 이루어지려면 양쪽 Drag가 같은 그룹에 속해야 합니다.

끌어 놓기 시스템은 스크린 언어로 사용하거나 디스플레이어블로 직접 사용할 수 있습니다. 생성한 Drag를 나중에 참조할 필요가 없다면 스크린 언어가 적합합니다. 예를 들어 사용자가 화면에 배치하는 창을 끌 수 있게 할 때가 그렇습니다. 생성 후에도 Drag를 참조해야 한다면 직접 Drag를 만들고 DragGroup에 추가하는 편이 나은 경우가 많습니다.

놓기

Ren'Py가 놓기를 처리하는 방식은 두 가지입니다.

  • mouse_drop이 참이면 마우스 커서 바로 아래의 대상에 Drag를 놓습니다.

  • mouse_drop가 기본값인 거짓이면 Drag와 가장 많이 겹치는 드롭 대상에 놓습니다.

끌기를 시작할 때 focus_mask를 사용하는 것과 달리, 놓을 때는 투명 픽셀을 포함한 끄는 요소와 놓는 대상의 사각형 영역 전체를 고려합니다. 이를 고려해 끌어 놓기 디스플레이어블을 대체로 사각형에 가깝게 설계해야 할 수 있습니다.

디스플레이어블

class Drag(d=None, drag_name=None, draggable=True, droppable=True, drag_raise=True, dragging=None, dragged=None, dropped=None, drag_handle=(0.0, 0.0, 1.0, 1.0), drag_joined=..., clicked=None, hovered=None, unhovered=None, mouse_drop=False, **properties)

자신을 감싸는 영역 안에서 끌 수 있는 객체를 나타내는 디스플레이어블입니다. Drag는 다른 Drag를 놓을 수 있는 영역을 나타낼 수도 있습니다.

Drag는 부모 안에서 움직일 수 있습니다. 일반적으로 부모는 Fixed() 또는 DragGroup이어야 합니다.

Drag에는 자식이 하나 있습니다. 자식의 상태는 끌어 놓기 작업의 상태를 반영합니다.

  • selected_hover - 끌고 있는 동안입니다.

  • selected_idle - 다른 요소를 놓을 수 있을 때입니다.

  • hover - 마우스를 클릭하면 끌기가 시작되는 상태입니다.

  • idle - 그 밖의 상태입니다.

끌기 핸들은 자식 내부의 사각형입니다. 끌거나 클릭하려면 마우스가 핸들 내부 픽셀 위에 있어야 합니다. focus_mask가 True이면 해당 픽셀이 투명해서는 안 됩니다.

새로 만든 끌기 요소는 기본 DragGroup에 추가됩니다. 하나의 DragGroup에만 속할 수 있으므로 두 번째 그룹에 추가하면 첫 번째 그룹에서 제거됩니다.

Drag를 처음 렌더링할 때 소속 DragGroup에서 위치를 알 수 없으면 표준 배치 알고리즘으로 왼쪽 위 모서리의 위치를 계산합니다. 위치를 한 번 계산한 뒤에는 배치 속성을 무시하고 Drag 안에 저장된 위치를 사용합니다.

Drag에 트랜스폼을 직접 적용하지 않아야 합니다. 대신 Drag의 자식에 적용하세요.

d

지정하면 이 Drag의 자식입니다. child 스타일이 None이 아니라면 Drag는 이 인수보다 해당 스타일을 우선 사용합니다.

drag_name

None이 아니라면 이 끌기 요소의 이름입니다. 끌기 객체의 drag_name 속성으로 접근할 수 있습니다. 같은 이름의 Drag가 DragGroup에 있거나 있었으면 그 Drag의 위치를 시작 위치로 사용합니다.

draggable

참이면 화면에서 마우스로 이 Drag를 끌 수 있습니다.

droppable

참이면 이 Drag 위에 다른 Drag를 놓을 수 있습니다.

drag_raise

참이면 끌 때 이 Drag를 맨 위로 올립니다. 다른 Drag와 연결되어 있으면 연결된 Drag를 모두 올립니다.

activated

Drag 위에서 마우스 버튼을 누를 때 호출하는 콜백 또는 콜백 리스트입니다. 끌고 있는 Drag 리스트를 인수 하나로 받습니다. 반환값은 무시합니다.

dragging

Drag를 끄는 동안 호출하는 콜백 또는 콜백 리스트입니다. 끌고 있는 Drag 리스트를 인수 하나로 받습니다. None 이외의 값을 반환하면 그 값이 상호작용의 결과가 됩니다.

dragged

Drag를 끈 뒤 호출하는 콜백 또는 콜백 리스트입니다. 인수 두 개를 받습니다. 첫 번째는 끌고 있는 Drag 리스트이며, 두 번째는 놓는 대상 Drag 또는 놓기가 이루어지지 않았을 때의 None입니다. None 이외의 값을 반환하면 그 값이 상호작용의 결과가 됩니다.

dropped

이 Drag 위에 다른 Drag를 놓으면 호출하는 콜백 또는 콜백 리스트입니다. 인수 두 개를 받습니다. 첫 번째는 놓는 대상 Drag이며, 두 번째는 끌고 있는 Drag 리스트입니다. None 이외의 값을 반환하면 그 값이 상호작용의 결과가 됩니다.

같은 이벤트로 dragged와 dropped 콜백이 모두 발생하면 dragged가 None을 반환한 경우에만 dropped를 호출합니다.

clicked

Drag를 움직이지 않고 클릭하면 호출하는 콜백입니다. 클릭한 Drag를 인수 하나로 받습니다. 놓기 대상에도 포커스를 주고 클릭할 수 있습니다. None 이외의 값을 반환하면 그 값이 상호작용의 결과가 됩니다.

alternate

데스크톱에서는 마우스 오른쪽 버튼으로 클릭할 때, 모바일에서는 움직이지 않고 길게 누를 때 실행하는 액션입니다. 모바일에서 너무 일찍 실행된다면 config.longpress_duration을 늘려야 할 수 있습니다.

hovered

Drag가 포커스를 얻을 때 실행할 액션입니다.

unhovered

Drag가 포커스를 잃을 때 실행할 액션입니다.

tooltip

Drag 위에 마우스를 올렸을 때 표시할 툴팁입니다.

drag_handle

자식 내부의 끌기 핸들 위치를 나타내는 (x, y, width, height) 튜플입니다. 이 튜플은 위치 값을 받습니다.

drag_joined

현재 Drag를 인수로 받아 호출됩니다. 함께 끌 요소들을 나타내는 [ (drag, x, y) ] 튜플 리스트를 반환해야 합니다. x와 y는 Drag 사이의 상대 오프셋이며, 현재 Drag의 모서리를 기준으로 한 값이 아닙니다. drag는 연결할 Drag 객체 또는 해당 Drag의 drag_name입니다.

drag_offscreen

화면 밖으로 끌 수 있는 조건을 결정합니다. drag_joined를 사용하거나 크기가 바뀌는 Drag에 화면 밖 끌기를 허용하면, Drag가 화면에서 완전히 사라져 다시 가져올 방법이 없어질 수 있으므로 주의해야 합니다.

다음 중 하나여야 합니다.

False

화면 밖으로 끌 수 없습니다. 기본값입니다.

True

어느 방향으로든 화면 밖으로 끌 수 있습니다.

"horizontal"

가로 방향으로만 화면 밖으로 끌 수 있습니다.

"vertical"

세로 방향으로만 화면 밖으로 끌 수 있습니다.

(width, height)

width와 height는 모두 정수여야 합니다. (width, height) 크기의 부분이 화면에 남아 있는 동안 화면 밖으로 끌 수 있습니다. 따라서 (100, 100)이면 나머지 부분이 화면 밖으로 나가도 최소 100x100픽셀 영역은 화면에 남습니다. 끄는 디스플레이어블의 너비와 높이를 지정하면 화면 밖 끌기를 전혀 허용하지 않는 것과 같습니다.

(min_x, max_x, min_y, max_y)

min_x, max_x, min_y, max_y는 각각 정수입니다. min_x는 왼쪽 경계와의 픽셀 거리, max_x는 오른쪽 경계와의 픽셀 거리입니다. min_y와 max_y도 각각 위쪽과 아래쪽 경계에 같은 방식으로 적용됩니다. Drag의 가장자리 중 하나가 지정한 경계에 닿을 때까지 이동할 수 있습니다. (0, 0, 0, 0)은 화면 밖 끌기를 전혀 허용하지 않는 것과 같습니다.

예를 들어 (-100, 200, 0, 0)은 화면 왼쪽으로 100픽셀, 오른쪽으로 200픽셀까지 벗어나도록 허용하지만 위아래로는 화면 밖으로 끌 수 없습니다.

화면 내부에서 이동 범위를 제한하는 데 사용할 수도 있습니다. (200, -200, 200, -200)은 화면 가장자리에서 200픽셀 안쪽 범위에만 Drag가 있도록 제한합니다.

Drag가 머물러야 하는 추가 경계를 주위에 두는 것으로 생각할 수 있습니다. 이 경계는 화면 밖으로 나갈 수도 있습니다.

callable

drag_offscreen에 호출 가능 객체를 지정할 수 있습니다. 끈 위치의 왼쪽 위 모서리를 나타내는 x, y 두 인수를 받아야 하며, Drag가 위치해야 할 새 (x, y) 튜플을 반환해야 합니다. Drag를 움직일 때마다 자주 호출됩니다. 예를 들어 다음 함수는 300픽셀 간격으로 Drag의 위치를 맞춥니다.

def drag_snap(x, y):

    if y < 300:
        y = 0
    elif y < 600:
        y = 300
    else:
        y = 600

    return 200, y
mouse_drop

참이면 커서 아래의 첫 놓기 대상에 놓습니다. 기본값인 거짓이면 가장 많이 겹치는 대상에 놓습니다.

drop_allowable

이 대상이 현재 끌고 있는 Drag들을 받아들일지 결정하는 콜백입니다. 인수 두 개를 받습니다. 첫 번째는 허용 여부를 결정할 Drag이며, 두 번째는 끌고 있는 Drag 리스트입니다.

snapped

Drag의 스냅 애니메이션이 끝날 때 호출하는 콜백 또는 콜백 리스트입니다. 인수 네 개를 받습니다. 첫 번째는 스냅 애니메이션 중이던 Drag, 두 번째와 세 번째는 목표 x, y 좌표, 네 번째는 애니메이션이 정상 완료되었으면 True, 도중에 잡는 등의 이유로 중단되었으면 False인 불리언입니다. 예를 들어 다음 함수는 스냅 애니메이션이 중단되면 Drag의 start_x와 start_y를 원래 목표 위치로 설정합니다.

def snapped_callback(drag, x, y, completed):
    if not completed:
        drag.start_x = x
        drag.start_y = y

d를 제외한 모든 매개변수는 Drag 객체에서 같은 이름의 필드로 접근할 수 있습니다. 또한 렌더링된 뒤에는 다음 필드도 사용할 수 있습니다.

x, y

부모를 기준으로 한 Drag의 픽셀 단위 위치입니다.

start_x, start_y

부모를 기준으로 한 끌기 시작 위치입니다. 단위는 픽셀입니다.

grab_x, grab_y

Drag를 잡은 위치의 x, y 좌표입니다. 부모를 기준으로 하며 단위는 픽셀입니다.

last_drop

현재 Drag를 놓으면 받아들일 수 있는 마지막 Drag입니다. 유효한 Drag가 없으면 None입니다.

snapping

이 Drag가 스냅 애니메이션 중이면 True입니다.

w, h

Drag 자식의 픽셀 단위 너비와 높이입니다.

bottom()

이 디스플레이어블을 drag_group의 맨 아래로 내립니다.

set_child(d)

이 Drag의 자식을 d로 바꿉니다.

snap(x, y, delay=0, warper=None)

Drag의 위치를 바꿉니다. 표시 중이 아니면 즉시 바뀝니다. 표시 중이면 delay초 동안 이동하며 선택적으로 워퍼를 사용할 수 있습니다. 워퍼를 지정하지 않으면 선형으로 이동합니다.

top()

이 디스플레이어블을 drag_group의 맨 위로 올립니다.

class DragGroup(*children, **properties)

Drag 그룹을 나타냅니다. Drag는 자신의 DragGroup 경계로 제한됩니다. 같은 DragGroup 안의 Drag끼리만 놓기가 작동합니다. DragGroup 안에 있을 때만 Drag를 위로 올릴 수 있습니다.

DragGroup은 Fixed()와 같은 방식으로 배치됩니다.

DragGroup 생성자의 모든 위치 인수는 Drag여야 하며, 해당 DragGroup에 추가됩니다.

min_overlap

놓기를 허용하는 데 필요한 최소 겹침 픽셀 수를 나타내는 정수입니다.

add(child)

Drag여야 하는 child를 이 DragGroup에 추가합니다. 다른 모든 자식보다 위에 추가됩니다.

get_child_by_name(name)

drag_name이 name인 첫 번째 자식을 반환합니다.

remove(child)

이 DragGroup에서 child를 제거합니다.

예제

다음 say 스크린은 사용자가 창을 끌어 화면의 원하는 위치에 놓도록 합니다.

screen say(who, what):

    drag:
        drag_name "say"
        yalign 1.0
        drag_handle (0, 0, 1.0, 30)

        xalign 0.5

        window id "window":
            # Ensure that the window is smaller than the screen.
            xmaximum 600

            has vbox

            if who:
                text who id "who"

            text what id "what"

다음은 끌기가 게임 진행에 영향을 주는 방법을 보여 주는 더 복잡한 예제입니다. 끌기로 캐릭터를 특정 장소에 보내는 방법을 보여 줍니다.

init python:

    def detective_dragged(drags, drop):

        if not drop:
            return

        store.detective = drags[0].drag_name
        store.city = drop.drag_name

        return True

screen send_detective_screen:

    # A map as background.
    add "europe.jpg"

    # A drag group ensures that the detectives and the cities can be
    # dragged to each other.
    draggroup:

        # Our detectives.
        drag:
            drag_name "아이비"
            droppable False
            dragged detective_dragged
            xpos 100 ypos 100

            add "ivy.png"
        drag:
            drag_name "잭"
            droppable False
            dragged detective_dragged
            xpos 150 ypos 100

            add "zack.png"

        # The cities they can go to.
        drag:
            drag_name "런던"
            draggable False
            xpos 450 ypos 140

            add "london.png"

        drag:
            drag_name "파리"
            draggable False
            xpos 500 ypos 280

            add "paris.png"

label send_detective:
    "조사해야 해요! 누구를 어디로 보낼까요?"

    call screen send_detective_screen

    "좋아요. [detective] 탐정을 [city]에 보내죠."

더 복잡한 시스템을 제대로 구현하려면 상당한 프로그래밍 실력이 필요합니다.

as 절로 Drag를 변수에 연결하면 그 변수로 Drag의 메서드를 호출할 수 있습니다.

screen snap():

    drag:
        as carmen
        draggable True
        xpos 100 ypos 100
        frame:
            style "empty"
            background "carmen.png"
            xysize (100, 100)

            vbox:
                textbutton "런던" action Function(carmen.snap, 450, 140, 1.0)
                textbutton "파리" action Function(carmen.snap, 500, 280, 1.0)