Merge pull request #715 from Guldoman/split_rootview
Split `Node` and `EmptyView` from `RootView`
This commit is contained in:
commit
9a813c818b
|
@ -0,0 +1,40 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
local View = require "core.view"
|
||||||
|
|
||||||
|
local EmptyView = View:extend()
|
||||||
|
|
||||||
|
local function draw_text(x, y, color)
|
||||||
|
local th = style.big_font:get_height()
|
||||||
|
local dh = 2 * th + style.padding.y * 2
|
||||||
|
local x1, y1 = x, y + (dh - th) / 2
|
||||||
|
x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color)
|
||||||
|
renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color)
|
||||||
|
x = x + style.padding.x
|
||||||
|
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
|
||||||
|
local lines = {
|
||||||
|
{ fmt = "%s to run a command", cmd = "core:find-command" },
|
||||||
|
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
|
||||||
|
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
|
||||||
|
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
|
||||||
|
}
|
||||||
|
th = style.font:get_height()
|
||||||
|
y = y + (dh - (th + style.padding.y) * #lines) / 2
|
||||||
|
local w = 0
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
local text = string.format(line.fmt, keymap.get_binding(line.cmd))
|
||||||
|
w = math.max(w, renderer.draw_text(style.font, text, x + style.padding.x, y, color))
|
||||||
|
y = y + th + style.padding.y
|
||||||
|
end
|
||||||
|
return w, dh
|
||||||
|
end
|
||||||
|
|
||||||
|
function EmptyView:draw()
|
||||||
|
self:draw_background(style.background)
|
||||||
|
local w, h = draw_text(0, 0, { 0, 0, 0, 0 })
|
||||||
|
local x = self.position.x + math.max(style.padding.x, (self.size.x - w) / 2)
|
||||||
|
local y = self.position.y + (self.size.y - h) / 2
|
||||||
|
draw_text(x, y, style.dim)
|
||||||
|
end
|
||||||
|
|
||||||
|
return EmptyView
|
|
@ -0,0 +1,737 @@
|
||||||
|
local core = require "core"
|
||||||
|
local common = require "core.common"
|
||||||
|
local config = require "core.config"
|
||||||
|
local style = require "core.style"
|
||||||
|
local Object = require "core.object"
|
||||||
|
local EmptyView = require "core.emptyview"
|
||||||
|
local View = require "core.view"
|
||||||
|
|
||||||
|
local Node = Object:extend()
|
||||||
|
|
||||||
|
function Node:new(type)
|
||||||
|
self.type = type or "leaf"
|
||||||
|
self.position = { x = 0, y = 0 }
|
||||||
|
self.size = { x = 0, y = 0 }
|
||||||
|
self.views = {}
|
||||||
|
self.divider = 0.5
|
||||||
|
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
|
||||||
|
self.tab_width = style.tab_width
|
||||||
|
self.move_towards = View.move_towards
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:propagate(fn, ...)
|
||||||
|
self.a[fn](self.a, ...)
|
||||||
|
self.b[fn](self.b, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:on_mouse_moved(x, y, ...)
|
||||||
|
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, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:on_mouse_released(...)
|
||||||
|
if self.type == "leaf" then
|
||||||
|
self.active_view:on_mouse_released(...)
|
||||||
|
else
|
||||||
|
self:propagate("on_mouse_released", ...)
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local type_map = { up="vsplit", down="vsplit", left="hsplit", right="hsplit" }
|
||||||
|
|
||||||
|
-- The "locked" argument below should be in the form {x = <boolean>, y = <boolean>}
|
||||||
|
-- and it indicates if the node want to have a fixed size along the axis where the
|
||||||
|
-- boolean is true. If not it will be expanded to take all the available space.
|
||||||
|
-- The "resizable" flag indicates if, along the "locked" axis the node can be resized
|
||||||
|
-- by the user. If the node is marked as resizable their view should provide a
|
||||||
|
-- set_target_size method.
|
||||||
|
function Node:split(dir, view, locked, resizable)
|
||||||
|
assert(self.type == "leaf", "Tried to split non-leaf node")
|
||||||
|
local node_type = assert(type_map[dir], "Invalid direction")
|
||||||
|
local last_active = core.active_view
|
||||||
|
local child = Node()
|
||||||
|
child:consume(self)
|
||||||
|
self:consume(Node(node_type))
|
||||||
|
self.a = child
|
||||||
|
self.b = Node()
|
||||||
|
if view then self.b:add_view(view) end
|
||||||
|
if locked then
|
||||||
|
assert(type(locked) == 'table')
|
||||||
|
self.b.locked = locked
|
||||||
|
self.b.resizable = resizable or false
|
||||||
|
core.set_active_view(last_active)
|
||||||
|
end
|
||||||
|
if dir == "up" or dir == "left" then
|
||||||
|
self.a, self.b = self.b, self.a
|
||||||
|
return self.a
|
||||||
|
end
|
||||||
|
return self.b
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:remove_view(root, view)
|
||||||
|
if #self.views > 1 then
|
||||||
|
local idx = self:get_view_idx(view)
|
||||||
|
if idx < self.tab_offset then
|
||||||
|
self.tab_offset = self.tab_offset - 1
|
||||||
|
end
|
||||||
|
table.remove(self.views, idx)
|
||||||
|
if self.active_view == view then
|
||||||
|
self:set_active_view(self.views[idx] or self.views[#self.views])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local parent = self:get_parent_node(root)
|
||||||
|
local is_a = (parent.a == self)
|
||||||
|
local other = parent[is_a and "b" or "a"]
|
||||||
|
local locked_size_x, locked_size_y = other:get_locked_size()
|
||||||
|
local locked_size
|
||||||
|
if parent.type == "hsplit" then
|
||||||
|
locked_size = locked_size_x
|
||||||
|
else
|
||||||
|
locked_size = locked_size_y
|
||||||
|
end
|
||||||
|
local next_primary
|
||||||
|
if self.is_primary_node then
|
||||||
|
next_primary = core.root_view:select_next_primary_node()
|
||||||
|
end
|
||||||
|
if locked_size or (self.is_primary_node and not next_primary) then
|
||||||
|
self.views = {}
|
||||||
|
self:add_view(EmptyView())
|
||||||
|
else
|
||||||
|
if other == next_primary then
|
||||||
|
next_primary = parent
|
||||||
|
end
|
||||||
|
parent:consume(other)
|
||||||
|
local p = parent
|
||||||
|
while p.type ~= "leaf" do
|
||||||
|
p = p[is_a and "a" or "b"]
|
||||||
|
end
|
||||||
|
p:set_active_view(p.active_view)
|
||||||
|
if self.is_primary_node then
|
||||||
|
next_primary.is_primary_node = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
core.last_active_view = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:close_view(root, view)
|
||||||
|
local do_close = function()
|
||||||
|
self:remove_view(root, view)
|
||||||
|
end
|
||||||
|
view:try_close(do_close)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:close_active_view(root)
|
||||||
|
self:close_view(root, self.active_view)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:add_view(view, idx)
|
||||||
|
assert(self.type == "leaf", "Tried to add view to non-leaf node")
|
||||||
|
assert(not self.locked, "Tried to add view to locked node")
|
||||||
|
if self.views[1] and self.views[1]:is(EmptyView) then
|
||||||
|
table.remove(self.views)
|
||||||
|
end
|
||||||
|
table.insert(self.views, idx or (#self.views + 1), view)
|
||||||
|
self:set_active_view(view)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:set_active_view(view)
|
||||||
|
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
|
||||||
|
self.active_view = view
|
||||||
|
core.set_active_view(view)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_view_idx(view)
|
||||||
|
for i, v in ipairs(self.views) do
|
||||||
|
if v == view then return i end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_node_for_view(view)
|
||||||
|
for _, v in ipairs(self.views) do
|
||||||
|
if v == view then return self end
|
||||||
|
end
|
||||||
|
if self.type ~= "leaf" then
|
||||||
|
return self.a:get_node_for_view(view) or self.b:get_node_for_view(view)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_parent_node(root)
|
||||||
|
if root.a == self or root.b == self then
|
||||||
|
return root
|
||||||
|
elseif root.type ~= "leaf" then
|
||||||
|
return self:get_parent_node(root.a) or self:get_parent_node(root.b)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_children(t)
|
||||||
|
t = t or {}
|
||||||
|
for _, view in ipairs(self.views) do
|
||||||
|
table.insert(t, view)
|
||||||
|
end
|
||||||
|
if self.a then self.a:get_children(t) end
|
||||||
|
if self.b then self.b:get_children(t) end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- return the width including the padding space and separately
|
||||||
|
-- the padding space itself
|
||||||
|
local function get_scroll_button_width()
|
||||||
|
local w = style.icon_font:get_width(">")
|
||||||
|
local pad = w
|
||||||
|
return w + 2 * pad, pad
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_divider_overlapping_point(px, py)
|
||||||
|
if self.type ~= "leaf" then
|
||||||
|
local axis = self.type == "hsplit" and "x" or "y"
|
||||||
|
if self.a:is_resizable(axis) and self.b:is_resizable(axis) then
|
||||||
|
local p = 6
|
||||||
|
local x, y, w, h = self:get_divider_rect()
|
||||||
|
x, y = x - p, y - p
|
||||||
|
w, h = w + p * 2, h + p * 2
|
||||||
|
if px > x and py > y and px < x + w and py < y + h then
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return self.a:get_divider_overlapping_point(px, py)
|
||||||
|
or self.b:get_divider_overlapping_point(px, py)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_visible_tabs_number()
|
||||||
|
return math.min(#self.views - self.tab_offset + 1, config.max_tabs)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_tab_overlapping_point(px, py)
|
||||||
|
if not self:should_show_tabs() then return nil end
|
||||||
|
local tabs_number = self:get_visible_tabs_number()
|
||||||
|
local x1, y1, w, h = self:get_tab_rect(self.tab_offset)
|
||||||
|
local x2, y2 = self:get_tab_rect(self.tab_offset + tabs_number)
|
||||||
|
if px >= x1 and py >= y1 and px < x2 and py < y1 + h then
|
||||||
|
return math.floor((px - x1) / w) + self.tab_offset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:should_show_tabs()
|
||||||
|
if self.locked then return false end
|
||||||
|
local dn = core.root_view.dragged_node
|
||||||
|
if #self.views > 1
|
||||||
|
or (dn and dn.dragging) then -- show tabs while dragging
|
||||||
|
return true
|
||||||
|
elseif config.always_show_tabs then
|
||||||
|
return not self.views[1]:is(EmptyView)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function close_button_location(x, w)
|
||||||
|
local cw = style.icon_font:get_width("C")
|
||||||
|
local pad = style.padding.y
|
||||||
|
return x + w - pad - cw, cw, pad
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_scroll_button_index(px, py)
|
||||||
|
if #self.views == 1 then return end
|
||||||
|
for i = 1, 2 do
|
||||||
|
local x, y, w, h = self:get_scroll_button_rect(i)
|
||||||
|
if px >= x and px < x + w and py >= y and py < y + h then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:tab_hovered_update(px, py)
|
||||||
|
local tab_index = self:get_tab_overlapping_point(px, py)
|
||||||
|
self.hovered_tab = tab_index
|
||||||
|
self.hovered_close = 0
|
||||||
|
self.hovered_scroll_button = 0
|
||||||
|
if tab_index then
|
||||||
|
local x, y, w, h = self:get_tab_rect(tab_index)
|
||||||
|
local cx, cw = close_button_location(x, w)
|
||||||
|
if px >= cx and px < cx + cw and py >= y and py < y + h and config.tab_close_button then
|
||||||
|
self.hovered_close = tab_index
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.hovered_scroll_button = self:get_scroll_button_index(px, py) or 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_child_overlapping_point(x, y)
|
||||||
|
local child
|
||||||
|
if self.type == "leaf" then
|
||||||
|
return self
|
||||||
|
elseif self.type == "hsplit" then
|
||||||
|
child = (x < self.b.position.x) and self.a or self.b
|
||||||
|
elseif self.type == "vsplit" then
|
||||||
|
child = (y < self.b.position.y) and self.a or self.b
|
||||||
|
end
|
||||||
|
return child:get_child_overlapping_point(x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_scroll_button_rect(index)
|
||||||
|
local w, pad = get_scroll_button_width()
|
||||||
|
local h = style.font:get_height() + style.padding.y * 2
|
||||||
|
local x = self.position.x + (index == 1 and 0 or self.size.x - w)
|
||||||
|
return x, self.position.y, w, h, pad
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_tab_rect(idx)
|
||||||
|
local sbw = get_scroll_button_width()
|
||||||
|
local maxw = self.size.x - 2 * sbw
|
||||||
|
local x0 = self.position.x + sbw
|
||||||
|
local x1 = x0 + common.clamp(self.tab_width * (idx - 1) - self.tab_shift, 0, maxw)
|
||||||
|
local x2 = x0 + common.clamp(self.tab_width * idx - self.tab_shift, 0, maxw)
|
||||||
|
local h = style.font:get_height() + style.padding.y * 2
|
||||||
|
return x1, self.position.y, x2 - x1, h
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_divider_rect()
|
||||||
|
local x, y = self.position.x, self.position.y
|
||||||
|
if self.type == "hsplit" then
|
||||||
|
return x + self.a.size.x, y, style.divider_size, self.size.y
|
||||||
|
elseif self.type == "vsplit" then
|
||||||
|
return x, y + self.a.size.y, self.size.x, style.divider_size
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Return two values for x and y axis and each of them is either falsy or a number.
|
||||||
|
-- A falsy value indicate no fixed size along the corresponding direction.
|
||||||
|
function Node:get_locked_size()
|
||||||
|
if self.type == "leaf" then
|
||||||
|
if self.locked then
|
||||||
|
local size = self.active_view.size
|
||||||
|
-- The values below should be either a falsy value or a number
|
||||||
|
local sx = (self.locked and self.locked.x) and size.x
|
||||||
|
local sy = (self.locked and self.locked.y) and size.y
|
||||||
|
return sx, sy
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local x1, y1 = self.a:get_locked_size()
|
||||||
|
local x2, y2 = self.b:get_locked_size()
|
||||||
|
-- The values below should be either a falsy value or a number
|
||||||
|
local sx, sy
|
||||||
|
if self.type == 'hsplit' then
|
||||||
|
if x1 and x2 then
|
||||||
|
local dsx = (x1 < 1 or x2 < 1) and 0 or style.divider_size
|
||||||
|
sx = x1 + x2 + dsx
|
||||||
|
end
|
||||||
|
sy = y1 or y2
|
||||||
|
else
|
||||||
|
if y1 and y2 then
|
||||||
|
local dsy = (y1 < 1 or y2 < 1) and 0 or style.divider_size
|
||||||
|
sy = y1 + y2 + dsy
|
||||||
|
end
|
||||||
|
sx = x1 or x2
|
||||||
|
end
|
||||||
|
return sx, sy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node.copy_position_and_size(dst, src)
|
||||||
|
dst.position.x, dst.position.y = src.position.x, src.position.y
|
||||||
|
dst.size.x, dst.size.y = src.size.x, src.size.y
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- calculating the sizes is the same for hsplits and vsplits, except the x/y
|
||||||
|
-- axis are swapped; this function lets us use the same code for both
|
||||||
|
local function calc_split_sizes(self, x, y, x1, x2, y1, y2)
|
||||||
|
local ds = ((x1 and x1 < 1) or (x2 and x2 < 1)) and 0 or style.divider_size
|
||||||
|
local n = x1 and x1 + ds or (x2 and self.size[x] - x2 or math.floor(self.size[x] * self.divider))
|
||||||
|
self.a.position[x] = self.position[x]
|
||||||
|
self.a.position[y] = self.position[y]
|
||||||
|
self.a.size[x] = n - ds
|
||||||
|
self.a.size[y] = self.size[y]
|
||||||
|
self.b.position[x] = self.position[x] + n
|
||||||
|
self.b.position[y] = self.position[y]
|
||||||
|
self.b.size[x] = self.size[x] - n
|
||||||
|
self.b.size[y] = self.size[y]
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:update_layout()
|
||||||
|
if self.type == "leaf" then
|
||||||
|
local av = self.active_view
|
||||||
|
if self:should_show_tabs() then
|
||||||
|
local _, _, _, th = self:get_tab_rect(1)
|
||||||
|
av.position.x, av.position.y = self.position.x, self.position.y + th
|
||||||
|
av.size.x, av.size.y = self.size.x, self.size.y - th
|
||||||
|
else
|
||||||
|
Node.copy_position_and_size(av, self)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local x1, y1 = self.a:get_locked_size()
|
||||||
|
local x2, y2 = self.b:get_locked_size()
|
||||||
|
if self.type == "hsplit" then
|
||||||
|
calc_split_sizes(self, "x", "y", x1, x2)
|
||||||
|
elseif self.type == "vsplit" then
|
||||||
|
calc_split_sizes(self, "y", "x", y1, y2)
|
||||||
|
end
|
||||||
|
self.a:update_layout()
|
||||||
|
self.b:update_layout()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:scroll_tabs_to_visible()
|
||||||
|
local index = self:get_view_idx(self.active_view)
|
||||||
|
if index then
|
||||||
|
local tabs_number = self:get_visible_tabs_number()
|
||||||
|
if self.tab_offset > index then
|
||||||
|
self.tab_offset = index
|
||||||
|
elseif self.tab_offset + tabs_number - 1 < index then
|
||||||
|
self.tab_offset = index - tabs_number + 1
|
||||||
|
elseif tabs_number < config.max_tabs and self.tab_offset > 1 then
|
||||||
|
self.tab_offset = #self.views - config.max_tabs + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:scroll_tabs(dir)
|
||||||
|
local view_index = self:get_view_idx(self.active_view)
|
||||||
|
if dir == 1 then
|
||||||
|
if self.tab_offset > 1 then
|
||||||
|
self.tab_offset = self.tab_offset - 1
|
||||||
|
local last_index = self.tab_offset + self:get_visible_tabs_number() - 1
|
||||||
|
if view_index > last_index then
|
||||||
|
self:set_active_view(self.views[last_index])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif dir == 2 then
|
||||||
|
local tabs_number = self:get_visible_tabs_number()
|
||||||
|
if self.tab_offset + tabs_number - 1 < #self.views then
|
||||||
|
self.tab_offset = self.tab_offset + 1
|
||||||
|
local view_index = self:get_view_idx(self.active_view)
|
||||||
|
if view_index < self.tab_offset then
|
||||||
|
self:set_active_view(self.views[self.tab_offset])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:target_tab_width()
|
||||||
|
local n = self:get_visible_tabs_number()
|
||||||
|
local w = self.size.x - get_scroll_button_width() * 2
|
||||||
|
return common.clamp(style.tab_width, w / config.max_tabs, w / n)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:update()
|
||||||
|
if self.type == "leaf" then
|
||||||
|
self:scroll_tabs_to_visible()
|
||||||
|
for _, view in ipairs(self.views) do
|
||||||
|
view:update()
|
||||||
|
end
|
||||||
|
self:tab_hovered_update(self.hovered.x, self.hovered.y)
|
||||||
|
local tab_width = self:target_tab_width()
|
||||||
|
self:move_towards("tab_shift", tab_width * (self.tab_offset - 1))
|
||||||
|
self:move_towards("tab_width", tab_width)
|
||||||
|
else
|
||||||
|
self.a:update()
|
||||||
|
self.b:update()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
||||||
|
local ds = style.divider_size
|
||||||
|
local dots_width = style.font:get_width("…")
|
||||||
|
local color = style.dim
|
||||||
|
local padding_y = style.padding.y
|
||||||
|
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
||||||
|
if standalone then
|
||||||
|
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
|
||||||
|
end
|
||||||
|
if is_active then
|
||||||
|
color = style.text
|
||||||
|
renderer.draw_rect(x, y, w, h, style.background)
|
||||||
|
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
||||||
|
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
||||||
|
end
|
||||||
|
local cx, cw, cspace = close_button_location(x, w)
|
||||||
|
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
|
||||||
|
if show_close_button then
|
||||||
|
local close_style = is_close_hovered and style.text or style.dim
|
||||||
|
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
||||||
|
end
|
||||||
|
if is_hovered then
|
||||||
|
color = style.text
|
||||||
|
end
|
||||||
|
local padx = style.padding.x
|
||||||
|
-- Normally we should substract "cspace" from text_avail_width and from the
|
||||||
|
-- clipping width. It is the padding space we give to the left and right of the
|
||||||
|
-- close button. However, since we are using dots to terminate filenames, we
|
||||||
|
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
||||||
|
-- close button.
|
||||||
|
local text_avail_width = cx - x - padx
|
||||||
|
core.push_clip_rect(x, y, cx - x, h)
|
||||||
|
x, w = x + padx, w - padx * 2
|
||||||
|
local align = "center"
|
||||||
|
if style.font:get_width(text) > text_avail_width then
|
||||||
|
align = "left"
|
||||||
|
for i = 1, #text do
|
||||||
|
local reduced_text = text:sub(1, #text - i)
|
||||||
|
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
||||||
|
text = reduced_text .. "…"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
common.draw_text(style.font, color, text, align, x, y, w, h)
|
||||||
|
core.pop_clip_rect()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:draw_tabs()
|
||||||
|
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
||||||
|
local ds = style.divider_size
|
||||||
|
local dots_width = style.font:get_width("…")
|
||||||
|
core.push_clip_rect(x, y, self.size.x, h)
|
||||||
|
renderer.draw_rect(x, y, self.size.x, h, style.background2)
|
||||||
|
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
|
||||||
|
|
||||||
|
if self.tab_offset > 1 then
|
||||||
|
local button_style = self.hovered_scroll_button == 1 and style.text or style.dim
|
||||||
|
common.draw_text(style.icon_font, button_style, "<", nil, x + scroll_padding, y, 0, h)
|
||||||
|
end
|
||||||
|
|
||||||
|
local tabs_number = self:get_visible_tabs_number()
|
||||||
|
if #self.views > self.tab_offset + tabs_number - 1 then
|
||||||
|
local xrb, yrb, wrb = self:get_scroll_button_rect(2)
|
||||||
|
local button_style = self.hovered_scroll_button == 2 and style.text or style.dim
|
||||||
|
common.draw_text(style.icon_font, button_style, ">", nil, xrb + scroll_padding, yrb, 0, h)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
||||||
|
local view = self.views[i]
|
||||||
|
local x, y, w, h = self:get_tab_rect(i)
|
||||||
|
self:draw_tab(view:get_name(), view == self.active_view,
|
||||||
|
i == self.hovered_tab, i == self.hovered_close,
|
||||||
|
x, y, w, h)
|
||||||
|
end
|
||||||
|
|
||||||
|
core.pop_clip_rect()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:draw()
|
||||||
|
if self.type == "leaf" then
|
||||||
|
if self:should_show_tabs() then
|
||||||
|
self:draw_tabs()
|
||||||
|
end
|
||||||
|
local pos, size = self.active_view.position, self.active_view.size
|
||||||
|
core.push_clip_rect(pos.x, pos.y, size.x, size.y)
|
||||||
|
self.active_view:draw()
|
||||||
|
core.pop_clip_rect()
|
||||||
|
else
|
||||||
|
local x, y, w, h = self:get_divider_rect()
|
||||||
|
renderer.draw_rect(x, y, w, h, style.divider)
|
||||||
|
self:propagate("draw")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:is_empty()
|
||||||
|
if self.type == "leaf" then
|
||||||
|
return #self.views == 0 or (#self.views == 1 and self.views[1]:is(EmptyView))
|
||||||
|
else
|
||||||
|
return self.a:is_empty() and self.b:is_empty()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:close_all_docviews(keep_active)
|
||||||
|
local node_active_view = self.active_view
|
||||||
|
local lost_active_view = false
|
||||||
|
if self.type == "leaf" then
|
||||||
|
local i = 1
|
||||||
|
while i <= #self.views do
|
||||||
|
local view = self.views[i]
|
||||||
|
if view.context == "session" and (not keep_active or view ~= self.active_view) then
|
||||||
|
table.remove(self.views, i)
|
||||||
|
if view == node_active_view then
|
||||||
|
lost_active_view = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.tab_offset = 1
|
||||||
|
if #self.views == 0 and self.is_primary_node then
|
||||||
|
-- if we are not the primary view and we had the active view it doesn't
|
||||||
|
-- matter to reattribute the active view because, within the close_all_docviews
|
||||||
|
-- top call, the primary node will take the active view anyway.
|
||||||
|
-- Set the empty view and takes the active view.
|
||||||
|
self:add_view(EmptyView())
|
||||||
|
elseif #self.views > 0 and lost_active_view then
|
||||||
|
-- In practice we never get there but if a view remain we need
|
||||||
|
-- to reset the Node's active view.
|
||||||
|
self:set_active_view(self.views[1])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.a:close_all_docviews(keep_active)
|
||||||
|
self.b:close_all_docviews(keep_active)
|
||||||
|
if self.a:is_empty() and not self.a.is_primary_node then
|
||||||
|
self:consume(self.b)
|
||||||
|
elseif self.b:is_empty() and not self.b.is_primary_node then
|
||||||
|
self:consume(self.a)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns true for nodes that accept either "proportional" resizes (based on the
|
||||||
|
-- node.divider) or "locked" resizable nodes (along the resize axis).
|
||||||
|
function Node:is_resizable(axis)
|
||||||
|
if self.type == 'leaf' then
|
||||||
|
return not self.locked or not self.locked[axis] or self.resizable
|
||||||
|
else
|
||||||
|
local a_resizable = self.a:is_resizable(axis)
|
||||||
|
local b_resizable = self.b:is_resizable(axis)
|
||||||
|
return a_resizable and b_resizable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Return true iff it is a locked pane along the rezise axis and is
|
||||||
|
-- declared "resizable".
|
||||||
|
function Node:is_locked_resizable(axis)
|
||||||
|
return self.locked and self.locked[axis] and self.resizable
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:resize(axis, value)
|
||||||
|
-- the application works fine with non-integer values but to have pixel-perfect
|
||||||
|
-- placements of view elements, like the scrollbar, we round the value to be
|
||||||
|
-- an integer.
|
||||||
|
value = math.floor(value)
|
||||||
|
if self.type == 'leaf' then
|
||||||
|
-- If it is not locked we don't accept the
|
||||||
|
-- resize operation here because for proportional panes the resize is
|
||||||
|
-- done using the "divider" value of the parent node.
|
||||||
|
if self:is_locked_resizable(axis) then
|
||||||
|
return self.active_view:set_target_size(axis, value)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if self.type == (axis == "x" and "hsplit" or "vsplit") then
|
||||||
|
-- we are resizing a node that is splitted along the resize axis
|
||||||
|
if self.a:is_locked_resizable(axis) and self.b:is_locked_resizable(axis) then
|
||||||
|
local rem_value = value - self.a.size[axis]
|
||||||
|
if rem_value >= 0 then
|
||||||
|
return self.b.active_view:set_target_size(axis, rem_value)
|
||||||
|
else
|
||||||
|
self.b.active_view:set_target_size(axis, 0)
|
||||||
|
return self.a.active_view:set_target_size(axis, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- we are resizing a node that is splitted along the axis perpendicular
|
||||||
|
-- to the resize axis
|
||||||
|
local a_resizable = self.a:is_resizable(axis)
|
||||||
|
local b_resizable = self.b:is_resizable(axis)
|
||||||
|
if a_resizable and b_resizable then
|
||||||
|
self.a:resize(axis, value)
|
||||||
|
self.b:resize(axis, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_split_type(mouse_x, mouse_y)
|
||||||
|
local x, y = self.position.x, self.position.y
|
||||||
|
local w, h = self.size.x, self.size.y
|
||||||
|
local _, _, _, tab_h = self:get_scroll_button_rect(1)
|
||||||
|
y = y + tab_h
|
||||||
|
h = h - tab_h
|
||||||
|
|
||||||
|
local local_mouse_x = mouse_x - x
|
||||||
|
local local_mouse_y = mouse_y - y
|
||||||
|
|
||||||
|
if local_mouse_y < 0 then
|
||||||
|
return "tab"
|
||||||
|
else
|
||||||
|
local left_pct = local_mouse_x * 100 / w
|
||||||
|
local top_pct = local_mouse_y * 100 / h
|
||||||
|
if left_pct <= 30 then
|
||||||
|
return "left"
|
||||||
|
elseif left_pct >= 70 then
|
||||||
|
return "right"
|
||||||
|
elseif top_pct <= 30 then
|
||||||
|
return "up"
|
||||||
|
elseif top_pct >= 70 then
|
||||||
|
return "down"
|
||||||
|
end
|
||||||
|
return "middle"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_drag_overlay_tab_position(x, y, dragged_node, dragged_index)
|
||||||
|
local tab_index = self:get_tab_overlapping_point(x, y)
|
||||||
|
if not tab_index then
|
||||||
|
local first_tab_x = self:get_tab_rect(1)
|
||||||
|
if x < first_tab_x then
|
||||||
|
-- mouse before first visible tab
|
||||||
|
tab_index = self.tab_offset or 1
|
||||||
|
else
|
||||||
|
-- mouse after last visible tab
|
||||||
|
tab_index = self:get_visible_tabs_number() + (self.tab_offset - 1 or 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local tab_x, tab_y, tab_w, tab_h = self:get_tab_rect(tab_index)
|
||||||
|
if x > tab_x + tab_w / 2 and tab_index <= #self.views then
|
||||||
|
-- use next tab
|
||||||
|
tab_x = tab_x + tab_w
|
||||||
|
tab_index = tab_index + 1
|
||||||
|
end
|
||||||
|
if self == dragged_node and dragged_index and tab_index > dragged_index then
|
||||||
|
-- the tab we are moving is counted in tab_index
|
||||||
|
tab_index = tab_index - 1
|
||||||
|
tab_x = tab_x - tab_w
|
||||||
|
end
|
||||||
|
return tab_index, tab_x, tab_y, tab_w, tab_h
|
||||||
|
end
|
||||||
|
|
||||||
|
return Node
|
|
@ -1,780 +1,11 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local config = require "core.config"
|
|
||||||
local style = require "core.style"
|
local style = require "core.style"
|
||||||
local keymap = require "core.keymap"
|
local Node = require "core.node"
|
||||||
local Object = require "core.object"
|
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
local NagView = require "core.nagview"
|
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
|
|
||||||
|
|
||||||
local EmptyView = View:extend()
|
|
||||||
|
|
||||||
local function draw_text(x, y, color)
|
|
||||||
local th = style.big_font:get_height()
|
|
||||||
local dh = 2 * th + style.padding.y * 2
|
|
||||||
local x1, y1 = x, y + (dh - th) / 2
|
|
||||||
x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color)
|
|
||||||
renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color)
|
|
||||||
x = x + style.padding.x
|
|
||||||
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
|
|
||||||
local lines = {
|
|
||||||
{ fmt = "%s to run a command", cmd = "core:find-command" },
|
|
||||||
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
|
|
||||||
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
|
|
||||||
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
|
|
||||||
}
|
|
||||||
th = style.font:get_height()
|
|
||||||
y = y + (dh - (th + style.padding.y) * #lines) / 2
|
|
||||||
local w = 0
|
|
||||||
for _, line in ipairs(lines) do
|
|
||||||
local text = string.format(line.fmt, keymap.get_binding(line.cmd))
|
|
||||||
w = math.max(w, renderer.draw_text(style.font, text, x + style.padding.x, y, color))
|
|
||||||
y = y + th + style.padding.y
|
|
||||||
end
|
|
||||||
return w, dh
|
|
||||||
end
|
|
||||||
|
|
||||||
function EmptyView:draw()
|
|
||||||
self:draw_background(style.background)
|
|
||||||
local w, h = draw_text(0, 0, { 0, 0, 0, 0 })
|
|
||||||
local x = self.position.x + math.max(style.padding.x, (self.size.x - w) / 2)
|
|
||||||
local y = self.position.y + (self.size.y - h) / 2
|
|
||||||
draw_text(x, y, style.dim)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local Node = Object:extend()
|
|
||||||
|
|
||||||
function Node:new(type)
|
|
||||||
self.type = type or "leaf"
|
|
||||||
self.position = { x = 0, y = 0 }
|
|
||||||
self.size = { x = 0, y = 0 }
|
|
||||||
self.views = {}
|
|
||||||
self.divider = 0.5
|
|
||||||
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
|
|
||||||
self.tab_width = style.tab_width
|
|
||||||
self.move_towards = View.move_towards
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:propagate(fn, ...)
|
|
||||||
self.a[fn](self.a, ...)
|
|
||||||
self.b[fn](self.b, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:on_mouse_moved(x, y, ...)
|
|
||||||
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, ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:on_mouse_released(...)
|
|
||||||
if self.type == "leaf" then
|
|
||||||
self.active_view:on_mouse_released(...)
|
|
||||||
else
|
|
||||||
self:propagate("on_mouse_released", ...)
|
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local type_map = { up="vsplit", down="vsplit", left="hsplit", right="hsplit" }
|
|
||||||
|
|
||||||
-- The "locked" argument below should be in the form {x = <boolean>, y = <boolean>}
|
|
||||||
-- and it indicates if the node want to have a fixed size along the axis where the
|
|
||||||
-- boolean is true. If not it will be expanded to take all the available space.
|
|
||||||
-- The "resizable" flag indicates if, along the "locked" axis the node can be resized
|
|
||||||
-- by the user. If the node is marked as resizable their view should provide a
|
|
||||||
-- set_target_size method.
|
|
||||||
function Node:split(dir, view, locked, resizable)
|
|
||||||
assert(self.type == "leaf", "Tried to split non-leaf node")
|
|
||||||
local node_type = assert(type_map[dir], "Invalid direction")
|
|
||||||
local last_active = core.active_view
|
|
||||||
local child = Node()
|
|
||||||
child:consume(self)
|
|
||||||
self:consume(Node(node_type))
|
|
||||||
self.a = child
|
|
||||||
self.b = Node()
|
|
||||||
if view then self.b:add_view(view) end
|
|
||||||
if locked then
|
|
||||||
assert(type(locked) == 'table')
|
|
||||||
self.b.locked = locked
|
|
||||||
self.b.resizable = resizable or false
|
|
||||||
core.set_active_view(last_active)
|
|
||||||
end
|
|
||||||
if dir == "up" or dir == "left" then
|
|
||||||
self.a, self.b = self.b, self.a
|
|
||||||
return self.a
|
|
||||||
end
|
|
||||||
return self.b
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:remove_view(root, view)
|
|
||||||
if #self.views > 1 then
|
|
||||||
local idx = self:get_view_idx(view)
|
|
||||||
if idx < self.tab_offset then
|
|
||||||
self.tab_offset = self.tab_offset - 1
|
|
||||||
end
|
|
||||||
table.remove(self.views, idx)
|
|
||||||
if self.active_view == view then
|
|
||||||
self:set_active_view(self.views[idx] or self.views[#self.views])
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local parent = self:get_parent_node(root)
|
|
||||||
local is_a = (parent.a == self)
|
|
||||||
local other = parent[is_a and "b" or "a"]
|
|
||||||
local locked_size_x, locked_size_y = other:get_locked_size()
|
|
||||||
local locked_size
|
|
||||||
if parent.type == "hsplit" then
|
|
||||||
locked_size = locked_size_x
|
|
||||||
else
|
|
||||||
locked_size = locked_size_y
|
|
||||||
end
|
|
||||||
local next_primary
|
|
||||||
if self.is_primary_node then
|
|
||||||
next_primary = core.root_view:select_next_primary_node()
|
|
||||||
end
|
|
||||||
if locked_size or (self.is_primary_node and not next_primary) then
|
|
||||||
self.views = {}
|
|
||||||
self:add_view(EmptyView())
|
|
||||||
else
|
|
||||||
if other == next_primary then
|
|
||||||
next_primary = parent
|
|
||||||
end
|
|
||||||
parent:consume(other)
|
|
||||||
local p = parent
|
|
||||||
while p.type ~= "leaf" do
|
|
||||||
p = p[is_a and "a" or "b"]
|
|
||||||
end
|
|
||||||
p:set_active_view(p.active_view)
|
|
||||||
if self.is_primary_node then
|
|
||||||
next_primary.is_primary_node = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
core.last_active_view = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:close_view(root, view)
|
|
||||||
local do_close = function()
|
|
||||||
self:remove_view(root, view)
|
|
||||||
end
|
|
||||||
view:try_close(do_close)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:close_active_view(root)
|
|
||||||
self:close_view(root, self.active_view)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:add_view(view, idx)
|
|
||||||
assert(self.type == "leaf", "Tried to add view to non-leaf node")
|
|
||||||
assert(not self.locked, "Tried to add view to locked node")
|
|
||||||
if self.views[1] and self.views[1]:is(EmptyView) then
|
|
||||||
table.remove(self.views)
|
|
||||||
end
|
|
||||||
table.insert(self.views, idx or (#self.views + 1), view)
|
|
||||||
self:set_active_view(view)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:set_active_view(view)
|
|
||||||
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
|
|
||||||
self.active_view = view
|
|
||||||
core.set_active_view(view)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_view_idx(view)
|
|
||||||
for i, v in ipairs(self.views) do
|
|
||||||
if v == view then return i end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_node_for_view(view)
|
|
||||||
for _, v in ipairs(self.views) do
|
|
||||||
if v == view then return self end
|
|
||||||
end
|
|
||||||
if self.type ~= "leaf" then
|
|
||||||
return self.a:get_node_for_view(view) or self.b:get_node_for_view(view)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_parent_node(root)
|
|
||||||
if root.a == self or root.b == self then
|
|
||||||
return root
|
|
||||||
elseif root.type ~= "leaf" then
|
|
||||||
return self:get_parent_node(root.a) or self:get_parent_node(root.b)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_children(t)
|
|
||||||
t = t or {}
|
|
||||||
for _, view in ipairs(self.views) do
|
|
||||||
table.insert(t, view)
|
|
||||||
end
|
|
||||||
if self.a then self.a:get_children(t) end
|
|
||||||
if self.b then self.b:get_children(t) end
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- return the width including the padding space and separately
|
|
||||||
-- the padding space itself
|
|
||||||
local function get_scroll_button_width()
|
|
||||||
local w = style.icon_font:get_width(">")
|
|
||||||
local pad = w
|
|
||||||
return w + 2 * pad, pad
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_divider_overlapping_point(px, py)
|
|
||||||
if self.type ~= "leaf" then
|
|
||||||
local axis = self.type == "hsplit" and "x" or "y"
|
|
||||||
if self.a:is_resizable(axis) and self.b:is_resizable(axis) then
|
|
||||||
local p = 6
|
|
||||||
local x, y, w, h = self:get_divider_rect()
|
|
||||||
x, y = x - p, y - p
|
|
||||||
w, h = w + p * 2, h + p * 2
|
|
||||||
if px > x and py > y and px < x + w and py < y + h then
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return self.a:get_divider_overlapping_point(px, py)
|
|
||||||
or self.b:get_divider_overlapping_point(px, py)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_visible_tabs_number()
|
|
||||||
return math.min(#self.views - self.tab_offset + 1, config.max_tabs)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_tab_overlapping_point(px, py)
|
|
||||||
if not self:should_show_tabs() then return nil end
|
|
||||||
local tabs_number = self:get_visible_tabs_number()
|
|
||||||
local x1, y1, w, h = self:get_tab_rect(self.tab_offset)
|
|
||||||
local x2, y2 = self:get_tab_rect(self.tab_offset + tabs_number)
|
|
||||||
if px >= x1 and py >= y1 and px < x2 and py < y1 + h then
|
|
||||||
return math.floor((px - x1) / w) + self.tab_offset
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:should_show_tabs()
|
|
||||||
if self.locked then return false end
|
|
||||||
local dn = core.root_view.dragged_node
|
|
||||||
if #self.views > 1
|
|
||||||
or (dn and dn.dragging) then -- show tabs while dragging
|
|
||||||
return true
|
|
||||||
elseif config.always_show_tabs then
|
|
||||||
return not self.views[1]:is(EmptyView)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function close_button_location(x, w)
|
|
||||||
local cw = style.icon_font:get_width("C")
|
|
||||||
local pad = style.padding.y
|
|
||||||
return x + w - pad - cw, cw, pad
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_scroll_button_index(px, py)
|
|
||||||
if #self.views == 1 then return end
|
|
||||||
for i = 1, 2 do
|
|
||||||
local x, y, w, h = self:get_scroll_button_rect(i)
|
|
||||||
if px >= x and px < x + w and py >= y and py < y + h then
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:tab_hovered_update(px, py)
|
|
||||||
local tab_index = self:get_tab_overlapping_point(px, py)
|
|
||||||
self.hovered_tab = tab_index
|
|
||||||
self.hovered_close = 0
|
|
||||||
self.hovered_scroll_button = 0
|
|
||||||
if tab_index then
|
|
||||||
local x, y, w, h = self:get_tab_rect(tab_index)
|
|
||||||
local cx, cw = close_button_location(x, w)
|
|
||||||
if px >= cx and px < cx + cw and py >= y and py < y + h and config.tab_close_button then
|
|
||||||
self.hovered_close = tab_index
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.hovered_scroll_button = self:get_scroll_button_index(px, py) or 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_child_overlapping_point(x, y)
|
|
||||||
local child
|
|
||||||
if self.type == "leaf" then
|
|
||||||
return self
|
|
||||||
elseif self.type == "hsplit" then
|
|
||||||
child = (x < self.b.position.x) and self.a or self.b
|
|
||||||
elseif self.type == "vsplit" then
|
|
||||||
child = (y < self.b.position.y) and self.a or self.b
|
|
||||||
end
|
|
||||||
return child:get_child_overlapping_point(x, y)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_scroll_button_rect(index)
|
|
||||||
local w, pad = get_scroll_button_width()
|
|
||||||
local h = style.font:get_height() + style.padding.y * 2
|
|
||||||
local x = self.position.x + (index == 1 and 0 or self.size.x - w)
|
|
||||||
return x, self.position.y, w, h, pad
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_tab_rect(idx)
|
|
||||||
local sbw = get_scroll_button_width()
|
|
||||||
local maxw = self.size.x - 2 * sbw
|
|
||||||
local x0 = self.position.x + sbw
|
|
||||||
local x1 = x0 + common.clamp(self.tab_width * (idx - 1) - self.tab_shift, 0, maxw)
|
|
||||||
local x2 = x0 + common.clamp(self.tab_width * idx - self.tab_shift, 0, maxw)
|
|
||||||
local h = style.font:get_height() + style.padding.y * 2
|
|
||||||
return x1, self.position.y, x2 - x1, h
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_divider_rect()
|
|
||||||
local x, y = self.position.x, self.position.y
|
|
||||||
if self.type == "hsplit" then
|
|
||||||
return x + self.a.size.x, y, style.divider_size, self.size.y
|
|
||||||
elseif self.type == "vsplit" then
|
|
||||||
return x, y + self.a.size.y, self.size.x, style.divider_size
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- Return two values for x and y axis and each of them is either falsy or a number.
|
|
||||||
-- A falsy value indicate no fixed size along the corresponding direction.
|
|
||||||
function Node:get_locked_size()
|
|
||||||
if self.type == "leaf" then
|
|
||||||
if self.locked then
|
|
||||||
local size = self.active_view.size
|
|
||||||
-- The values below should be either a falsy value or a number
|
|
||||||
local sx = (self.locked and self.locked.x) and size.x
|
|
||||||
local sy = (self.locked and self.locked.y) and size.y
|
|
||||||
return sx, sy
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local x1, y1 = self.a:get_locked_size()
|
|
||||||
local x2, y2 = self.b:get_locked_size()
|
|
||||||
-- The values below should be either a falsy value or a number
|
|
||||||
local sx, sy
|
|
||||||
if self.type == 'hsplit' then
|
|
||||||
if x1 and x2 then
|
|
||||||
local dsx = (x1 < 1 or x2 < 1) and 0 or style.divider_size
|
|
||||||
sx = x1 + x2 + dsx
|
|
||||||
end
|
|
||||||
sy = y1 or y2
|
|
||||||
else
|
|
||||||
if y1 and y2 then
|
|
||||||
local dsy = (y1 < 1 or y2 < 1) and 0 or style.divider_size
|
|
||||||
sy = y1 + y2 + dsy
|
|
||||||
end
|
|
||||||
sx = x1 or x2
|
|
||||||
end
|
|
||||||
return sx, sy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function copy_position_and_size(dst, src)
|
|
||||||
dst.position.x, dst.position.y = src.position.x, src.position.y
|
|
||||||
dst.size.x, dst.size.y = src.size.x, src.size.y
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- calculating the sizes is the same for hsplits and vsplits, except the x/y
|
|
||||||
-- axis are swapped; this function lets us use the same code for both
|
|
||||||
local function calc_split_sizes(self, x, y, x1, x2, y1, y2)
|
|
||||||
local ds = ((x1 and x1 < 1) or (x2 and x2 < 1)) and 0 or style.divider_size
|
|
||||||
local n = x1 and x1 + ds or (x2 and self.size[x] - x2 or math.floor(self.size[x] * self.divider))
|
|
||||||
self.a.position[x] = self.position[x]
|
|
||||||
self.a.position[y] = self.position[y]
|
|
||||||
self.a.size[x] = n - ds
|
|
||||||
self.a.size[y] = self.size[y]
|
|
||||||
self.b.position[x] = self.position[x] + n
|
|
||||||
self.b.position[y] = self.position[y]
|
|
||||||
self.b.size[x] = self.size[x] - n
|
|
||||||
self.b.size[y] = self.size[y]
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:update_layout()
|
|
||||||
if self.type == "leaf" then
|
|
||||||
local av = self.active_view
|
|
||||||
if self:should_show_tabs() then
|
|
||||||
local _, _, _, th = self:get_tab_rect(1)
|
|
||||||
av.position.x, av.position.y = self.position.x, self.position.y + th
|
|
||||||
av.size.x, av.size.y = self.size.x, self.size.y - th
|
|
||||||
else
|
|
||||||
copy_position_and_size(av, self)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local x1, y1 = self.a:get_locked_size()
|
|
||||||
local x2, y2 = self.b:get_locked_size()
|
|
||||||
if self.type == "hsplit" then
|
|
||||||
calc_split_sizes(self, "x", "y", x1, x2)
|
|
||||||
elseif self.type == "vsplit" then
|
|
||||||
calc_split_sizes(self, "y", "x", y1, y2)
|
|
||||||
end
|
|
||||||
self.a:update_layout()
|
|
||||||
self.b:update_layout()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:scroll_tabs_to_visible()
|
|
||||||
local index = self:get_view_idx(self.active_view)
|
|
||||||
if index then
|
|
||||||
local tabs_number = self:get_visible_tabs_number()
|
|
||||||
if self.tab_offset > index then
|
|
||||||
self.tab_offset = index
|
|
||||||
elseif self.tab_offset + tabs_number - 1 < index then
|
|
||||||
self.tab_offset = index - tabs_number + 1
|
|
||||||
elseif tabs_number < config.max_tabs and self.tab_offset > 1 then
|
|
||||||
self.tab_offset = #self.views - config.max_tabs + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:scroll_tabs(dir)
|
|
||||||
local view_index = self:get_view_idx(self.active_view)
|
|
||||||
if dir == 1 then
|
|
||||||
if self.tab_offset > 1 then
|
|
||||||
self.tab_offset = self.tab_offset - 1
|
|
||||||
local last_index = self.tab_offset + self:get_visible_tabs_number() - 1
|
|
||||||
if view_index > last_index then
|
|
||||||
self:set_active_view(self.views[last_index])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif dir == 2 then
|
|
||||||
local tabs_number = self:get_visible_tabs_number()
|
|
||||||
if self.tab_offset + tabs_number - 1 < #self.views then
|
|
||||||
self.tab_offset = self.tab_offset + 1
|
|
||||||
local view_index = self:get_view_idx(self.active_view)
|
|
||||||
if view_index < self.tab_offset then
|
|
||||||
self:set_active_view(self.views[self.tab_offset])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:target_tab_width()
|
|
||||||
local n = self:get_visible_tabs_number()
|
|
||||||
local w = self.size.x - get_scroll_button_width() * 2
|
|
||||||
return common.clamp(style.tab_width, w / config.max_tabs, w / n)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:update()
|
|
||||||
if self.type == "leaf" then
|
|
||||||
self:scroll_tabs_to_visible()
|
|
||||||
for _, view in ipairs(self.views) do
|
|
||||||
view:update()
|
|
||||||
end
|
|
||||||
self:tab_hovered_update(self.hovered.x, self.hovered.y)
|
|
||||||
local tab_width = self:target_tab_width()
|
|
||||||
self:move_towards("tab_shift", tab_width * (self.tab_offset - 1))
|
|
||||||
self:move_towards("tab_width", tab_width)
|
|
||||||
else
|
|
||||||
self.a:update()
|
|
||||||
self.b:update()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
|
||||||
local ds = style.divider_size
|
|
||||||
local dots_width = style.font:get_width("…")
|
|
||||||
local color = style.dim
|
|
||||||
local padding_y = style.padding.y
|
|
||||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
|
||||||
if standalone then
|
|
||||||
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
|
|
||||||
end
|
|
||||||
if is_active then
|
|
||||||
color = style.text
|
|
||||||
renderer.draw_rect(x, y, w, h, style.background)
|
|
||||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
|
||||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
|
||||||
end
|
|
||||||
local cx, cw, cspace = close_button_location(x, w)
|
|
||||||
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
|
|
||||||
if show_close_button then
|
|
||||||
local close_style = is_close_hovered and style.text or style.dim
|
|
||||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
|
||||||
end
|
|
||||||
if is_hovered then
|
|
||||||
color = style.text
|
|
||||||
end
|
|
||||||
local padx = style.padding.x
|
|
||||||
-- Normally we should substract "cspace" from text_avail_width and from the
|
|
||||||
-- clipping width. It is the padding space we give to the left and right of the
|
|
||||||
-- close button. However, since we are using dots to terminate filenames, we
|
|
||||||
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
|
||||||
-- close button.
|
|
||||||
local text_avail_width = cx - x - padx
|
|
||||||
core.push_clip_rect(x, y, cx - x, h)
|
|
||||||
x, w = x + padx, w - padx * 2
|
|
||||||
local align = "center"
|
|
||||||
if style.font:get_width(text) > text_avail_width then
|
|
||||||
align = "left"
|
|
||||||
for i = 1, #text do
|
|
||||||
local reduced_text = text:sub(1, #text - i)
|
|
||||||
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
|
||||||
text = reduced_text .. "…"
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
common.draw_text(style.font, color, text, align, x, y, w, h)
|
|
||||||
core.pop_clip_rect()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:draw_tabs()
|
|
||||||
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
|
||||||
local ds = style.divider_size
|
|
||||||
local dots_width = style.font:get_width("…")
|
|
||||||
core.push_clip_rect(x, y, self.size.x, h)
|
|
||||||
renderer.draw_rect(x, y, self.size.x, h, style.background2)
|
|
||||||
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
|
|
||||||
|
|
||||||
if self.tab_offset > 1 then
|
|
||||||
local button_style = self.hovered_scroll_button == 1 and style.text or style.dim
|
|
||||||
common.draw_text(style.icon_font, button_style, "<", nil, x + scroll_padding, y, 0, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
local tabs_number = self:get_visible_tabs_number()
|
|
||||||
if #self.views > self.tab_offset + tabs_number - 1 then
|
|
||||||
local xrb, yrb, wrb = self:get_scroll_button_rect(2)
|
|
||||||
local button_style = self.hovered_scroll_button == 2 and style.text or style.dim
|
|
||||||
common.draw_text(style.icon_font, button_style, ">", nil, xrb + scroll_padding, yrb, 0, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
|
||||||
local view = self.views[i]
|
|
||||||
local x, y, w, h = self:get_tab_rect(i)
|
|
||||||
self:draw_tab(view:get_name(), view == self.active_view,
|
|
||||||
i == self.hovered_tab, i == self.hovered_close,
|
|
||||||
x, y, w, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
core.pop_clip_rect()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:draw()
|
|
||||||
if self.type == "leaf" then
|
|
||||||
if self:should_show_tabs() then
|
|
||||||
self:draw_tabs()
|
|
||||||
end
|
|
||||||
local pos, size = self.active_view.position, self.active_view.size
|
|
||||||
core.push_clip_rect(pos.x, pos.y, size.x, size.y)
|
|
||||||
self.active_view:draw()
|
|
||||||
core.pop_clip_rect()
|
|
||||||
else
|
|
||||||
local x, y, w, h = self:get_divider_rect()
|
|
||||||
renderer.draw_rect(x, y, w, h, style.divider)
|
|
||||||
self:propagate("draw")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:is_empty()
|
|
||||||
if self.type == "leaf" then
|
|
||||||
return #self.views == 0 or (#self.views == 1 and self.views[1]:is(EmptyView))
|
|
||||||
else
|
|
||||||
return self.a:is_empty() and self.b:is_empty()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:close_all_docviews(keep_active)
|
|
||||||
local node_active_view = self.active_view
|
|
||||||
local lost_active_view = false
|
|
||||||
if self.type == "leaf" then
|
|
||||||
local i = 1
|
|
||||||
while i <= #self.views do
|
|
||||||
local view = self.views[i]
|
|
||||||
if view.context == "session" and (not keep_active or view ~= self.active_view) then
|
|
||||||
table.remove(self.views, i)
|
|
||||||
if view == node_active_view then
|
|
||||||
lost_active_view = true
|
|
||||||
end
|
|
||||||
else
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.tab_offset = 1
|
|
||||||
if #self.views == 0 and self.is_primary_node then
|
|
||||||
-- if we are not the primary view and we had the active view it doesn't
|
|
||||||
-- matter to reattribute the active view because, within the close_all_docviews
|
|
||||||
-- top call, the primary node will take the active view anyway.
|
|
||||||
-- Set the empty view and takes the active view.
|
|
||||||
self:add_view(EmptyView())
|
|
||||||
elseif #self.views > 0 and lost_active_view then
|
|
||||||
-- In practice we never get there but if a view remain we need
|
|
||||||
-- to reset the Node's active view.
|
|
||||||
self:set_active_view(self.views[1])
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.a:close_all_docviews(keep_active)
|
|
||||||
self.b:close_all_docviews(keep_active)
|
|
||||||
if self.a:is_empty() and not self.a.is_primary_node then
|
|
||||||
self:consume(self.b)
|
|
||||||
elseif self.b:is_empty() and not self.b.is_primary_node then
|
|
||||||
self:consume(self.a)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Returns true for nodes that accept either "proportional" resizes (based on the
|
|
||||||
-- node.divider) or "locked" resizable nodes (along the resize axis).
|
|
||||||
function Node:is_resizable(axis)
|
|
||||||
if self.type == 'leaf' then
|
|
||||||
return not self.locked or not self.locked[axis] or self.resizable
|
|
||||||
else
|
|
||||||
local a_resizable = self.a:is_resizable(axis)
|
|
||||||
local b_resizable = self.b:is_resizable(axis)
|
|
||||||
return a_resizable and b_resizable
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- Return true iff it is a locked pane along the rezise axis and is
|
|
||||||
-- declared "resizable".
|
|
||||||
function Node:is_locked_resizable(axis)
|
|
||||||
return self.locked and self.locked[axis] and self.resizable
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:resize(axis, value)
|
|
||||||
-- the application works fine with non-integer values but to have pixel-perfect
|
|
||||||
-- placements of view elements, like the scrollbar, we round the value to be
|
|
||||||
-- an integer.
|
|
||||||
value = math.floor(value)
|
|
||||||
if self.type == 'leaf' then
|
|
||||||
-- If it is not locked we don't accept the
|
|
||||||
-- resize operation here because for proportional panes the resize is
|
|
||||||
-- done using the "divider" value of the parent node.
|
|
||||||
if self:is_locked_resizable(axis) then
|
|
||||||
return self.active_view:set_target_size(axis, value)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if self.type == (axis == "x" and "hsplit" or "vsplit") then
|
|
||||||
-- we are resizing a node that is splitted along the resize axis
|
|
||||||
if self.a:is_locked_resizable(axis) and self.b:is_locked_resizable(axis) then
|
|
||||||
local rem_value = value - self.a.size[axis]
|
|
||||||
if rem_value >= 0 then
|
|
||||||
return self.b.active_view:set_target_size(axis, rem_value)
|
|
||||||
else
|
|
||||||
self.b.active_view:set_target_size(axis, 0)
|
|
||||||
return self.a.active_view:set_target_size(axis, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- we are resizing a node that is splitted along the axis perpendicular
|
|
||||||
-- to the resize axis
|
|
||||||
local a_resizable = self.a:is_resizable(axis)
|
|
||||||
local b_resizable = self.b:is_resizable(axis)
|
|
||||||
if a_resizable and b_resizable then
|
|
||||||
self.a:resize(axis, value)
|
|
||||||
self.b:resize(axis, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_split_type(mouse_x, mouse_y)
|
|
||||||
local x, y = self.position.x, self.position.y
|
|
||||||
local w, h = self.size.x, self.size.y
|
|
||||||
local _, _, _, tab_h = self:get_scroll_button_rect(1)
|
|
||||||
y = y + tab_h
|
|
||||||
h = h - tab_h
|
|
||||||
|
|
||||||
local local_mouse_x = mouse_x - x
|
|
||||||
local local_mouse_y = mouse_y - y
|
|
||||||
|
|
||||||
if local_mouse_y < 0 then
|
|
||||||
return "tab"
|
|
||||||
else
|
|
||||||
local left_pct = local_mouse_x * 100 / w
|
|
||||||
local top_pct = local_mouse_y * 100 / h
|
|
||||||
if left_pct <= 30 then
|
|
||||||
return "left"
|
|
||||||
elseif left_pct >= 70 then
|
|
||||||
return "right"
|
|
||||||
elseif top_pct <= 30 then
|
|
||||||
return "up"
|
|
||||||
elseif top_pct >= 70 then
|
|
||||||
return "down"
|
|
||||||
end
|
|
||||||
return "middle"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_drag_overlay_tab_position(x, y, dragged_node, dragged_index)
|
|
||||||
local tab_index = self:get_tab_overlapping_point(x, y)
|
|
||||||
if not tab_index then
|
|
||||||
local first_tab_x = self:get_tab_rect(1)
|
|
||||||
if x < first_tab_x then
|
|
||||||
-- mouse before first visible tab
|
|
||||||
tab_index = self.tab_offset or 1
|
|
||||||
else
|
|
||||||
-- mouse after last visible tab
|
|
||||||
tab_index = self:get_visible_tabs_number() + (self.tab_offset - 1 or 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local tab_x, tab_y, tab_w, tab_h = self:get_tab_rect(tab_index)
|
|
||||||
if x > tab_x + tab_w / 2 and tab_index <= #self.views then
|
|
||||||
-- use next tab
|
|
||||||
tab_x = tab_x + tab_w
|
|
||||||
tab_index = tab_index + 1
|
|
||||||
end
|
|
||||||
if self == dragged_node and dragged_index and tab_index > dragged_index then
|
|
||||||
-- the tab we are moving is counted in tab_index
|
|
||||||
tab_index = tab_index - 1
|
|
||||||
tab_x = tab_x - tab_w
|
|
||||||
end
|
|
||||||
return tab_index, tab_x, tab_y, tab_w, tab_h
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local RootView = View:extend()
|
local RootView = View:extend()
|
||||||
|
|
||||||
function RootView:new()
|
function RootView:new()
|
||||||
|
@ -1068,7 +299,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
function RootView:update()
|
function RootView:update()
|
||||||
copy_position_and_size(self.root_node, self)
|
Node.copy_position_and_size(self.root_node, self)
|
||||||
self.root_node:update()
|
self.root_node:update()
|
||||||
self.root_node:update_layout()
|
self.root_node:update_layout()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue