Add buttons to scroll tabs when there are too many
Use config.max_tabs to configure the maximum number of tabs that can be shown. Beyond the maximum value scroll buttons will appear to scroll the tabs.
This commit is contained in:
parent
90e41daa99
commit
b37c190db2
|
@ -11,6 +11,7 @@ config.symbol_pattern = "[%a_][%w_]*"
|
||||||
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||||
config.undo_merge_timeout = 0.3
|
config.undo_merge_timeout = 0.3
|
||||||
config.max_undos = 10000
|
config.max_undos = 10000
|
||||||
|
config.max_tabs = 10
|
||||||
config.highlight_current_line = true
|
config.highlight_current_line = true
|
||||||
config.line_height = 1.2
|
config.line_height = 1.2
|
||||||
config.indent_size = 2
|
config.indent_size = 2
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
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 keymap = require "core.keymap"
|
||||||
local Object = require "core.object"
|
local Object = require "core.object"
|
||||||
|
@ -59,7 +60,9 @@ function Node:new(type)
|
||||||
end
|
end
|
||||||
self.hovered = {x = -1, y = -1 }
|
self.hovered = {x = -1, y = -1 }
|
||||||
self.hovered_close = 0
|
self.hovered_close = 0
|
||||||
self.tab_margin = style.padding.x
|
self.tab_shift = 0
|
||||||
|
self.tab_offset = 1
|
||||||
|
self.tab_width = style.tab_width
|
||||||
self.move_towards = View.move_towards
|
self.move_towards = View.move_towards
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -236,11 +239,20 @@ function Node: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)
|
function Node:get_tab_overlapping_point(px, py)
|
||||||
if #self.views == 1 then return nil end
|
if #self.views == 1 then return nil end
|
||||||
local x, y, w, h = self:get_tab_rect(1)
|
local tabs_number = self:get_visible_tabs_number()
|
||||||
if px >= x and py >= y and px < x + w * #self.views and py < y + h then
|
-- FIXME: looping over tabs is may be not needed. Find a simpler way.
|
||||||
return math.floor((px - x) / w) + 1
|
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
||||||
|
local x, y, w, h = self:get_tab_rect(i)
|
||||||
|
if px >= x and py >= y and px < x + w and py < y + h then
|
||||||
|
return i
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -252,18 +264,33 @@ local function close_button_location(x, w)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_scroll_button_index(px, py)
|
||||||
|
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)
|
function Node:tab_hovered_update(px, py)
|
||||||
local tab_index = self:get_tab_overlapping_point(px, py)
|
local tab_index = self:get_tab_overlapping_point(px, py)
|
||||||
self.hovered_tab = tab_index
|
self.hovered_tab = tab_index
|
||||||
|
self.hovered_close = 0
|
||||||
|
self.hovered_tab_scroll = 0
|
||||||
if tab_index then
|
if tab_index then
|
||||||
local x, y, w, h = self:get_tab_rect(tab_index)
|
local x, y, w, h = self:get_tab_rect(tab_index)
|
||||||
local cx, cw = close_button_location(x, w)
|
local cx, cw = close_button_location(x, w)
|
||||||
if px >= cx and px < cx + cw and py >= y and py < y + h then
|
if px >= cx and px < cx + cw and py >= y and py < y + h then
|
||||||
self.hovered_close = tab_index
|
self.hovered_close = tab_index
|
||||||
return
|
end
|
||||||
|
else
|
||||||
|
local tab_scroll_index = self:get_scroll_button_index(px, py)
|
||||||
|
if tab_scroll_index then
|
||||||
|
self.hovered_tab_scroll = tab_scroll_index
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.hovered_close = 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -280,10 +307,24 @@ function Node:get_child_overlapping_point(x, y)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_scroll_button_rect(index)
|
||||||
|
local w = style.icon_font:get_width(">")
|
||||||
|
local pad = w
|
||||||
|
local h = style.font:get_height() + style.padding.y * 2
|
||||||
|
if index == 1 then
|
||||||
|
return self.position.x, self.position.y, w + 2 * pad, h, pad
|
||||||
|
else
|
||||||
|
return self.position.x + self.size.x - w - 2 * pad, self.position.y, w + 2 * pad, h, pad
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Node:get_tab_rect(idx)
|
function Node:get_tab_rect(idx)
|
||||||
local tw = math.min(style.tab_width, (self.size.x - self.tab_margin) / #self.views)
|
-- FIXME: we may consider to create a separate method just to get the
|
||||||
local x_left = self.position.x + tw * (idx - 1)
|
-- width of the scroll button. It is needed to compute tabs placement.
|
||||||
local x1, x2 = math.floor(x_left), math.floor(x_left + tw)
|
local _, _, sbw = self:get_scroll_button_rect(1)
|
||||||
|
local x_left = self.position.x + sbw + self.tab_width * (idx - 1) - self.tab_shift
|
||||||
|
local x1, x2 = math.floor(x_left), math.floor(x_left + self.tab_width)
|
||||||
local h = style.font:get_height() + style.padding.y * 2
|
local h = style.font:get_height() + style.padding.y * 2
|
||||||
return x1, self.position.y, x2 - x1, h
|
return x1, self.position.y, x2 - x1, h
|
||||||
end
|
end
|
||||||
|
@ -386,13 +427,61 @@ function Node: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 tabs_number = self:get_visible_tabs_number()
|
||||||
|
local _, _, sbw = self:get_scroll_button_rect(1)
|
||||||
|
return math.min(style.tab_width, (self.size.x - sbw * 2) / tabs_number)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Node:update()
|
function Node:update()
|
||||||
if self.type == "leaf" then
|
if self.type == "leaf" then
|
||||||
|
self:scroll_tabs_to_visible()
|
||||||
for _, view in ipairs(self.views) do
|
for _, view in ipairs(self.views) do
|
||||||
view:update()
|
view:update()
|
||||||
end
|
end
|
||||||
self:tab_hovered_update(self.hovered.x, self.hovered.y)
|
self:tab_hovered_update(self.hovered.x, self.hovered.y)
|
||||||
self:move_towards("tab_margin", style.padding.x)
|
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
|
else
|
||||||
self.a:update()
|
self.a:update()
|
||||||
self.b:update()
|
self.b:update()
|
||||||
|
@ -401,14 +490,27 @@ end
|
||||||
|
|
||||||
|
|
||||||
function Node:draw_tabs()
|
function Node:draw_tabs()
|
||||||
local x, y, _, h = self:get_tab_rect(1)
|
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
||||||
local ds = style.divider_size
|
local ds = style.divider_size
|
||||||
local dots_width = style.font:get_width("…")
|
local dots_width = style.font:get_width("…")
|
||||||
core.push_clip_rect(x, y, self.size.x, h)
|
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, self.size.x, h, style.background2)
|
||||||
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
|
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
|
||||||
|
|
||||||
for i, view in ipairs(self.views) do
|
if self.tab_offset > 1 then
|
||||||
|
local button_style = self.hovered_tab_scroll == 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_tab_scroll == 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)
|
local x, y, w, h = self:get_tab_rect(i)
|
||||||
local text = view:get_name()
|
local text = view:get_name()
|
||||||
local color = style.dim
|
local color = style.dim
|
||||||
|
@ -637,11 +739,13 @@ function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||||
|
if node.hovered_tab_scroll > 0 then
|
||||||
|
node:scroll_tabs(node.hovered_tab_scroll)
|
||||||
|
return
|
||||||
|
end
|
||||||
local idx = node:get_tab_overlapping_point(x, y)
|
local idx = node:get_tab_overlapping_point(x, y)
|
||||||
if idx then
|
if idx then
|
||||||
if button == "middle" or node.hovered_close == idx then
|
if button == "middle" or node.hovered_close == idx then
|
||||||
local _, _, tw = node:get_tab_rect(idx)
|
|
||||||
node.tab_margin = node.tab_margin + tw
|
|
||||||
node:close_view(self.root_node, node.views[idx])
|
node:close_view(self.root_node, node.views[idx])
|
||||||
else
|
else
|
||||||
self.dragged_node = idx
|
self.dragged_node = idx
|
||||||
|
@ -702,7 +806,9 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
||||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||||
local tab_index = node and node:get_tab_overlapping_point(x, y)
|
local tab_index = node and node:get_tab_overlapping_point(x, y)
|
||||||
if div then
|
if node and node:get_scroll_button_index(x, y) then
|
||||||
|
system.set_cursor("arrow")
|
||||||
|
elseif div then
|
||||||
local axis = (div.type == "hsplit" and "x" or "y")
|
local axis = (div.type == "hsplit" and "x" or "y")
|
||||||
if div.a:is_resizable(axis) and div.b:is_resizable(axis) then
|
if div.a:is_resizable(axis) and div.b:is_resizable(axis) then
|
||||||
system.set_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
system.set_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||||
|
|
Binary file not shown.
|
@ -1,118 +0,0 @@
|
||||||
{
|
|
||||||
"name": "icons",
|
|
||||||
"css_prefix_text": "icon-",
|
|
||||||
"css_use_suffix": false,
|
|
||||||
"hinting": true,
|
|
||||||
"units_per_em": 1000,
|
|
||||||
"ascent": 850,
|
|
||||||
"glyphs": [
|
|
||||||
{
|
|
||||||
"uid": "9dd9e835aebe1060ba7190ad2b2ed951",
|
|
||||||
"css": "search-1",
|
|
||||||
"code": 76,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "c76b7947c957c9b78b11741173c8349b",
|
|
||||||
"css": "attention-1",
|
|
||||||
"code": 33,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
|
|
||||||
"css": "doc-1",
|
|
||||||
"code": 102,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "f8aa663c489bcbd6e68ec8147dca841e",
|
|
||||||
"css": "folder-1",
|
|
||||||
"code": 100,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "c95735c17a10af81448c7fed98a04546",
|
|
||||||
"css": "folder-open-1",
|
|
||||||
"code": 68,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "e99461abfef3923546da8d745372c995",
|
|
||||||
"css": "cog",
|
|
||||||
"code": 80,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "7bf14281af5633a597f85b061ef1cfb9",
|
|
||||||
"css": "angle-right",
|
|
||||||
"code": 43,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "e4dde1992f787163e2e2b534b8c8067d",
|
|
||||||
"css": "angle-down",
|
|
||||||
"code": 45,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "ea2d9a8c51ca42b38ef0d2a07f16d9a7",
|
|
||||||
"css": "chart-line",
|
|
||||||
"code": 103,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "f4445feb55521283572ee88bc304f928",
|
|
||||||
"css": "floppy",
|
|
||||||
"code": 83,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "9755f76110ae4d12ac5f9466c9152031",
|
|
||||||
"css": "book",
|
|
||||||
"code": 66,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
|
|
||||||
"css": "info-circled-1",
|
|
||||||
"code": 105,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "5211af474d3a9848f67f945e2ccaf143",
|
|
||||||
"css": "cancel-1",
|
|
||||||
"code": 67,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "04f022b8bd044d4ccfffd3887ff72088",
|
|
||||||
"css": "window-minimize",
|
|
||||||
"code": 95,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "d0e62145dbf40f30e47b3819b8b43a8f",
|
|
||||||
"css": "window-restore",
|
|
||||||
"code": 119,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "7394501fc0b17cb7bda99538f92e26d6",
|
|
||||||
"css": "window-close",
|
|
||||||
"code": 88,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "559647a6f430b3aeadbecd67194451dd",
|
|
||||||
"css": "menu-1",
|
|
||||||
"code": 77,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "07f0832c07f3d9713fffb06c8bffa027",
|
|
||||||
"css": "window-maximize",
|
|
||||||
"code": 87,
|
|
||||||
"src": "fontawesome"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
Loading…
Reference in New Issue