SqueakByExample:11.4: Difference between revisions
Onionmixer (talk | contribs) (번역수정) |
Onionmixer (talk | contribs) (번역수정) |
||
Line 120: | Line 120: | ||
|[[image:overdrawBug.png]]||[[image:hairlineBug.png]] | |[[image:overdrawBug.png]]||[[image:hairlineBug.png]] | ||
|- style="text-align: center;" | |- style="text-align: center;" | ||
|그림 11.4: colour 로 2 번 채워진 | |그림 11.4: colour 로 2 번 채워진 십자의 중심부분||그림 11.5: 채워지지 않은 픽셀의 열을 보여주는 십자 모양의 Morph | ||
|} | |} | ||
Latest revision as of 19:05, 16 September 2013
나만의 morph 를 생성하고 그리기
Morph 의 합성을 이용해서 많은 종류의 편리하고, 재미있는 그래픽 표현을 만들 수 있지만, 가끔은 완전히 다른 뭔가를 만들고 싶을때가 있겠죠. 그런 작업을 하기 위해서는, Morph 의 서브클래스를 만들어서 drawOn: 메서드를 오버라이드override 해서 morph 의 모양새를 바꿀 수 있습니다.
Morphic 프레임워크는 화면에 morph 를 다시 표시해야하는 경우, 메시지 drawOn: 을 morph 에 보냅니다. drawOn: 메시지의 인자는, Canvas 클래스(또는 서브클래스)의 인스턴스로서, morph 가 스스로를 canvas 의 bounds[1] 내에 표시할거라는걸 예상할 수 있겠죠.
클래스 브라우저를 사용하여 Morph 로부터 상속받은 새로운 클래스 CrossMorph를 정의하십시오:
클래스 11.2: CrossMorph를 정의하기
Morph subclass: #CrossMorph
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'SBE--Morphic'
아래와 같이 drawOn: 메서드를 정의할 수 있습니다:
메서드 11.3: CrossMorph 그리기.
drawOn: aCanvas
| crossHeight crossWidth horizontalBar verticalBar |
crossHeight := self height / 3.0 .
crossWidth := self width / 3.0 .
horizontalBar := self bounds insetBy: 0 @ crossHeight.
verticalBar := self bounds insetBy: crossWidth @ 0.
aCanvas fillRectangle: horizontalBar color: self color.
aCanvas fillRectangle: verticalBar color: self color
Morph 에 bound 메시지를 전송하면, Rectangle 클래스의 인스턴스인 bounding box 가 반환되며, 이것은 morph 가 Rectangle 의 인스턴스 라는 의미가 됩니다. Rectangle 은 geometry 와 관련된 다른 사각형을 행성하는등의 많은 메시지를 사용할 수 있습니다; 위의 예제에서는, 인수로 점point 을 사용하는 insetBy: 메시지를 사용해서, 줄어든 높이로 첫번째 직사각형을 만들고, 줄어든 너비로 다른 직사각형을 만들고 있습니다.
새로운 morph 를 테스트 하기 위해 CrossMorph new openInWorld 를 실행하십시오.
실행하면 그림 11.3 과 같은 결과를 얻을 수 있습니다. 그렇지만 마우스에 반응하는장소-morph 를 잡기위해 클릭할 수 있는 영역-는 bounding box 전체가 된다는걸 알아차렸을거라고 생각합니다. 이걸 고쳐보도록 하죠.
Morphic 프레임워크는, 어떤 morph 가 마우스의 커서 아래에 있는지 알아내야할 필요가 있을때, 마우스의 포인터 아래에 bounding box 가 있는 모든 morph 에 대해서 containsPoint: 메시지를 보냅니다. 즉 마우스에 반응하는 영역을 morph 의 십자 부분으로 제한하고 싶다면, containsPoint: 메서드를 오버라이드 할 필요가 있는거죠.
클래스 CrossMorph 에서 다음 메서드를 정의하십시오:
메서드11.4: CrossMorph 의 반응 영역sensitive zone 설정하기
containsPoint: aPoint
| crossHeight crossWidth horizontalBar verticalBar |
crossHeight := self height / 3.0.
crossWidth := self width / 3.0.
horizontalBar := self bounds insetBy: 0 @ crossHeight.
verticalBar := self bounds insetBy: crossWidth @ 0.
↑ (horizontalBar containsPoint: aPoint)
or: [verticalBar containsPoint: aPoint]
이 메서드는 drawOn: 과 동일한 로직을 사용하기 때문에, containsPoint: 에서 true 를 반환하는 영역이, drawOn 에 의해 색칠이 가능한 영역과 일치한다는 것을 확신할 수 있죠. Rectangle 클래스의 containsPoint: 메서드를 활용하면, 이런 번거로운 작업을 해낼 수 있습니다.
메서드 11.3 과 11.4 에 있는 코드는 2 가지 문제를 가지고 있습니다. 눈에 띄는 문제점으로는 코드가 중복되고 있다는거죠. 이건 매우 큰 잘못입니다: 왜냐하면 horizonatalBar 또는 verticalBar 가 계산된 방식을 변경할 필요가 있을때, 2 개 지점중 한쪽의 코드를 변경하는 작업을 쉽게 잊어버리기 때문입니다. 해결하려면 새로운 2 개 메서드에서 계산에 관련된 부분을 분리해낸 후, private 프로토콜에 넣으면 됩니다.
메서드 11.5: horizontalBar.
horizontalBar
| crossHeight |
crossHeight := self height / 3.0.
↑ self bounds insetBy: 0 @ crossHeight
메서드 11.6: verticalBar.
verticalBar
| crossWidth |
crossWidth := self width / 3.0.
↑ self bounds insetBy: crossWidth @ 0
이렇게 만든 메서드를 이용해서 drawOn: 와 containsPoint: 양쪽을 다 정의하면 됩니다.
메서드 11.7: 리팩토링한 CrossMorph>>drawOn:.
drawOn: aCanvas
aCanvas fillRectangle: self horizontalBar color: self color.
aCanvas fillRectangle: self verticalBar color: self color
메서드 11.8: 리팩토링한 CrossMorph>>containsPoint:.
containsPoint: aPoint
↑ (self horizontalBar containsPoint: aPoint)
or: [self verticalBar containsPoint: aPoint]
그림 11.4: colour 로 2 번 채워진 십자의 중심부분 | 그림 11.5: 채워지지 않은 픽셀의 열을 보여주는 십자 모양의 Morph |
private 메서드에 의미있는 이름을 붙인 덕분에, 이 코드는 보다 이해가 쉬워졌습니다. 이제 간단히 두번째의 문제를 알 수 있게 됐죠: 십자의 중앙부분이 horizontalBar 와 verticalBar 의 양쪽에 걸쳐져 있기때문에 2 번 그려지는 문제입니다. 이런 버그는 투명하지 않는 생상으로 십자를 그린다면 별 문제가 아닐겁니다만, 그림 11.4 에서 확인할 수 있듯이 반투명 십자를 그리는 순간, 확실히 버그가 드러날겁니다.
m := CrossMorph new bounds: (0@0 corner: 300@300).
m openInWorld.
m color: (Color blue alpha: 0.3).
이 문제를 해결하려면 verticalBar 를 세부분으로 나눠서, 위쪽과 아래쪽만 칠하면 됩니다. Rectangle 클래스에서 이런 여려운 일을 해주는 메서드를 다시 발견할 수 있습니다: r1 areasOutside: r2 는 r2 의 바깥쪽으로 삐져나온 부분에 해당되는 r1 을 구성하는 사각형의 배열을 반환합니다. 아래쪽에 수정된 코드가 있습니다:
메서드 11.9: 개량된 drawOn: 메서드, 이 메서드는 십자의 중심부분을 한번만 채웁니다.
drawOn: aCanvas
| topAndBottom |
aCanvas fillRectangle: self horizontalBar color: self color.
topAndBottom := self verticalBar areasOutside: self horizontalBar.
topAndBottom do: [ :each | aCanvas fillRectangle: each color: self color]
위의 코드는 잘 작동하는걸로 보입니다만, 몇번정도 십자의 크기를 변경하려고 하는경우, 그림 11.5 처럼 특정 크기에서 1 pixel 만큼의 선이 십자의 아래부분과 위쪽을 나눠버리는것을 확인할 수 있습니다. 이 문제는 어림수(짐작하는수)처리rounding 때문에 일어납니다: 색을 채우려고 하는 사각형의 크기가 정수integer가 아닌경우, fillRectangle: color: 는 적당히 수치를 조절하기때문에 1 pixel 만큼 문제가 생기게 됩니다. 이 문제는, 사각형의 크기를 계산할때 명시적으로 값을 대입하는것으로 해결할 수 있습니다.
메서드 11.10: 정확한 CrossMorph>>horizontalBar rounding 처리
horizontalBar
| crossHeight |
crossHeight := (self height / 3.0) rounded.
↑ self bounds insetBy: 0 @ crossHeight
메서드 11.11: 정확한 CrossMorph>>verticalBar rounding 처리
verticalBar
| crossWidth |
crossWidth := (self width / 3.0) rounded.
↑ self bounds insetBy: crossWidth @ 0
Notes
- ↑ 이 경우는 그릴 morph 의 bounds 가 아니라 그림판이 될 대상 canvas 의 bounds 를 의미하는것 같습니다