Add mouse grab (#1501)
* Add mouse grab We now also send mouse movement events only to the interested view. * Add deprecation messages handler * Make various `View`s respect `on_mouse_left` * `StatusView` * `TitleView` * `TreeView` * `ToolbarView` * Fix scrollbar in `TreeView` not updating We were in some cases sending outdated mouse positions to the scrollbar, which made it think that the mouse was hovering it. This also updates the hovered item more responsively during scroll.
This commit is contained in:
parent
39182d49d6
commit
43f9b8accc
|
@ -104,7 +104,7 @@ end, t)
|
|||
|
||||
command.add(nil, {
|
||||
["root:scroll"] = function(delta)
|
||||
local view = (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) or core.active_view
|
||||
local view = core.root_view.overlapping_view or core.active_view
|
||||
if view and view.scrollable then
|
||||
view.scroll.to.y = view.scroll.to.y + delta * -config.mouse_wheel_scroll
|
||||
return true
|
||||
|
@ -112,7 +112,7 @@ command.add(nil, {
|
|||
return false
|
||||
end,
|
||||
["root:horizontal-scroll"] = function(delta)
|
||||
local view = (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) or core.active_view
|
||||
local view = core.root_view.overlapping_view or core.active_view
|
||||
if view and view.scrollable then
|
||||
view.scroll.to.x = view.scroll.to.x + delta * -config.mouse_wheel_scroll
|
||||
return true
|
||||
|
@ -154,7 +154,7 @@ command.add(function(node)
|
|||
)
|
||||
|
||||
command.add(function()
|
||||
local node = core.root_view.overlapping_node
|
||||
local node = core.root_view.root_node:get_child_overlapping_point(core.root_view.mouse.x, core.root_view.mouse.y)
|
||||
if not node then return false end
|
||||
return (node.hovered_tab or node.hovered_scroll_button > 0) and true, node
|
||||
end,
|
||||
|
|
|
@ -1490,5 +1490,16 @@ function core.on_error(err)
|
|||
end
|
||||
|
||||
|
||||
local alerted_deprecations = {}
|
||||
---Show deprecation notice once per `kind`.
|
||||
---
|
||||
---@param kind string
|
||||
function core.deprecation_log(kind)
|
||||
if alerted_deprecations[kind] then return end
|
||||
alerted_deprecations[kind] = true
|
||||
core.warn("Used deprecated functionality [%s]. Check if your plugins are up to date.", kind)
|
||||
end
|
||||
|
||||
|
||||
return core
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ function Node:new(type)
|
|||
if self.type == "leaf" then
|
||||
self:add_view(EmptyView())
|
||||
end
|
||||
self.hovered = {x = -1, y = -1 }
|
||||
self.hovered_close = 0
|
||||
self.tab_shift = 0
|
||||
self.tab_offset = 1
|
||||
|
@ -33,9 +32,10 @@ function Node:propagate(fn, ...)
|
|||
end
|
||||
|
||||
|
||||
---@deprecated
|
||||
function Node:on_mouse_moved(x, y, ...)
|
||||
core.deprecation_log("Node:on_mouse_moved")
|
||||
if self.type == "leaf" then
|
||||
self.hovered.x, self.hovered.y = x, y
|
||||
self.active_view:on_mouse_moved(x, y, ...)
|
||||
else
|
||||
self:propagate("on_mouse_moved", x, y, ...)
|
||||
|
@ -43,7 +43,9 @@ function Node:on_mouse_moved(x, y, ...)
|
|||
end
|
||||
|
||||
|
||||
---@deprecated
|
||||
function Node:on_mouse_released(...)
|
||||
core.deprecation_log("Node:on_mouse_released")
|
||||
if self.type == "leaf" then
|
||||
self.active_view:on_mouse_released(...)
|
||||
else
|
||||
|
@ -52,7 +54,9 @@ function Node:on_mouse_released(...)
|
|||
end
|
||||
|
||||
|
||||
---@deprecated
|
||||
function Node:on_mouse_left()
|
||||
core.deprecation_log("Node:on_mouse_left")
|
||||
if self.type == "leaf" then
|
||||
self.active_view:on_mouse_left()
|
||||
else
|
||||
|
@ -60,7 +64,10 @@ function Node:on_mouse_left()
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
---@deprecated
|
||||
function Node:on_touch_moved(...)
|
||||
core.deprecation_log("Node:on_touch_moved")
|
||||
if self.type == "leaf" then
|
||||
self.active_view:on_touch_moved(...)
|
||||
else
|
||||
|
@ -68,6 +75,7 @@ function Node:on_touch_moved(...)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:consume(node)
|
||||
for k, _ in pairs(self) do self[k] = nil end
|
||||
for k, v in pairs(node) do self[k] = v end
|
||||
|
@ -489,7 +497,7 @@ function Node:update()
|
|||
for _, view in ipairs(self.views) do
|
||||
view:update()
|
||||
end
|
||||
self:tab_hovered_update(self.hovered.x, self.hovered.y)
|
||||
self:tab_hovered_update(core.root_view.mouse.x, core.root_view.mouse.y)
|
||||
local tab_width = self:target_tab_width()
|
||||
self:move_towards("tab_shift", tab_width * (self.tab_offset - 1), nil, "tabs")
|
||||
self:move_towards("tab_width", tab_width, nil, "tabs")
|
||||
|
|
|
@ -24,6 +24,9 @@ function RootView:new()
|
|||
base_color = style.drag_overlay_tab,
|
||||
color = { table.unpack(style.drag_overlay_tab) } }
|
||||
self.drag_overlay_tab.to = { x = 0, y = 0, w = 0, h = 0 }
|
||||
self.grab = nil -- = {view = nil, button = nil}
|
||||
self.overlapping_view = nil
|
||||
self.touched_view = nil
|
||||
end
|
||||
|
||||
|
||||
|
@ -116,6 +119,31 @@ function RootView:close_all_docviews(keep_active)
|
|||
end
|
||||
|
||||
|
||||
---Obtain mouse grab.
|
||||
---
|
||||
---This means that mouse movements will be sent to the specified view, even when
|
||||
---those occur outside of it.
|
||||
---There can't be multiple mouse grabs, even for different buttons.
|
||||
---@see RootView:ungrab_mouse
|
||||
---@param button core.view.mousebutton
|
||||
---@param view core.view
|
||||
function RootView:grab_mouse(button, view)
|
||||
assert(self.grab == nil)
|
||||
self.grab = {view = view, button = button}
|
||||
end
|
||||
|
||||
|
||||
---Release mouse grab.
|
||||
---
|
||||
---The specified button *must* be the last button that grabbed the mouse.
|
||||
---@see RootView:grab_mouse
|
||||
---@param button core.view.mousebutton
|
||||
function RootView:ungrab_mouse(button)
|
||||
assert(self.grab and self.grab.button == button)
|
||||
self.grab = nil
|
||||
end
|
||||
|
||||
|
||||
---Function to intercept mouse pressed events on the active view.
|
||||
---Do nothing by default.
|
||||
---@param button core.view.mousebutton
|
||||
|
@ -132,6 +160,10 @@ end
|
|||
---@param clicks integer
|
||||
---@return boolean
|
||||
function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||
-- If there is a grab, release it first
|
||||
if self.grab then
|
||||
self:on_mouse_released(self.grab.button, x, y)
|
||||
end
|
||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
if div and (node and not node.active_view:scrollbar_overlaps_point(x, y)) then
|
||||
|
@ -156,6 +188,7 @@ function RootView:on_mouse_pressed(button, x, y, clicks)
|
|||
end
|
||||
elseif not self.dragged_node then -- avoid sending on_mouse_pressed events when dragging tabs
|
||||
core.set_active_view(node.active_view)
|
||||
self:grab_mouse(button, node.active_view)
|
||||
return self.on_view_mouse_pressed(button, x, y, clicks) or node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||
end
|
||||
end
|
||||
|
@ -188,6 +221,21 @@ end
|
|||
---@param x number
|
||||
---@param y number
|
||||
function RootView:on_mouse_released(button, x, y, ...)
|
||||
if self.grab then
|
||||
if self.grab.button == button then
|
||||
local grabbed_view = self.grab.view
|
||||
grabbed_view:on_mouse_released(button, x, y, ...)
|
||||
self:ungrab_mouse(button)
|
||||
|
||||
-- If the mouse was released over a different view, send it the mouse position
|
||||
local hovered_view = self.root_node:get_child_overlapping_point(x, y)
|
||||
if grabbed_view ~= hovered_view then
|
||||
self:on_mouse_moved(x, y, 0, 0)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if self.dragged_divider then
|
||||
self.dragged_divider = nil
|
||||
end
|
||||
|
@ -228,8 +276,6 @@ function RootView:on_mouse_released(button, x, y, ...)
|
|||
end
|
||||
self.dragged_node = nil
|
||||
end
|
||||
else -- avoid sending on_mouse_released events when dragging tabs
|
||||
self.root_node:on_mouse_released(button, x, y, ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -250,6 +296,14 @@ end
|
|||
---@param dx number
|
||||
---@param dy number
|
||||
function RootView:on_mouse_moved(x, y, dx, dy)
|
||||
self.mouse.x, self.mouse.y = x, y
|
||||
|
||||
if self.grab then
|
||||
self.grab.view:on_mouse_moved(x, y, dx, dy)
|
||||
core.request_cursor(self.grab.view.cursor)
|
||||
return
|
||||
end
|
||||
|
||||
if core.active_view == core.nag_view then
|
||||
core.request_cursor("arrow")
|
||||
core.active_view:on_mouse_moved(x, y, dx, dy)
|
||||
|
@ -269,8 +323,6 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
|||
return
|
||||
end
|
||||
|
||||
self.mouse.x, self.mouse.y = x, y
|
||||
|
||||
local dn = self.dragged_node
|
||||
if dn and not dn.dragging then
|
||||
-- start dragging only after enough movement
|
||||
|
@ -283,30 +335,33 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
|||
-- avoid sending on_mouse_moved events when dragging tabs
|
||||
if dn then return end
|
||||
|
||||
self.root_node:on_mouse_moved(x, y, dx, dy)
|
||||
local last_overlapping_view = self.overlapping_view
|
||||
local overlapping_node = self.root_node:get_child_overlapping_point(x, y)
|
||||
self.overlapping_view = overlapping_node and overlapping_node.active_view
|
||||
|
||||
local last_overlapping_node = self.overlapping_node
|
||||
self.overlapping_node = self.root_node:get_child_overlapping_point(x, y)
|
||||
|
||||
if last_overlapping_node and last_overlapping_node ~= self.overlapping_node then
|
||||
last_overlapping_node:on_mouse_left()
|
||||
if last_overlapping_view and last_overlapping_view ~= self.overlapping_view then
|
||||
last_overlapping_view:on_mouse_left()
|
||||
end
|
||||
if not self.overlapping_node then return end
|
||||
|
||||
if not self.overlapping_view then return end
|
||||
|
||||
self.overlapping_view:on_mouse_moved(x, y, dx, dy)
|
||||
core.request_cursor(self.overlapping_view.cursor)
|
||||
|
||||
if not overlapping_node then return end
|
||||
|
||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||
if self.overlapping_node:get_scroll_button_index(x, y) or self.overlapping_node:is_in_tab_area(x, y) then
|
||||
if overlapping_node:get_scroll_button_index(x, y) or overlapping_node:is_in_tab_area(x, y) then
|
||||
core.request_cursor("arrow")
|
||||
elseif div and not self.overlapping_node.active_view:scrollbar_overlaps_point(x, y) then
|
||||
elseif div and not self.overlapping_view:scrollbar_overlaps_point(x, y) then
|
||||
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||
else
|
||||
core.request_cursor(self.overlapping_node.active_view.cursor)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_left()
|
||||
if self.overlapping_node then
|
||||
self.overlapping_node:on_mouse_left()
|
||||
if self.overlapping_view then
|
||||
self.overlapping_view:on_mouse_left()
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -333,15 +388,16 @@ function RootView:on_text_input(...)
|
|||
end
|
||||
|
||||
function RootView:on_touch_pressed(x, y, ...)
|
||||
self.touched_node = self.root_node:get_child_overlapping_point(x, y)
|
||||
local touched_node = self.root_node:get_child_overlapping_point(x, y)
|
||||
self.touched_view = touched_node and touched_node.active_view
|
||||
end
|
||||
|
||||
function RootView:on_touch_released(x, y, ...)
|
||||
self.touched_node = nil
|
||||
self.touched_view = nil
|
||||
end
|
||||
|
||||
function RootView:on_touch_moved(x, y, dx, dy, ...)
|
||||
if not self.touched_node then return end
|
||||
if not self.touched_view then return end
|
||||
if core.active_view == core.nag_view then
|
||||
core.active_view:on_touch_moved(x, y, dx, dy, ...)
|
||||
return
|
||||
|
@ -372,7 +428,7 @@ function RootView:on_touch_moved(x, y, dx, dy, ...)
|
|||
-- avoid sending on_touch_moved events when dragging tabs
|
||||
if dn then return end
|
||||
|
||||
self.touched_node:on_touch_moved(x, y, dx, dy, ...)
|
||||
self.touched_view:on_touch_moved(x, y, dx, dy, ...)
|
||||
end
|
||||
|
||||
function RootView:on_ime_text_editing(...)
|
||||
|
|
|
@ -989,6 +989,12 @@ function StatusView:on_mouse_pressed(button, x, y, clicks)
|
|||
end
|
||||
|
||||
|
||||
function StatusView:on_mouse_left()
|
||||
StatusView.super.on_mouse_left(self)
|
||||
self.hovered_item = {}
|
||||
end
|
||||
|
||||
|
||||
function StatusView:on_mouse_moved(x, y, dx, dy)
|
||||
if not self.visible then return end
|
||||
StatusView.super.on_mouse_moved(self, x, y, dx, dy)
|
||||
|
|
|
@ -112,6 +112,12 @@ function TitleView:on_mouse_pressed(button, x, y, clicks)
|
|||
end
|
||||
|
||||
|
||||
function TitleView:on_mouse_left()
|
||||
TitleView.super.on_mouse_left(self)
|
||||
self.hovered_item = nil
|
||||
end
|
||||
|
||||
|
||||
function TitleView:on_mouse_moved(px, py, ...)
|
||||
if self.size.y == 0 then return end
|
||||
TitleView.super.on_mouse_moved(self, px, py, ...)
|
||||
|
|
|
@ -100,6 +100,16 @@ function ToolbarView:on_mouse_pressed(button, x, y, clicks)
|
|||
end
|
||||
|
||||
|
||||
function ToolbarView:on_mouse_left()
|
||||
ToolbarView.super.on_mouse_left(self)
|
||||
if self.tooltip then
|
||||
core.status_view:remove_tooltip()
|
||||
self.tooltip = false
|
||||
end
|
||||
self.hovered_item = nil
|
||||
end
|
||||
|
||||
|
||||
function ToolbarView:on_mouse_moved(px, py, ...)
|
||||
if not self.visible then return end
|
||||
ToolbarView.super.on_mouse_moved(self, px, py, ...)
|
||||
|
|
|
@ -51,7 +51,7 @@ function TreeView:new()
|
|||
self.target_size = config.plugins.treeview.size
|
||||
self.cache = {}
|
||||
self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 }
|
||||
self.cursor_pos = { x = 0, y = 0 }
|
||||
self.last_scroll_y = 0
|
||||
|
||||
self.item_icon_width = 0
|
||||
self.item_text_spacing = 0
|
||||
|
@ -251,10 +251,9 @@ function TreeView:get_text_bounding_box(item, x, y, w, h)
|
|||
end
|
||||
|
||||
|
||||
|
||||
function TreeView:on_mouse_moved(px, py, ...)
|
||||
if not self.visible then return end
|
||||
self.cursor_pos.x = px
|
||||
self.cursor_pos.y = py
|
||||
if TreeView.super.on_mouse_moved(self, px, py, ...) then
|
||||
-- mouse movement handled by the View (scrollbar)
|
||||
self.hovered_item = nil
|
||||
|
@ -281,6 +280,12 @@ function TreeView:on_mouse_moved(px, py, ...)
|
|||
end
|
||||
|
||||
|
||||
function TreeView:on_mouse_left()
|
||||
TreeView.super.on_mouse_left(self)
|
||||
self.hovered_item = nil
|
||||
end
|
||||
|
||||
|
||||
function TreeView:update()
|
||||
-- update width
|
||||
local dest = self.visible and self.target_size or 0
|
||||
|
@ -304,10 +309,10 @@ function TreeView:update()
|
|||
self.item_text_spacing = style.icon_font:get_width("f") / 2
|
||||
|
||||
-- this will make sure hovered_item is updated
|
||||
-- we don't want events when the thing is scrolling fast
|
||||
local dy = math.abs(self.scroll.to.y - self.scroll.y)
|
||||
if self.scroll.to.y ~= 0 and dy < self:get_item_height() then
|
||||
self:on_mouse_moved(self.cursor_pos.x, self.cursor_pos.y, 0, 0)
|
||||
local dy = math.abs(self.last_scroll_y - self.scroll.y)
|
||||
if dy > 0 then
|
||||
self:on_mouse_moved(core.root_view.mouse.x, core.root_view.mouse.y, 0, 0)
|
||||
self.last_scroll_y = self.scroll.y
|
||||
end
|
||||
|
||||
local config = config.plugins.treeview
|
||||
|
@ -751,7 +756,7 @@ command.add(
|
|||
|
||||
["treeview-context:show"] = function()
|
||||
if view.hovered_item then
|
||||
menu:show(view.cursor_pos.x, view.cursor_pos.y)
|
||||
menu:show(core.root_view.mouse.x, core.root_view.mouse.y)
|
||||
return
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue