グループ化後の変形とグループ化解除のテスト
前回からの続き、テストばっかりで本番前に力尽きそう
赤枠はグループの範囲の境界線、青マスは1マス50
見た目の位置とサイズは水色のこと
前提
グループを回転や拡大率の指定をするときの中心点は各Thumb(四角)の中心ではなくグループ範囲の中心にする
初期状態は
水色と桃色をグループ化した後にさらに黄色をグループ化した状態
グループB
┣黄色
┗グループA
┣水色
┗桃色
これを1回解除すると
グループA
┣水色
┗桃色
黄色
こうなって
もう一回解除すると
水色
桃色
黄色
ってバラバラになる
難しいのが解除した時に解除前の位置や回転や拡大率とかの変形の引き継ぎ
回転の場合
実際に位置やサイズを指定するのは内部のものになる
グループ解除で中心点の変化
内部の位置を変更して見た目の位置を変化させないようにしている
って今気づいたけど見た目の位置の表示がおかしい、変化しているw
→見た目の位置取得前に再描画する必要があったみたい
<Window xClass="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlnsx="http://schemas.microsoft.com/winfx/2006/xaml"
xmlnsd="http://schemas.microsoft.com/expression/blend/2008"
xmlnsmc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlnslocal="clr-namespace:Wpf_test128_TransformGroup"
mcIgnorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DockPanel>
<StatusBar DockPanelDock="Bottom">
<TextBlock xName="tbk1" Text="tbk1"/>
<TextBlock xName="tbk2" Text="tbk2"/>
<TextBlock xName="tbk3" Text="tbk3"/>
</StatusBar>
<StatusBar DockPanelDock="Bottom">
<TextBlock xName="tbk4" Text="rect"/>
<TextBlock xName="tbk5" Text="rect"/>
<TextBlock xName="tbk6" Text="rect"/>
</StatusBar>
<Menu DockPanelDock="Bottom">
<Button xName="btReset" Content="reset"/>
<Button xName="btChack" Content="確認"/>
<Button xName="bt1" Content="解除"/>
<Button xName="bt2" Content="拡大率-0.1"/>
<Button xName="bt3" Content="拡大率+0.1"/>
<Button xName="bt4" Content="左15度回転"/>
<Button xName="bt5" Content="右15度回転"/>
<Button xName="btAngle" Content="Angle"/>
</Menu>
<Canvas xName="canvas1"/>
</DockPanel>
</Grid>
</Window>
2019/10/30追記ここまで
2019/10/30追記ここから
Imports System.Windows.Controls.Primitives
Class MainWindow
Private ActiveThumb As ExThumb2
Private ActiveGroup As Group2
Private tList As New List(Of ExThumb2)
Private gridSize As Integer = 50
Private Sub DrawGridLine()
Dim pFigure As PathFigure
Dim pGeometry As New PathGeometry
For i As Integer = 0 To 50
pFigure = New PathFigure
pFigure.StartPoint = New Point(i * gridSize, 0)
pFigure.Segments.Add(New LineSegment(New Point(i * gridSize, 350), True))
pGeometry.Figures.Add(pFigure)
Next
For i As Integer = 0 To 35
pFigure = New PathFigure
pFigure.StartPoint = New Point(0, i * gridSize)
pFigure.Segments.Add(New LineSegment(New Point(500, i * gridSize), True))
pGeometry.Figures.Add(pFigure)
Next
Dim mPath As New Path With {.Stroke = Brushes.Blue, .StrokeThickness = 1, .Data = pGeometry}
canvas1.Children.Add(mPath)
Panel.SetZIndex(mPath, -1)
End Sub
Private Sub SetLocate(t As ExThumb2, p As Point)
Canvas.SetLeft(t, p.X)
Canvas.SetTop(t, p.Y)
End Sub
Private Sub OffSetLocate(t As ExThumb2, offset As Point)
Dim p As Point = GetLocate(t) + offset
Canvas.SetLeft(t, p.X)
Canvas.SetTop(t, p.Y)
End Sub
Private Function GetLocate(t) As Point
Return New Point(Canvas.GetLeft(t), Canvas.GetTop(t))
End Function
Private Sub ReRender(uie As UIElement)
uie.Dispatcher.Invoke(Threading.DispatcherPriority.Render, Sub()
End Sub)
End Sub
Private Function GetCenterPointLocate(t As ExThumb2) As Point
Dim gt As GeneralTransform = t.TransformToVisual(canvas1)
Dim cp As New Point(t.Width / 2, t.Height / 2)
Dim np As Point = gt.Transform(cp)
Return np
End Function
Private Function GetCenterPoint(r As Rect) As Point
Dim x As Double = r.Width / 2 + r.Left
Dim y As Double = r.Height / 2 + r.Top
Dim cp As New Point(x, y)
Return cp
End Function
Private Function AddThumb(p As Point, col As Color) As ExThumb2
Dim t As New ExThumb2
With t
.Width = 50
.Height = 50
.Background = New SolidColorBrush(col)
End With
SetLocate(t, p)
canvas1.Children.Add(t)
Call ReRender(t)
t.CenterAxis = GetCenterPointLocate(t)
Return t
End Function
Private Function GetGroupRect(rl As List(Of Rect)) As Rect
Dim minX As Double = rl(0).X
Dim minY As Double = rl(0).Y
Dim maxX As Double = minX + rl(0).Width
Dim maxY As Double = minY + rl(0).Height
Dim r As Rect
For i As Integer = 1 To rl.Count - 1
r = rl(i)
minX = Math.Min(minX, r.X)
minY = Math.Min(minY, r.Y)
maxX = Math.Max(maxX, r.X + r.Width)
maxY = Math.Max(maxY, r.Y + r.Height)
Next
r = New Rect(minX, minY, maxX - minX, maxY - minY)
Return r
End Function
Private Function GetRect(t As ExThumb2) As Rect
Dim gt As GeneralTransform = t.TransformToVisual(canvas1)
Dim r As Rect = gt.TransformBounds(New Rect(New Size(t.Width, t.Height)))
Return r
End Function
Private Function GetRects(tl As List(Of ExThumb2)) As List(Of Rect)
Dim rl As New List(Of Rect)
For Each t As ExThumb2 In tl
rl.Add(GetRect(t))
Next
Return rl
End Function
Private Sub WakuUpdate(g As Group2)
Dim rll As List(Of Rect) = GetRects(g.Items)
Dim r As Rect = GetGroupRect(rll)
g.Bound = r
End Sub
Private Function GetTransformGroup2(axis As Point) As TransformGroup
Dim tg As New TransformGroup
With tg.Children
.Add(New ScaleTransform(1, 1, axis.X, axis.Y))
.Add(New SkewTransform(0, 0, axis.X, axis.Y))
.Add(New RotateTransform(0, axis.X, axis.Y))
.Add(New TranslateTransform)
End With
Return tg
End Function
Private Sub StatusbarUpdate()
Dim g As Group2 = ActiveGroup
Dim t As ExThumb2 = ActiveThumb
If g Is Nothing Then
tbk3.Text = $"赤枠中心点(na)"
tbk1.Text = $"赤枠位置(na)"
tbk2.Text = $"赤枠サイズ(na)"
Else
tbk3.Text = $"赤枠中心点({g.CenterPoint:0.0})"
tbk1.Text = $"赤枠位置({g.Waku.Data.Bounds.Location:0.0})"
tbk2.Text = $"赤枠サイズ({g.Waku.Data.Bounds.Size:0.0})"
End If
Dim l As Point = GetLocate(t)
tbk4.Text = $"水色の位置({GetLocate(t):0.0})"
tbk5.Text = $"見た目の位置({GetRect(t).Location:0.0})"
tbk6.Text = $"見た目のサイズ({GetRect(t).Size:0.0})"
End Sub
Private Sub SetGroup(tl As List(Of ExThumb2))
Dim g As New Group2
g.Items.AddRange(tl)
Call WakuUpdate(g)
g.CenterPoint = GetCenterPoint(g.Bound)
Dim axis As Point
For Each t As ExThumb2 In tl
t.gtStack.Push(t.gtCollection)
If t.NowGroup IsNot Nothing Then
t.gStack.Push(t.NowGroup)
End If
t.NowGroup = g
axis = g.CenterPoint - t.CenterAxis
t.gtCollection = GetTransformGroup2(axis).Children
Dim tg As TransformGroup = t.RenderTransform
For i As Integer = 0 To 3
tg.Children.Add(t.gtCollection(i))
Next
Next
canvas1.Children.Add(g.Waku)
ActiveGroup = g
Call StatusbarUpdate()
End Sub
Private Sub SetGroupAndThumb(oldG As Group2, tl As List(Of ExThumb2))
Dim g As New Group2
g.Items.AddRange(oldG.Items)
g.Items.AddRange(tl)
Call WakuUpdate(g)
g.CenterPoint = GetCenterPoint(g.Bound)
Dim axis As Point
For Each t As ExThumb2 In g.Items
Dim neko = t.NowGroup
If t.NowGroup IsNot Nothing Then
t.gStack.Push(t.NowGroup)
End If
t.gtStack.Push(t.gtCollection)
t.NowGroup = g
axis = g.CenterPoint - t.CenterAxis
t.gtCollection = GetTransformGroup2(axis).Children
Dim tg As TransformGroup = t.RenderTransform
For i As Integer = 0 To 3
tg.Children.Add(t.gtCollection(i))
Next
Next
canvas1.Children.Remove(oldG.Waku)
canvas1.Children.Add(g.Waku)
ActiveGroup = g
Call StatusbarUpdate()
End Sub
Private Sub InheritsTransform(t As ExThumb2)
Dim motoTransformCollection As TransformCollection = t.gtStack.Pop
Dim tg As TransformGroup = t.RenderTransform
Dim gro As RotateTransform = t.gtCollection(GTransform.Rotate)
Dim tro As RotateTransform = motoTransformCollection(GTransform.Rotate)
tro.Angle += gro.Angle
Dim gsc As ScaleTransform = t.gtCollection(GTransform.Scale)
Dim tsc As ScaleTransform = motoTransformCollection(GTransform.Scale)
tsc.ScaleX *= gsc.ScaleX
tsc.ScaleY *= gsc.ScaleY
For Each tf As Transform In t.gtCollection
tg.Children.Remove(tf)
Next
t.gtCollection = motoTransformCollection
End Sub
Private Sub UnGroup2(g As Group2)
Dim jt As List(Of ExThumb2) = GetThumbsJustBelow(g)
For Each t As ExThumb2 In jt
Dim ncp As Point = GetCenterPointLocate(t)
Dim npp As Point = ncp - t.CenterAxis
OffSetLocate(t, npp)
Next
Dim gl As List(Of Group2) = GetGroupsOneLevelBelow(g)
For Each gg As Group2 In gl
Dim rl As List(Of Rect) = GetRects(gg.Items)
Dim r As Rect = GetGroupRect(rl)
Dim ggOldCp = gg.CenterPoint
gg.CenterPoint = GetCenterPoint(r)
Dim ggNewCp = gg.CenterPoint
Dim ggDiffCp = ggNewCp - ggOldCp
For Each t As ExThumb2 In gg.Items
OffSetLocate(t, ggDiffCp)
t.CenterAxis += ggDiffCp
Next
gg.Bound = r
Next
For Each t As ExThumb2 In g.Items
Call InheritsTransform(t)
If t.gStack.Count <> 0 Then
Dim popG As Group2 = t.gStack.Pop
t.NowGroup = popG
Else
t.NowGroup = Nothing
End If
Next
canvas1.Children.Remove(g.Waku)
If ActiveThumb.NowGroup IsNot Nothing Then
canvas1.Children.Add(ActiveThumb.NowGroup.Waku)
End If
ActiveGroup = ActiveThumb.NowGroup
Call ReRender(canvas1)
Call StatusbarUpdate()
End Sub
Private Function GetGroupsOneLevelBelow(g As Group2) As List(Of Group2)
Dim gl As New List(Of Group2)
For Each t As ExThumb2 In g.Items
If t.gStack.Count <> 0 Then
gl.Add(t.gStack.Peek)
End If
Next
gl = gl.Distinct.ToList
Return gl
End Function
Private Function GetThumbsJustBelow(g As Group2) As List(Of ExThumb2)
Dim tl As New List(Of ExThumb2)
For Each t As ExThumb2 In g.Items
If t.gStack.Count = 0 Then
tl.Add(t)
End If
Next
Return tl
End Function
Private Sub start()
Call DrawGridLine()
Panel.SetZIndex(canvas1, -1)
tList.Add(AddThumb(New Point(100, 100), Colors.Cyan))
tList.Add(AddThumb(New Point(200, 100), Colors.Magenta))
tList.Add(AddThumb(New Point(300, 100), Colors.Yellow))
ActiveThumb = tList(0)
Call SetGroup(tList.GetRange(0, 2))
Call SetGroupAndThumb(ActiveGroup, tList.GetRange(2, 1))
End Sub
Private Sub MainWindow_ContentRendered(sender As Object, e As EventArgs) Handles Me.ContentRendered
Call start()
End Sub
Private Sub btReset_Click(sender As Object, e As RoutedEventArgs) Handles btReset.Click
For Each t As ExThumb2 In tList
canvas1.Children.Remove(t)
t = Nothing
Dim uie As UIElementCollection = canvas1.Children
Dim pl As List(Of Path) = uie.OfType(Of Path).ToList
For Each p As Path In pl
canvas1.Children.Remove(p)
Next
Next
tList.Clear()
Call start()
Call StatusbarUpdate()
End Sub
Private Sub btChack_Click(sender As Object, e As RoutedEventArgs) Handles btChack.Click
Dim at As ExThumb2 = ActiveThumb
Dim ag As Group2 = ActiveGroup
Dim p = GetLocate(ActiveThumb)
Dim ttc As TransformCollection = at.gtCollection
Dim gtg As TransformGroup = at.RenderTransform
Dim gtc As TransformCollection = gtg.Children
End Sub
Private Sub bt1_Click(sender As Object, e As RoutedEventArgs) Handles bt1.Click
If ActiveGroup Is Nothing Then Return
Call UnGroup2(ActiveGroup)
ActiveGroup = ActiveThumb.NowGroup
End Sub
Private Sub bt2_Click(sender As Object, e As RoutedEventArgs) Handles bt2.Click
Call kakudai(ActiveGroup, -0.1)
End Sub
Private Sub bt3_Click(sender As Object, e As RoutedEventArgs) Handles bt3.Click
Call kakudai(ActiveGroup, 0.1)
End Sub
Private Sub bt4_Click(sender As Object, e As RoutedEventArgs) Handles bt4.Click
Call henkei(ActiveGroup, -15)
End Sub
Private Sub bt5_Click(sender As Object, e As RoutedEventArgs) Handles bt5.Click
Call henkei(ActiveGroup, 15)
End Sub
Private Sub henkei(g As Group2, angle As Double)
If g IsNot Nothing Then
For Each t As ExThumb2 In g.Items
Dim r As RotateTransform = t.gtCollection(GTransform.Rotate)
r.Angle += angle
Next
Call WakuUpdate(g)
Else
Dim r As RotateTransform = ActiveThumb.gtCollection(GTransform.Rotate)
r.Angle += angle
End If
Call StatusbarUpdate()
End Sub
Private Sub kakudai(g As Group2, scale As Double)
If g IsNot Nothing Then
For Each t As ExThumb2 In g.Items
Dim s As ScaleTransform = t.gtCollection(GTransform.Scale)
s.ScaleX += scale
s.ScaleY += scale
Next
Call WakuUpdate(g)
Else
Dim s As ScaleTransform = ActiveThumb.gtCollection(GTransform.Scale)
s.ScaleX += scale
s.ScaleY += scale
End If
Call StatusbarUpdate()
End Sub
Private Sub btAngle_Click(sender As Object, e As RoutedEventArgs) Handles btAngle.Click
Call henkei(ActiveGroup, 15)
Call kakudai(ActiveGroup, 0.1)
End Sub
End Class
Public Class ExThumb2
Inherits Thumb
Public Property CenterAxis As Point
Public Property NowGroup As Group2
Public Property gtCollection As New TransformCollection
Public Property gtStack As New Stack(Of TransformCollection)
Public Property gStack As New Stack(Of Group2)
Public Sub New()
Dim tg As New TransformGroup
With tg.Children
.Add(New ScaleTransform)
.Add(New SkewTransform)
.Add(New RotateTransform)
.Add(New TranslateTransform)
End With
Me.RenderTransform = tg
Me.RenderTransformOrigin = New Point(0.5, 0.5)
gtCollection = tg.Children
gtStack.Push(gtCollection)
End Sub
End Class
Public Class Group2
Public Property Items As New List(Of ExThumb2)
Public Property Waku As New Path
Public Property CenterPoint As Point
Private Property _Bound As Rect
Public Property Bound As Rect
Get
Return _Bound
End Get
Set(value As Rect)
_Bound = value
Waku.Data = New RectangleGeometry(value)
End Set
End Property
Public Sub New()
With Waku
.Stroke = Brushes.Red
.StrokeThickness = 1.0R
End With
End Sub
End Class
Public Enum GTransform
Scale = 0
Skew
Rotate
Translate
End Enum
2019/10/30追記ここまで
前回のこれ
A:自身のRenderTransformの中からRotateTransformを探しだして、その中のAngleプロパティを変更する
B:新たにRotateTransformを作って自身のRenderTransformのGroupに追加する
このどちらかになると思う
Aはムダがないけどちょっとめんどくさい
Bはラクだけど回転の変更する度に追加するからかなりのムダ、きりがない
前回はBの方法だったけど今回はまともなA
の方法にすることになったけどグループ化後に回転すると位置がずれる!
これはグループ化の前後で回転軸の位置が変わるのが原因
例えば
グループ化前の時に10度回転、その後グループ化してグループを10度回転で合計20度回転
これが回転の変化で回転軸の変化が
グループ化前の回転軸は(0,0)、グループ化によって回転軸の位置が(50,30)になった場合
RotateTransform(10,0,0)から
RotateTransform(20,50,30)に変更することになるけど
これだと位置がずれる
追加方式のBなら
RotateTransform(10,0,0) + RotateTransform(10,50,30)
これならズレることはない、けど回転の変更する度に増えるから
RotateTransform(10,0,0) + RotateTransform(10,50,30) + RotateTransform(10,50,30) + RotateTransform(10,50,30) + RotateTransform(10,50,30)…
ってきりがないのは前回
なので
1回だけ追加(B方式)して、それ以降は追加したものに変更を加える(A方式)
RotateTransform(10,0,0) + RotateTransform(10,50,30)
こうして1回だけ追加して、ここからさらに10度回転して合計30度にするときは
RotateTransform(10,0,0) + RotateTransform(20,50,30)
こうする
変形用のTransformは回転以外に3つ用意されていて合計4つ
これをまとめたもの(Collection)を記録しておく入れ物が必要
同じグループ内でもThumbによって中心点は違ってくるからThumbに持たせる必要がある
ってことで前回同様Thumbを継承したクラスを作ってこんな感じになった
492行目のgtCollectionがそれ、グループ化した時にScale、Skew、Rotate、Translateこの4つのTransformを作成して入れておく、変形するときはここから取り出して値を設定する
グループ化したものをさらに別のものとグループ化すると中心点も変化するから、また新しい4つのTransformを作成してgtCollectionを今のものと入れ替える
この時古い方は捨てないで取っておくとグループ化解除した時にまた使えるので、それの入れ物が493行目のgtStack
494行目は自身がどのグループに属しているかの目印用、Group2を入れておく
この辺りのStack型のCollectionの使い方は
この時と同じ感じ
Group2、グループの情報用クラス
この時とだいたい同じ感じ
違うのは516行目、CenterPointにはグループの中心点を記録しておく
中に入っている順番は固定していてその順番に合わせてある
上から0,1,2,3
ScaleTransformを取り出したい時に
gtCollection(0)でもいいけど数値だけだとわかりにくいかなと
gtCollection(GTransform.Scale)
Thumbのグループ化
ここで終わっているので違うところは
変形に使うTransformを設定しているあたり
198行目で新たなTransformを作成、このTransformの中心軸、基準点っていうのかな、この点の位置は
グループの中心点 - 自身の中心点(197行目)
これを指定している
175,125 グループの中心
125,125 水色の中心
なので
175-125=50、125-125=0で
50,0
これを新しいTransformの軸に指定する
4つのTransformを作成するGetTransformGroup2
これに50,0を渡すと
ScaleTransform(1,1,50,0)
とかになる
この新しいTransformを198行目でtgCollectionに入れて
200から203行目でRenderTransformに追加している(B方式)
RenderTransformの中を見てみる
グループ化前の4つ(0から3)と今追加した4つ(4から7)合わせて8個入っているのがわかる
4のScaleTransformを見てみると
CenterXが50、CenterYが0と指定したとおりになっている
4から7の4つ全てのCenterXとCenterYは同じになっているはず
グループを変形するときはこれらを取り出して値を指定することになる
最初はこのとり出す方法がわからなかった
グループの拡大率を変えたいときは4番のScaleTransformが目的のものになるけど
0番にもScaleTransformがあるのでScaleTransformを探すってのは使えない
追加する順番や個数は決まっているから番号を覚えておけばできるけどわかりにくい
ってことで今回の方法
新しく作ったTransformはこのgtCollectionとRenderTransformのTransformGroupの両方に入れる、この両方の入れ物はCollectionでCollectionに入れたものは実際の値ではなくて参照(リンクみたい)になるのでどちらかに変更があるともう片方も変更される
gtCollectionには今のグループのTransformだけを入れておくので0から3番までの4つを覚えておけばいい
これなら簡単に目的のTransformを取得できるので、こっちで取得して値を変更する、両者は参照(リンク)関係なのでRenderTransformの方も変更される
グループの回転
447行目で回転のRotateTransformを取得している
gtCollectionにGTransform.Rotate(は2番)を指定して取得
448行目で角度変更
これでRenderTransformの中の今のグループに関係あるRotateTransformも変更されるので期待通りの動作になる
15度回転
この時の水色のgtCollectionとRenderTransformを見てみると
gtCollectionのRotateTransform
2番めのRotateTransformのAngleが15になっている
RenderTransformのRotateTransform
グループ化で追加した4から7のうち6番目のRotateTransformのAngleも15になっている
グループ化解除
移動
2から3へは見た目の位置に変化はないけど変化しないように移動している
2の時点では1の時と全く同じ位置にあって、回転によって見た目の位置が変化しているだけ
3でグループ化解除して単体になる黄色はグループの回転から外れるので位置を変更しないと
こうなってしまう、これだと不自然なので移動させる必要がある
どのグループにも属さないで単体になるThumbの移動
今(解除前)の見た目の中心位置 - 前の中心位置 = 移動距離
前の位置 + 移動距離 = 期待する位置
二度手間な感じだけどこうなった
今の見た目の中心点を取得する
GetCenterPointLocate
自身の元の中心点をTransformToVisualを使って得たGeneralTransformのTransformで変換して取得している
指定した距離分移動させる
OffsetLocate
1階層下のグループの中のThumbすべての移動
これも移動させないと
解除するグループ(3つのThumb)の中心点と解除後のグループ(水色と桃色)の中心点
この2つの点の差
これの分だけ移動させると自然な位置になる
グループ化解除時の
回転とかの変形(Transform)の引き継ぎと削除
引き継ぎしないと位置もおかしくなるし、これを変形させるともっとおかしくなるので
引き継ぎと削除する
InheritsTransform
グループ化するときに捨てないで取っておいたTransformCollectionをgtStackから取り出す(258行目)
これにそれぞれ4つの値を引き継ぐ、って今回は回転と拡大率しか使っていないから2つしか書いてなかった
回転角度は足す、拡大率は掛け算でいいみたい(271行目まで)
引き継いだら今のをRenderTransformから削除(273から276行目)
gtCollectionも入れ替える(278行目)
これをグループ化解除の移動が終わった後のここで実行している
ついでに今属しているグループの目印用の変数の中も入れ替え
これでやっとグループ化解除の処理が終わる
エクセルでのグループ化→回転→グループ化解除
グループ化したものを回転するとかは
めったに使わないんだよねw
エクセルでグループ化→拡大率150%
エクセルの拡大率変化の基準点は中心じゃなくて左上なんだなあ
今回のテストでの基準点は全て中心にしたから
結果が異なる
基準点の切り替えできるようにしたいけどこのままかなあ
やっとできたグループ化解除で不自然にならないようにつじつま合わせ
以前の方法のグループ化するときにはThumbのなかにThumbを入れる、っていうのだとこれができなさそうで諦めて今のThumbはThumbのままで擬似的なグループ化っていう方法を試していたんだけど、なんとかできたっぽい
グループ化のテストは次で最後になるかなあ
前々回の範囲をマウスで指定してグループ化するのと移動を今回のに付け足す感じ
今回のコード全部
GitHubとかなら誰でも見れるようなんだけど使い方がわからん
ヤフーブログの2万文字制限が無くなるか緩和してくれないかしら
前々回は5日前