Sonorous에서 변경 가능한 내용은 미리 지정된 훅(hook)을 통해 그려진다. 훅은 크게 블록 훅(block hook)과 스칼라 훅(scalar hook)으로 나뉜다. 블록 훅은 특정한 요소를 미리 지정된 조건에 따라 0번 이상 그리는데, 이를테면 특정 조건이 만족될 때만 요소가 보이거나(예: BGA가 있으면 BGA를 보여 준다), 조건에 따라 다른 요소를 보여 주거나(예: 제목이 있으면 제목을 보여 주고 없으면 "제목 없음"이라고 표시한다), 임의 길이의 목록을 보여 주거나(예: 곡 목록) 하는 용도로 쓰인다. 스칼라 훅은 문자열(예: 곡 제목)이나 숫자(예: 곡 레벨), 텍스쳐(예: 배너 이미지) 등을 반환한다. 일반적으로 블록 훅 안에서는 안에서만 의미가 있는 다른 블록 훅이나 스칼라 훅을 쓸 수 있게 된다.
Sonorous 스킨 시스템은 화면에 무언가를 그릴 때마다 클리핑 사각형(clipping rectangle)이라는 걸 관리한다. 이건 두 가지 용도로 쓰이는데 일단 상대 위치를 절대 위치로 바꾸는데 사용한다. 어느 요소가 사각형으로 구성되어 있는데 그 안에 다른 요소가 있으면 둘 다 절대 위치로 쓸 수도 있지만 바깥 요소에 해당하는 클리핑 사각형을 설정해서 다른 요소를 그 사각형을 기준으로 좌표를 지정하면 편리할 수도 있다. 또한 만약 클리핑 사각형이 충분히 작아져서 아무리 그리려고 해도 아무 것도 그릴 수 없게 된다면 더 그릴 게 남아 있어도 계속 진행할 이유가 없으므로 거기서 멈추는 역할도 한다. 둘을 종합하면 목록 같은 걸 유연하게 표시하는 용도로 쓰일 수 있다.
이제 이런 화면을 만들고 싶다고 하자:
+-----------------------+
| Title |
| +-------------------+ |
| +-------------------+ |
| +-------------------+ |
| +-------------------+ |
| ESC for exit |
+-----------------------+
여기에서 맨 위 요소는 위치는 고정되어 있으나 내용은 바뀔 수 있고, 목록은 위치도 내용도 바뀔 수 있으며 (다만 화면 상에서 고정된 공간 안에 위치해야 함만 보장) 맨 아래 요소는 위치와 내용 모두 고정되어 있다. 이 경우 스킨은 다음 순서대로 화면을 그린다.
- 고정된 위치, 이를테면
[20, 20]
에title
이라는 스칼라 훅이 반환한 문자열을 그린다. - 현재 클리핑 사각형을 저장한다. (안 저장하면 6에서 제 위치에 그릴 수 없게 된다.)
- 클리핑 사각형을 목록이 나올 사각형, 이를테면
[[20, 60], ["100%-20", "100%-40"]]
으로 지정한다. 아직 클리핑 사각형이 바뀌기 전이었으므로100%
는 전체 화면의 폭/높이를 가리킨다. entries
라는 블록 훅을 실행한다. 매 호출마다:- 클리핑 사각형의 맨 윗쪽에, 이를테면
[0, 0]
부터["100%", 20]
영역에entries.name
이라는 스칼라 훅이 반환한 문자열을 그린다. 꼭 20픽셀이 아니라 4-2에서 제대로 처리만 하면 높이가 매 호출마다 바뀌어도 무관하다. - 클리핑 사각형에서 맨 위 20픽셀(이나 여튼 4-1에서 그린 만큼의 높이)을 제거한다. 만약 사각형의 높이가 0이 되면 블록 훅은 강제로 종료될 것이다.
- 클리핑 사각형의 맨 윗쪽에, 이를테면
- 2에서 저장한 클리핑 사각형을 복구한다.
- 고정된 위치, 이를테면
["100%-20", 20]
에 "ESC to exit"라는 고정된 문자열을 그린다.
몇 가지 알아 두면 좋은 것들이 있다.
- 블록 훅은 문자열의 일부를 변화시키는 데도 쓸 수 있다.
- 스칼라 훅이 문자열이라면 보통 같은 이름의 블록 훅은 문자열이 비었는가 안 비었는가에 따라 다른 조건으로 호출한다. 만약 이런게 없다면 특수한 종류의 블록 훅(
$$text
등)을 사용할 수 있다. - 클리핑 사각형 바깥에 그리는 것 자체는 허용되므로 주의. 특수한 목적으로 쓸 수도 있겠지만 원치 않는다면 다른 요소 위에 나와야 하는 것들이 뒤에 그려져야 한다.
100%
와 같은 상대 위치는 위치(at
등)를 지정할 때는 클리핑 사각형을 기준으로 하지만, 텍스쳐 안에서 어느 부분을 그릴지(clip
) 지정할 때는 텍스쳐 크기를 기준으로 한다는 것을 주의. 둘 다 같이 써도 된다.
(안타깝게도) 아직 멀쩡한 문법이 있는 건 아니라서 임시로 JSON을 쓰고 있다. 고로 문법 하나라도 틀리면 바로 나가리가 난다. 특히 []
나 {}
맨 마지막에 콤마를 남긴다거나 해서 틀릴 수 있으므로 주의. 최상위 값은 오브젝트({"key": "value"}
같은 거)로 다음 값을 포함한다.
scalars
키는 현재 스킨에서 공통적으로 사용할 이미지나 문자열, 수치 등을 담는 오브젝트이다.nodes
키는 실제로 스킨에서 그릴 요소들을 담는 배열([1, 2, 3]
같은 거)이다. 어떤 요소는 다른 요소를 포함할 수도 있다.
JSON에는 주석 같은 게 없으므로(...) 임시방편으로 문자열 값을 주석으로 쓰기 위하여 무시하고 있다. 아무 위치에나 올 수 있는 게 아니라 정확히 요소가 올 수 있는 위치에만 올 수 있다는 걸 주의.
["목록을 그림",
{"$rect": ...},
{"$rect": ...},
"목록 아랫부분을 그림",
{"$rect": ...}]
{"$debug": "hello world"} -- 로그로 출력할 메시지
해당 요소를 그리려고 하면 화면에 그리는 대신 현재 클리핑 사각형과 주어진 메시지를 로그로 출력한다. 디버깅에 유용하다. Rust의 로깅 시스템을 사용하므로 Sonorous를 실행할 때 RUST_LOG=gfx::skin=4
환경변수가 설정되어 있어야만 출력된다.
{"$rect": "texname", -- 텍스쳐 이름(문자열)이나, 단색 사각형일 경우 `null`
"at": [...], -- 현재 클리핑 사각형에 상대적인 사각형 위치
"clip": [...], -- 텍스쳐 크기에 상대적인, 텍스쳐에서 실제로 그릴 부분을 나타내는 사각형
-- 단색 사각형에서는 사용할 수 없다.
"opacity": 1.0, -- 텍스쳐나 단색 사각형의 불투명도(0=투명, 1=불투명), 없다면 1.0
-- color에 알파 채널이 들어 있다면 함께 사용할 수 없다.
"color": "#7733aa" -- 텍스쳐에 적용될 색깔(후술) 또는 단색 사각형의 색깔
-- 알파 채널이 포함되어 있지 않을 경우 opacity와 함께 사용할 수 있다.
-- 단색 사각형일 경우 무조건 있어야 한다.
}
특정한 텍스쳐의 특정한 부분 또는 전체를 그리거나, 지정한 색깔로 사각형을 그린다. 텍스쳐는 스칼라 훅을 통해서 받아 온다. 텍스쳐 크기와 그려지는 크기가 다를 경우 텍스쳐는 그려지는 크기에 맞게 축소 및 확대된다. 가로와 세로의 축소/확대 폭은 다를 수 있다.
텍스쳐를 그릴 경우 color
값은 텍스쳐의 각 픽셀에 해당 색깔을 곱하는 데 사용된다. 이를테면 이 값이 blue
일 경우 이미지가 파래지며, black
이면 완전히 검정색이 된다(투명과는 다름). 어느 색깔을 다른 색깔에 곱하면 항상 본래 색깔보다 어두워지므로 이 값의 기본값은 흰색(white
).
{"$line": null, -- 무조건 null이어야 한다.
"from": [...], -- 선을 그릴 시작 위치
"to": [...], -- 선을 그릴 끝 위치
"opacity": 1.0, -- 선의 불투명도, 없다면 1.0
"color": "#7733aa" -- 선의 색깔
}
지정한 위치에서 다른 위치로 선을 그린다. 선의 두께는 항상 1픽셀이며 안티앨리어싱은 되지 않는다.
{"$clip": [...]} -- 현재 클리핑 사각형을 기준으로 새로 바꿀 사각형의 좌표
{"$cliptop": ...} -- 현재 클리핑 사각형에서 윗쪽 끝을 지정한 크기만큼 아래로 내림
{"$clipbottom": ...} -- 현재 클리핑 사각형에서 아랫쪽 끝을 지정한 크기만큼 위로 올림
{"$clipleft": ...} -- 현재 클리핑 사각형에서 왼쪽 끝을 지정한 크기만큼 오른쪽으로 옮김
{"$clipright": ...} -- 현재 클리핑 사각형에서 오른쪽 끝을 지정한 크기만큼 왼쪽으로 옮김
클리핑 사각형을 바꾼다. 일단 한 번 바뀐 클리핑 사각형은 블록 훅 등에 영향을 받지 않고 계속 남아 있다. 만약 클리핑 사각형을 열심히 바꾼 뒤 원래 값으로 돌리고 싶다면 후술할 클리핑 그룹을 써야 한다.
만약 클리핑 사각형이 축소되어 가로나 세로가 0 이하가 되면 현재 클리핑 그룹이 끝날 때까지 (또는 그룹이 없을 경우 스킨 맨 끝까지) 모든 요소를 무시한다. 정확히 보이는 만큼만 그린다고 생각하면 쉽다. 반대로 클리핑 사각형을 의도적으로 확대하는 것도 가능하나 이 경우 무한 루프나 성능 하락의 가능성이 있음을 주의.
{"$clip": ...}, -- 1: 전체 화면을 기준으로 새 클리핑 사각형을 설정한다.
[..., -- 2: 1에서 설정한 사각형이 유지된다.
{"$clip": ...}, -- 3: 1에서 설정한 사각형을 기준으로 새 클리핑 사각형을 설정한다.
...], -- 4: 3에서 설정한 사각형이 유지된다.
... -- 5: 1에서 설정한 사각형으로 되돌아간다.
그룹이 시작할 때에 클리핑 사각형을 저장해 놓았다가 끝나고 나서 사각형을 저장된 값으로 되돌린다. $clip
계열 요소와 함께 클리핑 사각형을 다룰 수 있는 유일한 요소로, 다른 요소는 (심지어 블록 훅조차) 클리핑 사각형에 영향을 끼치지 않는다. 이리 저리 많이 쓰이는 관계로 JSON 배열([1, 2, 3]
같은 거)로 표현된다.
{"$text": ..., -- 화면에 그릴 문자열 정보 (후술)
"at": [...], -- 문자열을 그릴 위치
"anchor": [...], -- 해당 위치가 문자열의 어느 부분에 해당하는가? 기본값은 왼쪽 위.
-- [0,0]이면 왼쪽 위, [1,1]이면 오른쪽 아래, [0.5,0.5]면 정중앙 등등등.
-- 문자열 "left"/"center"/"right"는 각각 왼쪽/중앙/오른쪽 **위**를 가리킨다.
"size": ..., -- 문자열의 픽셀 높이
"color": "#7733aa" -- 문자열의 색깔
}
화면에 문자열을 그린다. 다른 요소와는 달리 문자열은 가변 길이이므로 anchor
필드가 존재하여 주어진 위치를 문자열의 어느 부분으로 삼을지를 결정한다.
문자열은 여러 부분으로 이루어져 있을 수 있으며 다음 중 하나가 될 수 있다.
- 그냥 문자열 (
"asdf"
) - 다른 부분들을 포함하는 배열 (
["foo", {"$":"bar"}, "quux"}]
) - 스칼라 훅을 호출 (
{"$": "훅 이름"}
). 여기에는format
필드가 추가될 수 있으며 다음 중 하나여야 한다. 없으면 있는 그대로 출력.- 숫자 포매팅 문자열:
##000.00
,+####0
,..##0*100
등.#
는 해당하는 자릿수가 없으면 안 나와도 되는 자리고0
은 무조건 0이 나와야 한다...#
가 명시적으로 나오지 않았을 경우 자릿수를 넘치면 아랫자리만 나온다(0000
은 아래 네자리만 나옴). 맨 뒤에*100
이나/100
등을 붙여서 출력할 값에 특정 숫자를 곱하거나 나눌 수 있다(퍼센트의 경우*100
쓰면 됨). - 시간 포매팅 문자열:
0:00:00.0
,##00:00.0
등. 숫자와 유사하나:00:00
이나:00
이 붙으면 시분초 또는 분초 단위로 표시된다. 다른 옵션은 동일하다.
- 숫자 포매팅 문자열:
- 블록 훅을 호출 (
{"$$": ..., ...}
등). 후술할 블록 훅을 참고할 것.
{"$$": "foo", -- 블록 훅 이름
"$then": [...], -- 블록 훅이 호출되었을 때 표시할 요소
"$else": [...]} -- 블록 훅이 호출되지 않았을 때 표시할 요소
{"$$": "foo", -- 블록 훅 이름
"alt1": [...], -- 블록 훅이 "alt1"이라는 문자열로 호출되었을 때 표시할 요소
"alt2": [...], -- 블록 훅이 "alt2"이라는 문자열로 호출되었을 때 표시할 요소
"alt3": [...], -- 블록 훅이 "alt3"이라는 문자열로 호출되었을 때 표시할 요소
"$default": [...], -- 블록 훅이 위와는 다른 문자열로 호출되었을 때 표시할 요소
"$else": [...]} -- 블록 훅이 호출되지 않았을 때 표시할 요소
주어진 요소를 특정 조건이 만족될 때만 표시하거나, 요소를 여러 번 표시해 준다. 문자열을 출력할 때($text
요소)도 같은 문법으로 사용할 수 있다. 블록 훅 안에 있는 요소에서는 해당 조건이 만족될 때만 유효한 훅들을 사용할 수도 있다. (이를테면 블록 훅은 곡 제목이 있을 때 호출되고, 그 안에서 곡 제목을 반환하는 스칼라 훅이 있을 수 있다.)
블록 훅은 크게 두 종류로 나눌 수 있다.
- 단순 조건 및 반복:
$then
과$else
만 있을 경우$then
요소들은 호출때마다 무조건적으로 표시되고(여러번 실행 가능),$else
요소들은 호출이 한 번도 이루어지지 않을 때 표시된다. 호출을 많아도 한 번 하는 경우 단순 조건문이 된다.$else
는 생략 가능하다. - 다중 조건: 만약
$
로 시작하지 않는 요소들이 존재할 경우 호출할 때 함께 들어 오는 문자열과 비교해서 해당하는 게 있으면 해당 요소들을 표시한다. 만약 해당하는 요소들이 없으면$default
요소들이 표시된다(생략 가능). 이도 저도 아니고 호출 자체를 안 했을 경우$else
요소들이 사용된다(역시 생략 가능).
{"$$text": "foo", -- 스칼라 훅 이름
...}
$$
블록 훅과 동일하지만, 스칼라 훅으로부터 블록 훅을 생성해 낸다. 스칼라 훅이 있고 문자열이면 해당 문자열을 가지고 호출을 하기 때문에, 스칼라 훅이 특정한 문자열일 때만 보여야 하는 요소들에 유용하다.
{"$$len": "foo", -- 스칼라 훅 이름
...}
$$
블록 훅과 동일하지만, 스칼라 훅의 문자열 길이로부터 블록 훅을 생성해 낸다. 스칼라 훅이 있고 문자열이면 해당 문자열의 길이를 가지고 호출을 하기 때문에, 스칼라 훅이 빈 문자열인지 확인하는 데 유용하다. 이를테면 {"$$len": "foo", "0": [...], "$default": [...]}
같이.
TODO