Skip to content

Commit

Permalink
feat: custom animatations and opacity (#8)
Browse files Browse the repository at this point in the history
Abstracts the animation stages to allow for users to supply custom
versions. Adds the ability to change window opacity

Also overhauls architecture to separate concerns a bit more.
  • Loading branch information
rcarriga authored Aug 27, 2021
1 parent 3bace26 commit 280a08c
Show file tree
Hide file tree
Showing 18 changed files with 1,168 additions and 373 deletions.
150 changes: 148 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

A fancy, configurable, notification manager for NeoVim

![notify](https://user-images.githubusercontent.com/24252670/128085627-d1c0d929-5a98-4743-9cf0-5c1bc3367f0a.gif)
![notify](https://user-images.githubusercontent.com/24252670/130856848-e8289850-028f-4f49-82f1-5ea1b8912f5e.gif)

Credit to [sunjon](https://github.com/sunjon) for [the design](https://neovim.discourse.group/t/wip-animated-notifications-plugin/448) that inspired the appearance of this plugin.

Expand All @@ -23,6 +23,7 @@ vim.notify = require("notify")
```

You can supply a level to change the border highlighting

```lua
vim.notify("This is an error message", "error")
```
Expand All @@ -34,8 +35,10 @@ There are a number of custom options that can be supplied in a table as the thir
- `on_close`: A function to call with the window ID as an argument after closing
- `title`: Title string for the header
- `icon`: Icon to use for the header
- `keep`: Function that returns whether or not to keep the window open instead of using a timeout

Sample code for the GIF above:

```lua
local plugin = "My Awesome Plugin"

Expand All @@ -62,4 +65,147 @@ vim.notify("This is an error message.\nSomething went wrong!", "error", {

## Configuration

TODO!
### Setup

You can optionally call the `setup` function to provide configuration options

Default Config:

```lua
require("notify").setup({
-- Animation style (see below for details)
stages = "fade_in_slide_out",

-- Default timeout for notifications
timeout = 5000,

-- For stages that change opacity this is treated as the highlight behind the window
background_colour = "Normal",

-- Icons for the different levels
icons = {
ERROR = "",
WARN = "",
INFO = "",
DEBUG = "",
TRACE = "",
},
})
```

### Highlights

You can define custom highlights by supplying highlight groups for each of the levels.
The naming scheme follows a simple structure: `Notify<upper case level name><section>`

Here are the defaults:

```vim
highlight NotifyERRORBorder guifg=#8A1F1F
highlight NotifyWARNBorder guifg=#79491D
highlight NotifyINFOBorder guifg=#4F6752
highlight NotifyDEBUGBorder guifg=#8B8B8B
highlight NotifyTRACEBorder guifg=#4F3552
highlight NotifyERRORIcon guifg=#F70067
highlight NotifyWARNIcon guifg=#F79000
highlight NotifyINFOIcon guifg=#A9FF68
highlight NotifyDEBUGIcon guifg=#8B8B8B
highlight NotifyTRACEIcon guifg=#D484FF
highlight NotifyERRORTitle guifg=#F70067
highlight NotifyWARNTitle guifg=#F79000
highlight NotifyINFOTitle guifg=#A9FF68
highlight NotifyDEBUGTitle guifg=#8B8B8B
highlight NotifyTRACETitle guifg=#D484FF
highlight link NotifyERRORBody Normal
highlight link NotifyWARNBody Normal
highlight link NotifyINFOBody Normal
highlight link NotifyDEBUGBody Normal
highlight link NotifyTRACEBody Normal
```

### Animation Style

The animation is designed to work in stages. The first stage is the opening of
the window, and all subsequent stages can changes the position or opacity of
the window. You can use one of the built-in styles or provide your own in the setup.

1. "fade_in_slide_out"

![fade_slide](https://user-images.githubusercontent.com/24252670/130924913-f3a61f2c-2330-4426-a787-3cd7494fccc0.gif)

2. "fade"

![fade](https://user-images.githubusercontent.com/24252670/130924911-a89bef9b-e815-4aa5-a255-84bc23dd8c8e.gif)

3. "slide"

![slide](https://user-images.githubusercontent.com/24252670/130924905-656cabfc-9eb7-4e22-b6da-8a2a1f508fa5.gif)

4. "static"

![static](https://user-images.githubusercontent.com/24252670/130924902-8c77b5a1-6d13-48f4-98a9-866e58cb76e4.gif)

Custom styles can be provided by setting the config `stages` value to a list of
functions.

If you create a custom style, feel free to open a PR to submit it as a built-in style!

**NB.** This is a prototype API that is open to change. I am looking for
feedback on both issues or extra data that could be useful in creating
animation styles.

Check the [built-in styles](./lua/notify/stages/) to see examples

#### Opening the window

The first function in the list should return a table to be provided to
`nvim_open_win`, optionally including an extra `opacity` key which can be
between 0-100.

The function is given a state table that contains the following keys:

- `message: table` State of the message to be shown
- `width` Width of the message buffer
- `height` Height of the message buffer
- `open_windows: integer[]` List of all window IDs currently showing messages

If a notification can't be shown at the moment the function should return `nil`.

#### Changing the window

All following functions should return the goal values for the window to reach from it's current point.
They will receive the same state object as the initial function and a second argument of the window ID.

The following fields can be returned in a table:
- `col`
- `row`
- `height`
- `width`
- `opacity`

These can be provided as either numbers or as a table. If they are
provided as numbers then they will change instantly the value given.

If they are provided as a table, they will be treated as a value to animate towards.
This uses a dampened spring algorithm to provide a natural feel to the movement.

The table must contain the goal value as the 1st index (e.g. `{10}`)

All other values are provided with keys:

- `damping: number` How motion decays over time. Values less than 1 mean the spring can overshoot.
- Bounds: >= 0
- Default: 1
- `frequency: number` How fast the spring oscillates
- Bounds: >= 0
- Default: 1
- `complete: fun(value: number): bool` Function to determine if value has reached its goal. If not
provided it will complete when the value rounded to 2 decimal places is equal
to the goal.

Once the last function has reached its goals, the window is removed.

One of the stages should also return the key `time` set to true. This is
treated as the stage which the notification is on a timer. The goals of this
stage are not used to check if it is complete. The next stage will start
once the notification reaches its timeout.
11 changes: 6 additions & 5 deletions lua/notify/animate/spring.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ local cos = math.cos
local sqrt = math.sqrt

---@class SpringState
---@field position number
---@field goal number
---@field position number | string
---@field goal number | string
---@field velocity number | nil
---@field damping number
---@field frequency number

---@param dt number @Step in time
---@param state SpringState
---@return SpringState
return function(dt, state)
local damping = state.damping
local angular_freq = state.frequency * 2 * pi
return function(dt, state, settings)
local damping = settings.damping
local angular_freq = settings.frequency * 2 * pi

local cur_os = state.position
local cur_vel = state.velocity or 0
local goal = state.goal
Expand Down
40 changes: 25 additions & 15 deletions lua/notify/config/highlights.lua
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
local M = {}

function M.setup()
vim.cmd[[
hi default NotifyERROR guifg=#8A1F1F
hi default NotifyWARN guifg=#79491D
hi default NotifyINFO guifg=#4F6752
hi default NotifyDEBUG guifg=#8B8B8B
hi default NotifyTRACE guifg=#4F3552
hi default NotifyERRORTitle guifg=#F70067
hi default NotifyWARNTitle guifg=#F79000
hi default NotifyINFOTitle guifg=#A9FF68
hi default NotifyDEBUGTitle guifg=#8B8B8B
hi default NotifyTRACETitle guifg=#D484FF
]]
vim.cmd([[
hi default NotifyERRORBorder guifg=#8A1F1F
hi default NotifyWARNBorder guifg=#79491D
hi default NotifyINFOBorder guifg=#4F6752
hi default NotifyDEBUGBorder guifg=#8B8B8B
hi default NotifyTRACEBorder guifg=#4F3552
hi default NotifyERRORIcon guifg=#F70067
hi default NotifyWARNIcon guifg=#F79000
hi default NotifyINFOIcon guifg=#A9FF68
hi default NotifyDEBUGIcon guifg=#8B8B8B
hi default NotifyTRACEIcon guifg=#D484FF
hi default NotifyERRORTitle guifg=#F70067
hi default NotifyWARNTitle guifg=#F79000
hi default NotifyINFOTitle guifg=#A9FF68
hi default NotifyDEBUGTitle guifg=#8B8B8B
hi default NotifyTRACETitle guifg=#D484FF
hi default link NotifyERRORBody Normal
hi default link NotifyWARNBody Normal
hi default link NotifyINFOBody Normal
hi default link NotifyDEBUGBody Normal
hi default link NotifyTRACEBody Normal
]])
end

M.setup()

vim.cmd[[
augroup nvim_notify
vim.cmd([[
augroup NvimNotifyRefreshHighlights
autocmd!
autocmd ColorScheme * lua require('notify.config.highlights').setup()
augroup END
]]
]])

return M
55 changes: 54 additions & 1 deletion lua/notify/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@ local M = {}

require("notify.config.highlights")

local BUILTIN_STAGES = {
FADE = "fade",
SLIDE = "slide",
FADE_IN_SLIDE_OUT = "fade_in_slide_out",
STATIC = "static",
}

local default_config = {
timeout = 5000,
stages = BUILTIN_STAGES.FADE_IN_SLIDE_OUT,
background_colour = "Normal",
icons = {
ERROR = "",
WARN = "",
Expand All @@ -14,14 +24,57 @@ local default_config = {

local user_config = default_config

local function validate_highlight(colour_or_group, needs_opacity)
if colour_or_group:sub(1, 1) == "#" then
return colour_or_group
end
local group_bg = vim.fn.synIDattr(vim.fn.synIDtrans(vim.fn.hlID(colour_or_group)), "bg")
if group_bg == "" then
if needs_opacity then
vim.notify(
"Highlight group '"
.. colour_or_group
.. "' has no background highlight.\n\n"
.. "Please provide an RGB hex value or highlight group with a background value for 'background_colour' option\n\n"
.. "Defaulting to #000000",
"warn",
{ title = "nvim-notify" }
)
end
return "#000000"
end
return group_bg
end

function M.setup(config)
local filled = vim.tbl_deep_extend("keep", config or {}, default_config)
user_config = filled
require("dapui.config.highlights").setup()
local stages = M.stages()

local needs_opacity = vim.tbl_contains(
{ BUILTIN_STAGES.FADE_IN_SLIDE_OUT, BUILTIN_STAGES.FADE },
stages
)

user_config.background_colour = validate_highlight(user_config.background_colour, needs_opacity)
end

---@param colour_or_group string

function M.background_colour()
return user_config.background_colour
end

function M.icons()
return user_config.icons
end

function M.stages()
return user_config.stages
end

function M.default_timeout()
return user_config.timeout
end

return M
Loading

0 comments on commit 280a08c

Please sign in to comment.