🎆
NeovimのFloating Windowで花火を打ち上げる
NeovimにはFloating Windowという機能があります。
この機能で花火を打ち上げて遊んでみたので紹介します!
(Neovim: v0.9.1)
Floating Windowとは
Floating Windowはv0.4
あたりで入った機能です。
通常のウィンドウ内ではなく、ポップアップのように重ねて配置できるウィンドウです。
なんでもできる。
できたもの
花火の点一つ一つがひとつのFloating Windowです。
:call Firework()
で画面におさまるランダムな位置に花火を打ち上げます。
ついでに
:call Fireworks(n)
でn回連続で花火を打ちげられるようにしました。
それではざっくりと流れを解説していきます!
Floating Windowで点を打つ
nvim_open_win
)
点(横2,縦1のFloating Window)を打つ(半角2文字分、高さ1で大体正方形になるのでこれを1ドットとします。
" Floating Window用の空のバッファ
let empty_buf = nvim_create_buf(v:false, v:true)
" Floating Window生成
" Floating WindowのIDが返ってくるので背景色の設定や削除するときのために保持する
let window_id = nvim_open_win(empty_buf, v:true,
\ {
\ 'row': 10, 'col': 20, 'width': 2, 'height': 1,
\ 'relative': 'editor', 'style': 'minimal',
\ })
nvim_win_set_option
)
点の色(Floating Windowの背景色)を設定する(" 適当な色を追加
highlight FireworkRed guibg=#FF0000
" window_idで指定したWindowの`winhighlight`を`FireworkRed`に設定
call nvim_win_set_option(window_id, 'winhighlight', 'Normal:FireworkRed')
timer_start
, nvim_win_close
)
指定時間で点を消す(指定時間後に点を消すことで、花火のアニメーションを実現します。
" window_idで指定したWindowを閉じる関数
function! s:remove_window(window_id, _timer_id) abort
call nvim_win_close(a:window_id, v:true)
endfunction
" 3秒後(3000ms)に`s:remove_window`を実行. 引数に`window_id`を渡す
call timer_start(3000, function('s:remove_window', [window_id]))
花火を打ち上げる
上記の工程を1つの関数にまとめる
指定した座標に指定した色で点を表示し、指定時間後に点を消す関数です。
let s:current_window_id = win_getid()
let s:empty_buf = nvim_create_buf(v:false, v:true)
let s:winconf = { 'width': 2, 'height': 1, 'relative': 'editor' }
" x, y: 座標を指定
" color_name: 色名を文字列で渡す
" ms: 何ms後に消すか
function! s:FireworkFlash(x, y, color_name, ms) abort
let l:firework_window_id = nvim_open_win(
\ s:empty_buf, v:true,
\ extend(s:winconf, { 'col': a:x, 'row': a:y })
\ )
call nvim_win_set_option(l:firework_window_id, 'winhighlight', a:color_name)
call nvim_win_set_option(l:firework_window_id, 'winblend', 40)
function! s:remove_window(firework_window_id, _timer_id) abort
call nvim_win_close(a:firework_window_id, v:true)
endfunction
call nvim_set_current_win(s:current_window_id)
call timer_start(a:ms, function('s:remove_window', [l:firework_window_id]))
endfunction
スプレッドシートで花火のドット絵を作る
花火の座標、色などのデータを用意する
highlight FireworkYellow guibg=#FFF100
highlight FireworkOrange guibg=#FFA500
highlight FireworkBlue guibg=#0000FF
highlight FireworkGreen guibg=#7CFC00
highlight FireworkRed guibg=#FF0000
let s:color_sets = [
\ ['Normal:FireworkYellow', 'Normal:FireworkOrange'],
\ ['Normal:FireworkGreen', 'Normal:FireworkBlue'],
\ ['Normal:FireworkYellow', 'Normal:FireworkBlue'],
\ ['Normal:FireworkGreen', 'Normal:FireworkOrange'],
\ ['Normal:FireworkYellow', 'Normal:FireworkRed'],
\ ['Normal:FireworkGreen', 'Normal:FireworkRed'],
\ ]
" 上で作った花火の座標を、段階的に分ける
" color_index: 層の色として1つ目/2つ目どっちの色を使うか
" ms: 層の表示時間
" positions: 層のドットたちの、中心からの相対位置
let s:firework_matrix = [
\ {
\ 'color_index': 1,
\ 'ms': 500,
\ 'positions': [
\ { 'x': 0, 'y': -3 }, { 'x': 2, 'y': -2 }, { 'x': 3, 'y': 0 },
\ { 'x': 2, 'y': 2 }, { 'x': 0, 'y': 3 }, { 'x': -2, 'y': 2 },
\ { 'x': -3, 'y': 0 }, { 'x': -2, 'y': -2 },
\ ],
\ },
\ {
\ 'color_index': 0,
\ 'ms': 300,
\ 'positions': [
\ { 'x': 0, 'y': -6 }, { 'x': 2, 'y': -5 }, { 'x': 4, 'y': -4 },
\ { 'x': 5, 'y': -2 }, { 'x': 6, 'y': 0 }, { 'x': 5, 'y': 2 },
\ { 'x': 4, 'y': 4 }, { 'x': 2, 'y': 5 }, { 'x': 0, 'y': 6 },
\ { 'x': -2, 'y': 5 }, { 'x': -4, 'y': 4 }, { 'x': -5, 'y': 2 },
\ { 'x': -6, 'y': 0 }, { 'x': -5, 'y': -2 }, { 'x': -4, 'y': -4 },
\ { 'x': -2, 'y': -5 },
\ ],
\ },
\ {
\ 'color_index': 0,
\ 'ms': 200,
\ 'positions': [
\ { 'x': 0, 'y': -7 }, { 'x': 5, 'y': -5 }, { 'x': 7, 'y': 0 },
\ { 'x': 5, 'y': 5 }, { 'x': 0, 'y': 7 }, { 'x': -5, 'y': 5 },
\ { 'x': -7, 'y': 0 }, { 'x': -5, 'y': -5 },
\ ],
\ },
\ {
\ 'color_index': 1,
\ 'ms': 500,
\ 'positions': [
\ { 'x': 0, 'y': -8 }, { 'x': 6, 'y': -6 }, { 'x': 8, 'y': 0 },
\ { 'x': 6, 'y': 6 }, { 'x': 0, 'y': 8 }, { 'x': -6, 'y': 6 },
\ { 'x': -8, 'y': 0 }, { 'x': -6, 'y': -6 },
\ ],
\ },
\ ]
完成
let s:fw_vertical_radius = 8
let s:fw_horizontal_radius = 16
let s:margin = 8
function! Firework() abort
" color_setsの中からランダムにセットを決定
let l:color_set = s:color_sets[rand() % len(s:color_sets)]
let l:max_y = nvim_get_option('lines')
let l:max_x = nvim_get_option('columns')
" 花火の中心を画面におさまる範囲でランダムに決定
let l:center_y = rand() % (l:max_y - (s:fw_vertical_radius + s:margin) * 2) + s:fw_vertical_radius + s:margin
let l:center_x = rand() % (l:max_x - (s:fw_horizontal_radius + s:margin) * 2) + s:fw_horizontal_radius + s:margin
" 花火の中心に向かって打ち上げ
for i in range(0, (l:max_y - l:center_y) / 2)
let l:y = l:max_y - i * 2
call s:FireworkFlash(l:center_x, l:y, l:color_set[0], 300)
sleep 50m
endfor
sleep 100m
" 花火の中心から層を段階的に表示していく
for i in range(0, len(s:firework_matrix) - 1)
let l:firework_circle = s:firework_matrix[i]
for j in range(0, len(l:firework_circle['positions']) - 1)
let l:pos = l:firework_circle['positions'][j]
call s:FireworkFlash(
\ l:center_x + l:pos['x'] * 2,
\ l:center_y + l:pos['y'],
\ l:color_set[l:firework_circle['color_index']],
\ l:firework_circle['ms'],
\ )
endfor
sleep 100m
endfor
endfunction
おまけ
ループ
for i in range(0, n - 1)
" n回実行したい処理
endfor
花火をn回連続で打ち上げる
function! Fireworks(n) abort
for i in range(0, a:n - 1)
call Firework()
endfor
endfunction
スリープ
100msのスリープ
sleep 100ms
ランダム整数
0~(n-1)のランダムな整数を生成
echo rand() % n
変数スコープ
他にもあるけど今回使ったもの
let g:hoge = "hoge" " global-variable グローバル変数
let s:hoge = "hoge" " script-variable スクリプトに閉じた変数
let l:hoge = "hoge" " local-variable 関数に閉じた変数
function Hoge(hoge)
echo a:hoge " 引数(アクセスするときに`a:`をつける)
endfunction
参考
- nvim_open_winの基礎
さいごに
Vim scriptを設定以外で普段書かないので、やりたいことをやるために色々詰まることがありました。
変数/関数の書き方、呼び出し方、ループ処理、スリープ処理、ランダム、遅延処理などなど。
「Vim script勉強しよー」ではなく、やりたいこと(遊びだろうと業務だろうと)のためにコードを書いていると活きた知見になりますね。
皆さんもNeovimのFloating Windowでぜひ遊んでみてください!
株式会社クロスビットでは、デスクレスワーカーのためのHR管理プラットフォームを開発しています。
一緒に開発を行ってくれる各ポジションのエンジニアを募集中です。
Discussion