diff --git a/data/core/commands/root.lua b/data/core/commands/root.lua index 0ae4c68e..03f22c25 100644 --- a/data/core/commands/root.lua +++ b/data/core/commands/root.lua @@ -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, diff --git a/data/core/init.lua b/data/core/init.lua index 6fcd188a..5034f0ea 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -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 diff --git a/data/core/node.lua b/data/core/node.lua index e282882c..0ea2c07e 100644 --- a/data/core/node.lua +++ b/data/core/node.lua @@ -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") diff --git a/data/core/rootview.lua b/data/core/rootview.lua index c6b18c47..18ae40ab 100644 --- a/data/core/rootview.lua +++ b/data/core/rootview.lua @@ -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(...) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 930c7aef..731c0552 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -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) diff --git a/data/core/titleview.lua b/data/core/titleview.lua index 69315be1..482d6c8e 100644 --- a/data/core/titleview.lua +++ b/data/core/titleview.lua @@ -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, ...) diff --git a/data/plugins/toolbarview.lua b/data/plugins/toolbarview.lua index caf86d71..ddc0f39d 100644 --- a/data/plugins/toolbarview.lua +++ b/data/plugins/toolbarview.lua @@ -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, ...) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 5256302e..2a773edb 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -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