Make Button By Floating Window
Tags: neovim
Published: 2021-02-14 12:12:47

## Let's make a button by floating window!
I made a button using floating window. You can push the button by <kbd>ENTER</kbd>.
<img src="https://github.com/Rasukarusan/blog-assets/blob/master/nvim-button/demo.gif?raw=true">


## How do I make it look like a button?

![screenshot_ 2021-02-14 20.56.45](https://dev-to-uploads.s3.amazonaws.com/i/4wwee1aked3kiq40uqbv.png)

<figure class="figure-image figure-image-fotolife" title="Looks like a button.">

- Prepare two windows, front and back.
- Make the color of the back a little darker than the color of the front.
- Lower the position of the front so that it covers the back when it is pressed.

When <kbd>ENTER</kbd> is pressed, just move it back down and it will appear to be pressed.

## Code

```vim
let g:button_window = {}
hi FrontColor guibg=#F27200
hi BackColor guibg=#AC5D24

function! s:center(str)
  let width = nvim_win_get_width(0)
  let shift = floor(width/2) - floor(strdisplaywidth(a:str)/2)
  return repeat(' ', float2nr(shift)) . a:str
endfunction

function! s:remove_button() abort
  let front_win = nvim_get_current_win()
  let back_win = g:button_window[front_win]
  call nvim_win_close(back_win, v:true)
  call nvim_win_close(front_win, v:true)
  call remove(g:button_window, front_win)
endfunction

function! s:create_window(config, ...) abort
  let hi_group = a:1
  let transparency = get(a:, '2', 0)
  let buf = nvim_create_buf(v:false, v:true)
  let win = nvim_open_win(buf, v:true, a:config)
  if hi_group != ''
    call nvim_win_set_option(win, 'winhighlight', hi_group)
    call nvim_win_set_option(win, 'winblend', transparency)
    call nvim_win_set_config(win, a:config)
  endif
  return win
endfunction

function! s:push() abort
  let front_win = nvim_get_current_win()
  let back_win = g:button_window[front_win]

  let config = nvim_win_get_config(front_win)
  let config.row += 1
  call nvim_win_set_config(front_win, config)

  sleep 100ms
  redraw

  let config.row -= 1
  call nvim_win_set_config(front_win, config)
endfunction

function! s:main() abort
  let row = 20
  let col = 20
  let width = 20
  let height = 3
  let config = { 'relative': 'editor', 'row': row, 'col': col, 'width': width, 'height': height, 'anchor': 'NW', 'style': 'minimal' }

  " back window
  let back_config = deepcopy(config)
  let back_config.row += 1
  let back_win = s:create_window(back_config, 'Normal:BackColor')

  " front window
  let front_win = s:create_window(config, 'Normal:FrontColor')
  call setline(2, s:center('Button'))
  call cursor(2, 0)
  nnoremap <buffer><nowait><silent> :q :call <SID>remove_button()
  nnoremap <CR> :call <SID>push()<CR>

  let g:button_window[front_win] = back_win
endfunction

call s:main()
```

## Bonus: Fire bullets with ENTER

<div style="text-align:center">
    <video controls muted autoplay playsinline width="95%">
        <source src="https://github.com/Rasukarusan/blog-assets/blob/master/nvim-button/demo2.mp4?raw=true">
    </video>
</div>

And you can move by <kbd>hjkl</kbd>.

```vim
let g:button_window = {}
hi FrontColor guibg=#F27200
hi BackColor guibg=#AC5D24

function! s:center(str)
  let width = nvim_win_get_width(0)
  let shift = floor(width/2) - floor(strdisplaywidth(a:str)/2)
  return repeat(' ', float2nr(shift)) . a:str
endfunction

function! s:move(direction, value)
  let front_win = nvim_get_current_win()
  let back_win = g:button_window[front_win]
  for id in [front_win, back_win]
    let config = nvim_win_get_config(id)
    if a:direction == 'x'
      let config.col += a:value
    else
      let config.row += a:value
    endif
    call nvim_win_set_config(id, config)
  endfor
endfunction

function! s:remove_button() abort
  let front_win = nvim_get_current_win()
  let back_win = g:button_window[front_win]
  call nvim_win_close(back_win, v:true)
  call nvim_win_close(front_win, v:true)
  call remove(g:button_window, front_win)
endfunction

function! s:create_window(config, ...) abort
  let hi_group = a:1
  let transparency = get(a:, '2', 0)
  let buf = nvim_create_buf(v:false, v:true)
  let win = nvim_open_win(buf, v:true, a:config)
  if hi_group != ''
    call nvim_win_set_option(win, 'winhighlight', hi_group)
    call nvim_win_set_option(win, 'winblend', transparency)
    call nvim_win_set_config(win, a:config)
  endif
  return win
endfunction

function! s:fire() abort
  let front_win = nvim_get_current_win()
  let conf = nvim_win_get_config(front_win)
  let row = conf.row + 1
  let col = conf.col + conf.width
  let width = 2
  let height = 1
  let config = { 'relative': 'editor', 'row': row, 'col': col, 'width': width, 'height': height, 'anchor': 'NW', 'style': 'minimal', }
  let ballet = s:create_window(config, 'Normal:FrontColor')

  for i in range(1, 30)
    let config.col += 1
    call nvim_win_set_config(ballet, config)
    redraw
    sleep 10ms
  endfor

  call nvim_win_close(ballet, v:true)
endfunction

function! s:push() abort
  let front_win = nvim_get_current_win()
  let back_win = g:button_window[front_win]
  let config = nvim_win_get_config(front_win)
  let config.row += 1
  call nvim_win_set_config(front_win, config)

  sleep 100ms
  redraw

  let config.row -= 1
  call nvim_win_set_config(front_win, config)

  call s:fire()
endfunction

function! s:main() abort
  let row = 20
  let col = 20
  let width = 20
  let height = 3
  let config = { 'relative': 'editor', 'row': row, 'col': col, 'width': width, 'height': height, 'anchor': 'NW', 'style': 'minimal' }

  " back window
  let back_config = deepcopy(config)
  let back_config.row += 1
  let back_win = s:create_window(back_config, 'Normal:BackColor')

  " front window
  let front_win = s:create_window(config, 'Normal:FrontColor')
  call setline(2, s:center('Button'))
  call cursor(2, 0)

  nnoremap <buffer><nowait><silent> l :call <SID>move('x', 2)<CR>
  nnoremap <buffer><nowait><silent> h :call <SID>move('x', -2)<CR>
  nnoremap <buffer><nowait><silent> j :call <SID>move('y', 2)<CR>
  nnoremap <buffer><nowait><silent> k :call <SID>move('y', -2)<CR>
  nnoremap <buffer><nowait><silent> :q :call <SID>remove_button()
  nnoremap <CR> :call <SID>push()<CR>

  let g:button_window[front_win] = back_win
endfunction

call s:main()
```

Have a happy vim life!