diff --git a/frontend/apps/reader/modules/readertoc.lua b/frontend/apps/reader/modules/readertoc.lua
index 499cde266..9d98a57c5 100644
--- a/frontend/apps/reader/modules/readertoc.lua
+++ b/frontend/apps/reader/modules/readertoc.lua
@@ -1,11 +1,12 @@
local InputContainer = require("ui/widget/container/inputcontainer")
local CenterContainer = require("ui/widget/container/centercontainer")
local GestureRange = require("ui/gesturerange")
+local Button = require("ui/widget/button")
+local UIManager = require("ui/uimanager")
local Menu = require("ui/widget/menu")
local Geom = require("ui/geometry")
local Screen = require("ui/screen")
local Device = require("ui/device")
-local UIManager = require("ui/uimanager")
local Event = require("ui/event")
local Font = require("ui/font")
local DEBUG = require("dbg")
@@ -14,6 +15,10 @@ local _ = require("gettext")
local ReaderToc = InputContainer:new{
toc = nil,
ticks = {},
+ toc_indent = " ",
+ collapsed_toc = {},
+ collapse_depth = 2,
+ expanded_nodes = {},
toc_menu_title = _("Table of contents"),
}
@@ -54,6 +59,7 @@ end
function ReaderToc:onUpdateToc()
self.toc = nil
self.ticks = {}
+ self.collapsed_toc = {}
return true
end
@@ -208,29 +214,66 @@ function ReaderToc:getChapterPagesDone(pageno, level)
return previous_chapter
end
+function ReaderToc:updateCurrentNode()
+ if #self.collapsed_toc > 0 then
+ for i, v in ipairs(self.collapsed_toc) do
+ if v.page > self.pageno then
+ self.collapsed_toc.current = i > 1 and i - 1 or 1
+ break
+ end
+ end
+ end
+end
+
function ReaderToc:onShowToc()
self:fillToc()
+ local max_depth = self:getMaxDepth()
-- build menu items
if #self.toc > 0 and not self.toc[1].text then
for _,v in ipairs(self.toc) do
- v.text = (" "):rep(v.depth-1)..self:cleanUpTocTitle(v.title)
+ v.text = self.toc_indent:rep(v.depth-1)..self:cleanUpTocTitle(v.title)
v.mandatory = v.page
end
end
- -- update current entry
- if #self.toc > 0 then
- for i=1, #self.toc do
- v = self.toc[i]
- if v.page > self.pageno then
- self.toc.current = i > 1 and i - 1 or 1
- break
+
+ -- update collapsible state
+ self.expand_button = Button:new{
+ icon = "resources/icons/appbar.control.expand.png",
+ bordersize = 0,
+ show_parent = self,
+ }
+
+ self.collapse_button = Button:new{
+ icon = "resources/icons/appbar.control.collapse.png",
+ bordersize = 0,
+ show_parent = self,
+ }
+
+ if #self.toc > 0 and #self.collapsed_toc == 0 then
+ local depth = 0
+ for i = #self.toc, 1, -1 do
+ local v = self.toc[i]
+ -- node v has child node(s)
+ if v.depth < depth then
+ v.state = self.expand_button:new{
+ callback = function() self:expandToc(i) end,
+ indent = self.toc_indent:rep(v.depth-1),
+ }
+ end
+ if v.depth < self.collapse_depth then
+ table.insert(self.collapsed_toc, 1, v)
end
+ depth = v.depth
end
end
+ self:updateCurrentNode()
+
+ local button_size = self.expand_button:getSize()
local toc_menu = Menu:new{
title = _("Table of Contents"),
- item_table = self.toc,
+ item_table = self.collapsed_toc,
+ state_size = button_size,
ui = self.ui,
is_borderless = true,
width = Screen:getWidth(),
@@ -264,11 +307,74 @@ function ReaderToc:onShowToc()
toc_menu.show_parent = menu_container
+ self.toc_menu = toc_menu
+
UIManager:show(menu_container)
return true
end
+-- expand TOC node of index in raw toc table
+function ReaderToc:expandToc(index)
+ table.insert(self.expanded_nodes, index)
+ local cur_node = self.toc[index]
+ local cur_depth = cur_node.depth
+ local collapsed_index = nil
+ for i, v in ipairs(self.collapsed_toc) do
+ if v.page == cur_node.page and v.depth == cur_depth
+ and v.text == cur_node.text then
+ collapsed_index = i
+ break
+ end
+ end
+ for i = index + 1, #self.toc do
+ local v = self.toc[i]
+ if v.depth == cur_depth + 1 then
+ collapsed_index = collapsed_index + 1
+ table.insert(self.collapsed_toc, collapsed_index, v)
+ elseif v.depth <= cur_depth then
+ break
+ end
+ end
+ -- change state of current node to expanded
+ cur_node.state = self.collapse_button:new{
+ callback = function() self:collapseToc(index) end,
+ indent = self.toc_indent:rep(cur_depth-1),
+ }
+ self:updateCurrentNode()
+ self.toc_menu:swithItemTable(nil, self.collapsed_toc, -1)
+end
+
+-- collapse TOC node of index in raw toc table
+function ReaderToc:collapseToc(index)
+ local cur_node = self.toc[index]
+ local cur_depth = cur_node.depth
+ local i = 1
+ local is_child_node = false
+ while i <= #self.collapsed_toc do
+ local v = self.collapsed_toc[i]
+ if v.page > cur_node.page and v.depth <= cur_depth then
+ is_child_node = false
+ end
+ if is_child_node then
+ table.remove(self.collapsed_toc, i)
+ else
+ i = i + 1
+ end
+ if v.page == cur_node.page and v.depth == cur_depth
+ and v.text == cur_node.text then
+ is_child_node = true
+ end
+ end
+ -- change state of current node to collapsed
+ cur_node.state = self.expand_button:new{
+ callback = function() self:expandToc(index) end,
+ indent = self.toc_indent:rep(cur_depth-1),
+ }
+ self:updateCurrentNode()
+ self.toc_menu:swithItemTable(nil, self.collapsed_toc, -1)
+end
+
function ReaderToc:addToMainMenu(tab_item_table)
-- insert table to main reader menu
table.insert(tab_item_table.navi, 1, {
diff --git a/frontend/ui/widget/menu.lua b/frontend/ui/widget/menu.lua
index 724a94b22..65fc7b0ab 100644
--- a/frontend/ui/widget/menu.lua
+++ b/frontend/ui/widget/menu.lua
@@ -160,28 +160,51 @@ function MenuItem:init()
end
local mandatory = self.mandatory and ""..self.mandatory.." " or ""
- local mandatory_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.info_face, ""..mandatory, true).x
-
- w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, self.text, true).x
- if w + mandatory_w >= self.content_width then
- if Device:isTouchDevice() then
- else
+ local mandatory_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.info_face,
+ ""..mandatory, true, self.bold).x
+
+ local state_button_width = self.state_size.w or 0
+ w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face,
+ self.text, true, self.bold).x
+ if w + mandatory_w + state_button_width >= self.content_width then
+ if Device:hasKeyboard() then
self.active_key_events.ShowItemDetail = {
{"Right"}, doc = "show item detail"
}
end
local indicator = " >> "
- local indicator_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, indicator, true).x
+ local indicator_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face,
+ indicator, true, self.bold).x
self.text = RenderText:getSubTextByWidth(self.text, self.face,
- self.content_width - indicator_w - mandatory_w, true) .. indicator
+ self.content_width - indicator_w - mandatory_w - state_button_width,
+ true, self.bold) .. indicator
end
+ local state_button = self.state or HorizontalSpan:new{
+ width = state_button_width,
+ }
+ local state_indent = self.state and self.state.indent or ""
+ local state_container = LeftContainer:new{
+ dimen = Geom:new{w = self.content_width/2, h = self.dimen.h},
+ HorizontalGroup:new{
+ HorizontalSpan:new{
+ width = RenderText:sizeUtf8Text(0, self.dimen.w, self.face,
+ state_indent, true, self.bold).x,
+ },
+ state_button
+ }
+ }
local text_container = LeftContainer:new{
dimen = Geom:new{w = self.content_width, h = self.dimen.h},
- TextWidget:new{
- text = self.text,
- face = self.face,
- bold = self.bold,
+ HorizontalGroup:new{
+ HorizontalSpan:new{
+ width = self.state_size.w,
+ },
+ TextWidget:new{
+ text = self.text,
+ face = self.face,
+ bold = self.bold,
+ }
}
}
@@ -195,6 +218,7 @@ function MenuItem:init()
}
self._underline_container = UnderlineContainer:new{
+ vertical_align = "center",
dimen = Geom:new{
w = self.content_width,
h = self.dimen.h
@@ -203,16 +227,17 @@ function MenuItem:init()
align = "center",
OverlapGroup:new{
dimen = Geom:new{w = self.content_width, h = self.dimen.h},
+ state_container,
text_container,
mandatory_container,
},
}
}
-
self[1] = FrameContainer:new{
bordersize = 0,
padding = 0,
HorizontalGroup:new{
+ align = "center",
HorizontalSpan:new{ width = 5 },
ItemShortCutIcon:new{
dimen = shortcut_icon_dimen,
@@ -569,6 +594,8 @@ function Menu:updateItems(select_number)
end
local item_tmp = MenuItem:new{
show_parent = self.show_parent,
+ state = self.item_table[i].state,
+ state_size = self.state_size or {},
text = self.item_table[i].text,
mandatory = self.item_table[i].mandatory,
bold = self.item_table.current == i,
diff --git a/resources/icons/appbar.control.collapse.png b/resources/icons/appbar.control.collapse.png
new file mode 100644
index 000000000..b87cad71f
Binary files /dev/null and b/resources/icons/appbar.control.collapse.png differ
diff --git a/resources/icons/appbar.control.expand.png b/resources/icons/appbar.control.expand.png
new file mode 100644
index 000000000..284407b2d
Binary files /dev/null and b/resources/icons/appbar.control.expand.png differ
diff --git a/resources/icons/src/appbar.control.collapse.svg b/resources/icons/src/appbar.control.collapse.svg
new file mode 100644
index 000000000..101eed96d
--- /dev/null
+++ b/resources/icons/src/appbar.control.collapse.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/resources/icons/src/appbar.control.expand.svg b/resources/icons/src/appbar.control.expand.svg
new file mode 100644
index 000000000..311087080
--- /dev/null
+++ b/resources/icons/src/appbar.control.expand.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/spec/unit/readertoc_spec.lua b/spec/unit/readertoc_spec.lua
index 1b62d8cd1..a4c93098e 100644
--- a/spec/unit/readertoc_spec.lua
+++ b/spec/unit/readertoc_spec.lua
@@ -24,7 +24,7 @@ describe("Readertoc module", function()
local ticks_level_0 = nil
it("should get ticks of level 0", function()
ticks_level_0 = toc:getTocTicks(0)
- DEBUG("ticks", ticks_level_0)
+ --DEBUG("ticks", ticks_level_0)
assert.are.same(28, #ticks_level_0)
end)
local ticks_level_1 = nil
@@ -68,4 +68,26 @@ describe("Readertoc module", function()
assert.are.same(0, toc:getChapterPagesDone(100, 0))
assert.are.same(10, toc:getChapterPagesDone(200, 0))
end)
+ describe("collasible TOC", function()
+ it("should collapse the secondary toc nodes by default", function()
+ toc:onShowToc()
+ assert.are.same(7, #toc.collapsed_toc)
+ end)
+ it("should not expand toc nodes that have no child nodes", function()
+ toc:expandToc(2)
+ assert.are.same(7, #toc.collapsed_toc)
+ end)
+ it("should expand toc nodes that have child nodes", function()
+ toc:expandToc(3)
+ assert.are.same(13, #toc.collapsed_toc)
+ toc:expandToc(18)
+ assert.are.same(18, #toc.collapsed_toc)
+ end)
+ it("should collapse toc nodes that have been expanded", function()
+ toc:collapseToc(3)
+ assert.are.same(12, #toc.collapsed_toc)
+ toc:collapseToc(18)
+ assert.are.same(7, #toc.collapsed_toc)
+ end)
+ end)
end)