# ブロックエディタチーム(10,11)
[**前回(8,9回目)**](https://hackmd.io/@ep18116/SJjAvr75d)
[**ここにブロックエディタチームのGithubがあります**](https://github.com/ep18116/BlockEdit-Team)
(閲覧には招待が必要なため、もし必要だったらアカウント名を教えてください)
<br>
## 7月6日(第10回目)
## 先週の復習
#### 画像表示バグについて
```elm
div
[]
-- [
-- div[style "position" "absolute"]
[ img
[ src image
, width "100"
, height "100"
-- , align "center"
, style "position" "absolute"
, style "margin" "15px 15px"
, class "image is-overray" -- Bulma必須
, style "top" "0%" -- Bulma必須
, style "left" "0%" -- Bulma必須
]
[]
--]
```
* 前回、理由は分からないが、上で宣言している style "position" "absolute" の部分を消したところ、画像が表示された。
* 基本的にROMというのは、親の要素の中に子の要素がいくつか入っている。
デフォルトでは、親の要素に対して子の要素がどう表示するのかというのは決まっているが、それを自分で調節して行いたい場合は、親要素のpositionをnegativeにしておき、子要素の方をabsoluteに指定するという方法を採ることになる。
(通常、中身に何も書かなかったらnegativeに指定される)
そのため、img の中でstyle "position" "absolute"を行うことで、画像が表示される位置をこちら側で制御していることになる。
* どうしてimgとsvgの上側に div[style “position” “absolute”] があるとBulmaで画像が表示されるかの原因は分からないため、これを調べて理解することができれば、今後役に立つと思われる。
* imgの中身で画像のサイズを調整しているので、ここにある数値を弄ると解決するかもしれない。今回試してみる。
<br>
#### 先生から頂いたコードについて
* 先週頂いた先生のコードは、Elmのパッケージを扱うのをやめ、全部json.codeを使って自力で書かれている。
理由は、原因の分からないバグが多く発生していた(ブロックを持った状態で他のブロックの上を通過するとブロックを持つ判定が奪われるバグなど)ため、自分で全部書いた方が原因が分かりやすいとなったためである。
* 何か使える箇所があったら、どんどん参考にしていくことにする。
<br>
## 今回の目標
* 画像のサイズの調整を試す。
* よりscratchの動作に近づけるため、複製のタイミングや動作を調整する。
* スクロールボックス内にパレットを入れる。(今週は進捗無し)
<br>
## 今回行ったこと
#### 画像のサイズの調整を試す
* まず、画像が下にはみ出していたため、 style "margin" "5px 15px" の値を書き換えてみたところ、今度は画像が上にはみ出してしまった。
<img src="https://i.imgur.com/EpyGny1.png" width="400"><br>
* 画像の位置がずれているというよりも、画像の大きさがおかしくなっていたため、画像のサイズを少し小さくし、style "margin" の値も調整した。
その結果、この数値がバランス良くなった。
```elm
[ img
[ src image
, width "92"
-- , height "100"
, style "position" "absolute"
, style "margin" "13px 19px"
, class "image is-overray" -- Bulma必須
, style "top" "0%" -- Bulma必須
, style "left" "0%" -- Bulma必須
]
[]
]
```
<img src="https://i.imgur.com/0LKRwZv.png" width="400"><br>
これで一応、画像が全部枠内に収まらせることに成功した。
色々試してみて気づいたこと(メモ)
* height の数値を弄ってみても画像が変わらなかったため、消してみたところ変化はなかった。必要ないかもしれない?(確証無し)
* style "margin" "15px 15px" は、左側の値を大きくすると画像は下側に、右側の値を大きくすると画像は右側に動いていく。両方とも同じ値で良いなら、値は一つでも問題はない。
* style "top" "0%" と style "left" "0%" も、margin と同じような動作をとる。Bulma側で画像がズレない用に置いてあるだけのものであるため、ここの値を弄る必要は特にないと思われる。
<br>
#### よりscratchの動作に近づけるため、複製のタイミングや動作を調整する。
* 現状のプログラムは、右クリック(contextmenu)でブロックが複製されるようになっている。
しかし実際のscratchでは、パレットにあるブロックを左クリックでドラッグすると、そのタイミングで複製されている。
そのため、よりscratchの動作に近づけるため、複製のタイミングや動作に手を付けることにした。
* まず、viewASTxyにある "contextmenu" (\event -> MsgCloneUs (ASTne n b r) event) がブロックの複製のメッセージを送っているため、この"contextmenu"を左クリックである "click" に変更してみた。
しかし、左クリックでは複製できなかった。他のメッセージが干渉しているのかと思い、他の部分をコメントアウトしても結果は変わらなかった。
* そこで、メッセージを受け取る側に何かあると思い、cloneUs 側を確認してみたところ、
```elm
cloneUs ast event model =
if event.button == Mouse.MainButton && model.getDnDInfo.getOnDnD then
model
```
となっていた。この部分を書き換え、event.button /= Mouse.MainButton としてみたところ、左クリックでもブロックを複製することに成功した。
* しかし、実際のscratchでは複製されるタイミングは左クリックではなく、ドラッグを開始したタイミングである。そして、このままではパレット以外のブロックを掴んだ時も複製が行われてしまう状態である。
* ここで、複製の命令を "click" ではなく、マウスを押した瞬間に発生する "mousedown" に変更してみた。しかし、試してみると、マウスを押してみても複製することが出来なかった。
* 原因としては、既に on "mousedown" (\event -> MsgStartDnD event) という、ドラッグ&ドロップを開始したことを知らせるメッセージが存在していたため、これが干渉しているためでると考えられる。
試しに、この行をコメントアウトしてみた結果、ブロックを動かすことはできなくなったが、mousedownで複製をすることには成功した。
<br>
## 今回の反省・わからなかったところ
#### 複製の命令(mousedown)が他の命令と干渉?してしまう原因・解決方法
* 複製の命令を “contextmenu” から "mousedown" に変更すると、on "mousedown" (\event -> MsgStartDnD event) の命令に干渉?してしまい、複製することが出来なかった。
* これの解決方法として、1つの "mousedown" で同時に2つのメッセージを送る方法を考えた。しかし、色々な書き方を試してみても、1つの on では1つの msg しか送れないといったエラーが発生してしまうため、この方法は不可能かもしれない。
* 次に、受け取るメッセージ側の中身を書き換え、cloneUs と startDnD が1つのメッセージ内で同時に行われるような書き方を目指した。
しかし、2つのメッセージは受け取る引数が異なったり、startDnD が MsgLetMeRoot に関わっていたりするため、書き換えるのに難儀すると思われる。
* 他にも、MsgStartDnD の命令を他のマウス操作で発生するよう方法も考えた。しかし、どの操作コマンドを試しても、現状と結果が変わらなかった。
<br>
#### パレット以外のブロックを掴んだ時も複製されてしまう状況の解決方法
* 現時点では、複製がパレットの部分にしか出来ないという書き方になっていない。
* まず、viewAST 側にある on "contextmenu" (\event -> MsgCloneUs (ASTne node b r) event) は、ブロックの根以外に対する複製の命令である。
目指すものとしては、パレット以外に対しては複製は行わない予定であるため、この行はコメントアウトした。
<br>
```elm
cloneUs : ASTne Node -> Mouse.Event -> Model -> Model
cloneUs ast event model =
if event.button /= Mouse.MainButton && model.getDnDInfo.getOnDnD then
model
else
addASTxy (ASTxy (pairMap2 (+)
(pairMap2 (-) event.pagePos event.offsetPos) --凹凸を含む左上の角の座標
(10, 10)) -- 元のブロックと完全に重ならないように少しずらす
ast)
model
```
* 上は現時点の cloneUS である。パレットの部分にしか複製できないということは、model.getPallet に対してのみelseに飛び、それ以外には何もしないような書き方ができればこの問題は解決すると考えられる。
しかし、その書き方が分からず、どのように書けば pallet かそうでないかを判別することが出来るのかが分からなかった。
<br>
## 次週(今後)の目標
* 今回分からなかった、複製の問題を解決する。
<br>
## 参考サイト
[マウスの各操作に対応したイベント一覧](https://gray-code.com/javascript/event-list-of-mouse/)
[ドラッグ・ドロップしたときに処理を実行する](https://gray-code.com/javascript/execute-processing-when-drag-and-drop/)
<br>
<br>
<br>
## 7月13日(第11回目)
## 先週の復習
#### 複製の命令(mousedown)が他の命令と干渉?してしまう原因・解決方法について
* 先週考えた方法の1つに、「1つの "mousedown" で同時に2つのメッセージを送る」という方法があった。
しかし、1つのコマンドの命令には、1つしかバインドできない。つまり、 "mousedown" に対してメッセージを送る場合は、1つのメッセージしか送れないということである。
そのため、この方法は不可能である。
どうしても2つのメッセージを送りたい場合は、その次の方法として考えていた、1つのメッセージを送り、メッセージを受け取った側で2つ処理を行わせるしかない。
* この問題は、次の問題を解決することで達成できる。
<br>
#### パレット以外のブロックを掴んだ時も複製されてしまう状況の解決方法
* 先週考えていた方法として、メッセージを受け取る側である cloneUs 側を書き換え、そこで model.getPallet かそうでないかを判断すれば解決すると考えていた。
しかし、この方法には大きな問題があった。
* それは、触れたのがパレットであってもそうでなくても、右クリックをすると、必ず MsgCloneUS が発行されてしまうことである。
そのため、このまま実装してしまうと、まず MsgCloneUS が呼び出され、Pallet ではなかったときに、MsgLetMeRoot が呼び出される、といった無駄のある処理が発生してしまう。よって不適切である。
* ここで、この状況の遷移図を書いてみると、この問題の解決方法が非常に分かりやすくなる。
<br>
①は先週考えていた方法であり、mousedown によって cloneUs が必ず呼び出された後、cloneUs の中で Pallet かそうでないかを判断して、処理を変える方法である。
②は新しい方法その1である。MsgCloneUs を使うのをやめ、新たに MsgMouseDown という名前のメッセージを定義し、このメッセージの update の中で Pallet かそうでないかを判断して、letMeRoot か cloneUs のどちらかだけを呼び出す方法である。
③は新しい方法その2である。view の内部の時点で Pallet かそうでないかを判断し、Pallet であったら MsgCloneUs を、そうでなかったら MsgLetMeRoot を送るという方法である。
* この中で一番簡単に実装でき、無駄がないと思われるのは**③**の方法である。
①や②の場合では、modelにflagの情報を加えたりして、何らかの方法で Pallet かそうでないかを判断する必要がある。modelに情報を加えることは複雑になり難しく、処理も増えてしまうため、これらの方法は推奨されない。
③の場合、viewの中で判断すればいいため、modelに追加の情報は必要ない。そして、view側で送るメッセージを調整できるため、送られるメッセージ側の方は中身を弄る必要はなく、処理も増えない。
そのため、③の方法を選択することにする。
* 現時点では、Pallet 部分の描画と、ASTRoots 部分の描画は viewASTxy で共有し、ひとまとめに行っている。そこを2つに分けてしまえば、"mousedown" に対して別々の命令を送ることが可能である。
viewPallet を新たに作成し、viewPallet 側の "mousedown" では MsgCloneUs を呼び出し、元々の viewASTxy 側の "mousedown" では MsgCloneUs を呼び出さず、MsgLetMeRoot だけを呼び出すようにすれば良い。
そして、viewPallet と viewASTxy を両方行うようにすれば、pallet に対してのみ複製が行われ、それ以外の場所では複製がされないようになり、cloneUsの問題はすべて解決すると思われる。今回、これを試してみることにする。
<br>
## 今回の目標
* 先週の反省を踏まえ、cloneUsの問題を解決する。
* 複製したブロックを、そのまま掴めるようにする。
<br>
## 今回行ったこと
#### cloneUsの問題を解決する。
* 反省を踏まえ、まず、新たに viewPallet を作成した。主な目的としては、"mousedown" に対する命令が、MsgStartDnD から MsgCloneUs に変えることである。内容は以下のようになる。
```elm
viewPallet : Model -> ASTxy Node -> Html Msg
viewPallet model (ASTxy (x, y) (ASTne n b r)) =
Keyed.node "div"
[ style "position" "absolute"
, style "top" (String.fromFloat y ++ "px")
, style "left" (String.fromFloat x ++ "px")
-- , on "mousemove" (\event -> MsgMoveUs (x, y) event)
-- , on "mouseup" (\event -> MsgAttachMe (ASTxy (x, y) (ASTne n b r)) event)
, on "mousedown" (\event -> MsgCloneUs (ASTne n b r) event)
]
[ ("N", brickSvg n model.getBrickSize ) -- 実際のブロックの描画はbrickSvgで
-- , ("R", lazy3 viewAST model.getBrickSize True r)
-- , ("B", lazy3 viewAST model.getBrickSize False b)
]
```
* viewPallet はパレットの描画だけを行うので、viewASTxy と比べ不要な部分が多い。そのため、不要な行はコメントアウトし、簡略化した。
また、viewASTxy 側も、複製を行わなくなるので MsgCloneUs の行を削除した。
* そして、view 関数を書き換え、viewASTxy と viewPallet を同時に描画できるように変更した。内容は以下のようになる。
```elm
view : Model -> Html Msg
view model =
(model.getPallet
|> List.indexedMap (\index astxy -> (String.fromInt index, viewPallet model astxy)))
++
(model.getASTRoots
|> List.indexedMap (\index astxy -> (String.fromInt index, viewASTxy model astxy)))
|> Keyed.node "div" []
```
* この書き方により、2つのviewを同時に描画することに成功し、パレットに対してマウスを押すとブロックが複製され、複製されたブロックには、どのような操作をしてもブロックが複製されないような動作を取ることを実現することができた。
<br>
#### 複製したブロックを、そのまま掴めるようにする。
* パレットに対してのみ複製を行うようにすることは成功したが、まだ実際のScratchの動作とは異なる。
このままだと複製だけで処理が終わってしまい、複製したブロックをそのまま動かせるようにはなっていない。
* 思いつく方法としては、複製されたブロックに対して、そのまま MsgStartDnD の命令が行われるように書けると、複製の直後にDnDが開始され、上手く行くと考えた。
* ここで、viewPallet 側では不要になった "mousemove" の命令を利用し、ここで MsgStartDnD を行ってしまえばいいのでは?と閃いた。
試しに、viewPalletを以下のように書き換えた。
```elm
viewPallet : Model -> ASTxy Node -> Html Msg
viewPallet model (ASTxy (x, y) (ASTne n b r)) =
Keyed.node "div"
[ style "position" "absolute"
, style "top" (String.fromFloat y ++ "px")
, style "left" (String.fromFloat x ++ "px")
, on "mousemove" (\event -> MsgStartDnD event) ←変更箇所
, on "mousedown" (\event -> MsgCloneUs (ASTne n b r) event)
]
[ ("N", brickSvg n model.getBrickSize ) -- 実際のブロックの描画はbrickSvgで
]
```
これを試してみた結果、複製されたブロックをそのまま掴んで動かすことに成功した。
* しかし、現時点の cloneUs は、複製するブロックは座標を少し右下にずらした位置に複製されるといった書き方になっているため、このままだとマウスから右下にズレた位置にブロックが複製されてしまう。
* そのため、cloneUsを書き換え、パレットのブロックと同位置にブロックが複製されるように位置調整した。
```elm
cloneUs : ASTne Node -> Mouse.Event -> Model -> Model
cloneUs ast event model =
if event.button /= Mouse.MainButton && model.getDnDInfo.getOnDnD then
model
else
addASTxy (ASTxy (pairMap2 (-)
(pairMap2 (-) event.pagePos event.offsetPos) --凹凸を含む左上の角の座標
(19, 13)) -- 元のブロックと重なるように位置調整する ast)
model
```
* これにより、複製されたブロックは丁度パレットのブロックの位置と重なるようになったため、感覚的にブロックを複製し、そのまま掴んで動かすことに成功した。
<br>
## 今回の反省・わからなかったところ
* 現時点では、ブロックの端を掴んだ時、少しずれた位置でブロックが複製されてしまうバグ?が発生している。
* そもそも、今回行った複製したブロックの位置調整は、元の数値を変更して無理やり調整しており、適した書き方にはなっていないと考えられる。これが原因で、このような現象が発生しているのではと推測される。
* 他の方法でブロックの位置調整を試してみたが、上手く行かなかったためこのようになっているため、正しい書き方が分かれば、この問題は解決すると思われる。
<br>
## 次週(今後)の目標
* 複製されたブロックをパレットの上で離したら削除されるようにする。
* ブロックの端を掴んだときの少しずれた位置でブロックが複製されてしまうバグの修正。
<br>
## [次回(12,13回目)](https://hackmd.io/@ep18116/BJG3MIE6_)
<br>