summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2020-11-29 22:36:04 +0800
committerruki <[email protected]>2020-11-29 22:36:04 +0800
commite4058387bfd67c02aa0e065705655b1e4b0aafbf (patch)
treed2dfd95343e3161a9e4dc55691e37abbca69b430
parentd48118e840fc0be69f3d78a3894e4bf76b8d801b (diff)
support scrollbar
-rw-r--r--tests/ui/dialog.lua1
-rw-r--r--tests/ui/mconfdialog.lua23
-rw-r--r--xmake/core/ui/action.lua1
-rw-r--r--xmake/core/ui/boxdialog.lua1
-rw-r--r--xmake/core/ui/choicebox.lua173
-rw-r--r--xmake/core/ui/choicedialog.lua43
-rw-r--r--xmake/core/ui/inputdialog.lua1
-rw-r--r--xmake/core/ui/mconfdialog.lua45
-rw-r--r--xmake/core/ui/menuconf.lua190
-rw-r--r--xmake/core/ui/panel.lua2
-rw-r--r--xmake/core/ui/scrollbar.lua215
-rw-r--r--xmake/core/ui/textarea.lua34
-rw-r--r--xmake/core/ui/textdialog.lua70
-rw-r--r--xmake/core/ui/textedit.lua3
14 files changed, 681 insertions, 121 deletions
diff --git a/tests/ui/dialog.lua b/tests/ui/dialog.lua
index 459907fa3..ace1873e4 100644
--- a/tests/ui/dialog.lua
+++ b/tests/ui/dialog.lua
@@ -75,6 +75,7 @@ function demo:dialog_help()
local dialog_help = self._DIALOG_HELP
if not dialog_help then
dialog_help = textdialog:new("dialog.help", rect {1, 1, self:width() - 1, self:height() - 1}, "README")
+ dialog_help:option_set("scrollable", true)
local helptext = nil
local file = io.open("./LICENSE.md", 'r')
if file then
diff --git a/tests/ui/mconfdialog.lua b/tests/ui/mconfdialog.lua
index fd6385ffc..33d6b15dc 100644
--- a/tests/ui/mconfdialog.lua
+++ b/tests/ui/mconfdialog.lua
@@ -59,6 +59,27 @@ function demo:init()
table.insert(configs_sub, menuconf.menu {description = "menu config sub-item", configs = configs_sub2})
table.insert(configs_sub, menuconf.choice {value = 2, values = {2, 5, 16, 87}, description = "choice config sub-item"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item1"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item2"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item3"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item4"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item5"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item6"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item7"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item8"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item9"})
+
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item10"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item11"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item12"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item13"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item14"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item15"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item16"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item17"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item18"})
+ table.insert(configs_sub, menuconf.number {value = 6, default = 10, description = "number config item19"})
+
local configs = {}
table.insert(configs, menuconf.boolean {description = "boolean config item"})
table.insert(configs, menuconf.boolean {default = true, new = false, description = {"boolean config item2",
@@ -68,7 +89,7 @@ function demo:init()
table.insert(configs, menuconf.number {value = 6, default = 10, description = "number config item"})
table.insert(configs, menuconf.string {value = "x86_64", description = "string config item"})
table.insert(configs, menuconf.menu {description = "menu config item", configs = configs_sub})
- table.insert(configs, menuconf.choice {value = 3, values = {1, 5, 6, 7}, description = "choice config item"})
+ table.insert(configs, menuconf.choice {value = 3, values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, default = 2, description = "choice config item"})
-- init menu config dialog
self:dialog_mconf():load(configs)
diff --git a/xmake/core/ui/action.lua b/xmake/core/ui/action.lua
index f3bb87c7e..50d52dd81 100644
--- a/xmake/core/ui/action.lua
+++ b/xmake/core/ui/action.lua
@@ -42,6 +42,7 @@ action:register("ac_max",
"ac_on_selected",
"ac_on_clicked",
"ac_on_resized",
+ "ac_on_scrolled",
"ac_on_enter",
"ac_on_load",
"ac_on_save",
diff --git a/xmake/core/ui/boxdialog.lua b/xmake/core/ui/boxdialog.lua
index 3aedc50dd..8901b8505 100644
--- a/xmake/core/ui/boxdialog.lua
+++ b/xmake/core/ui/boxdialog.lua
@@ -40,7 +40,6 @@ function boxdialog:init(name, bounds, title)
self:text():bounds().ey = self._TEXT_EY
self:text():invalidate(true)
self:text():option_set("selectable", false)
- self:text():option_set("progress", false)
-- insert box
self:panel():insert(self:box())
diff --git a/xmake/core/ui/choicebox.lua b/xmake/core/ui/choicebox.lua
index 1dd84159a..da39dc00c 100644
--- a/xmake/core/ui/choicebox.lua
+++ b/xmake/core/ui/choicebox.lua
@@ -38,27 +38,11 @@ function choicebox:init(name, bounds)
-- init panel
panel.init(self, name, bounds)
- -- init values
- self._VALUES = {}
-end
-
--- on event
-function choicebox:on_event(e)
+ -- init items
+ self._ITEMS = {}
- -- select config
- if e.type == event.ev_keyboard then
- if e.key_name == "Down" then
- return self:select_next()
- elseif e.key_name == "Up" then
- return self:select_prev()
- elseif e.key_name == "Enter" or e.key_name == " " then
- self:_do_select()
- return true
- end
- elseif e.type == event.ev_command and e.command == "cm_enter" then
- self:_do_select()
- return true
- end
+ -- init start index
+ self._STARTINDEX = 1
end
-- load values
@@ -67,38 +51,152 @@ function choicebox:load(values, selected)
-- clear the views first
self:clear()
- -- insert values
- self._VALUES = values
+ -- load items
+ local items = {}
for idx, value in ipairs(values) do
- self:_do_insert(value, idx, idx == selected)
+ table.insert(items, self:_load_item(value, idx, idx == selected))
+ end
+ self._ITEMS = items
+
+ -- insert top-n items
+ local startindex = self._STARTINDEX
+ for idx = startindex, startindex + self:height() - 1 do
+ local item = items[idx]
+ if item then
+ self:insert(item)
+ else
+ break
+ end
end
-- select the first item
self:select(self:first())
+ -- on loaded
+ self:action_on(action.ac_on_load)
+
-- invalidate
self:invalidate()
end
--- do insert a value item
-function choicebox:_do_insert(value, index, selected)
+-- is scrollable?
+function choicebox:scrollable()
+ return #self:_items() > self:height()
+end
+
+-- scroll
+function choicebox:scroll(count)
+ if self:scrollable() then
+ local items = self:_items()
+ local totalcount = #items
+ local startindex = self._STARTINDEX + count
+ if startindex > totalcount then
+ return
+ elseif startindex < 1 then
+ startindex = 1
+ end
+ self._STARTINDEX = startindex
+ self:clear()
+ for idx = startindex, startindex + self:height() - 1 do
+ local item = items[idx]
+ if item then
+ item:bounds():move2(0, idx - startindex)
+ self:insert(item)
+ else
+ break
+ end
+ end
+ if count > 0 then
+ self:select(self:first())
+ else
+ self:select(self:last())
+ end
+ self:invalidate()
+ end
+end
+
+-- on resize
+function choicebox:on_resize()
+ local items = self:_items()
+ local totalcount = #items
+ local startindex = self._STARTINDEX
+ for idx = 1, totalcount do
+ local item = items[idx]
+ if item then
+ if idx >= startindex and idx < startindex + self:height() then
+ if not self:view(item:name()) then
+ item:bounds():move2(0, idx - startindex)
+ self:insert(item)
+ end
+ else
+ if self:view(item:name()) then
+ self:remove(item)
+ end
+ end
+ end
+ end
+ panel.on_resize(self)
+end
+
+-- on event
+function choicebox:on_event(e)
+ if e.type == event.ev_keyboard then
+ if e.key_name == "Down" then
+ if self:current() == self:last() then
+ self:scroll(self:height())
+ else
+ self:select_next()
+ end
+ self:_notify_scrolled()
+ return true
+ elseif e.key_name == "Up" then
+ if self:current() == self:first() then
+ self:scroll(-self:height())
+ else
+ self:select_prev()
+ end
+ self:_notify_scrolled()
+ return true
+ elseif e.key_name == "Enter" or e.key_name == " " then
+ self:_do_select()
+ return true
+ end
+ elseif e.type == event.ev_command and e.command == "cm_enter" then
+ self:_do_select()
+ return true
+ end
+end
+
+-- load a item with value
+function choicebox:_load_item(value, index, selected)
-- init text
local text = (selected and "(X) " or "( ) ") .. tostring(value)
-- init a value item view
- local item = button:new("choicebox.value." .. self:count(),
- rect:new(0, self:count(), self:width(), 1),
+ local item = button:new("choicebox.value." .. index,
+ rect:new(0, index - 1, self:width(), 1),
text,
function (v, e)
self:_do_select()
end)
- -- attach this index
+ -- attach index and value
item:extra_set("index", index)
+ item:extra_set("value", value)
+ return item
+end
- -- insert this config item
- self:insert(item)
+-- notify scrolled
+function choicebox:_notify_scrolled()
+ local totalcount = #self:_items()
+ local startindex = self:current():extra("index")
+ self:action_on(action.ac_on_scrolled, startindex / totalcount)
+end
+
+-- get all items
+function choicebox:_items()
+ return self._ITEMS
end
-- do select the current config
@@ -108,24 +206,17 @@ function choicebox:_do_select()
for v in self:views() do
local text = v:text()
if text and text:startswith("(X) ") then
- local i = v:extra("index")
- if i then
- local t = self._VALUES[i]
- v:text_set("( ) " .. tostring(t))
- end
+ local t = v:extra("value")
+ v:text_set("( ) " .. tostring(t))
end
end
-- get the current item
local item = self:current()
- -- get the current index
- local index = item:extra("index")
-
- -- get the current value
- local value = self._VALUES[index]
-
-- do action: on selected
+ local index = item:extra("index")
+ local value = item:extra("value")
self:action_on(action.ac_on_selected, index, value)
-- update text
diff --git a/xmake/core/ui/choicedialog.lua b/xmake/core/ui/choicedialog.lua
index 86eaac2c6..caf547ff8 100644
--- a/xmake/core/ui/choicedialog.lua
+++ b/xmake/core/ui/choicedialog.lua
@@ -25,6 +25,7 @@ local event = require("ui/event")
local action = require("ui/action")
local curses = require("ui/curses")
local window = require("ui/window")
+local scrollbar = require("ui/scrollbar")
local choicebox = require("ui/choicebox")
local boxdialog = require("ui/boxdialog")
@@ -50,23 +51,63 @@ function choicedialog:init(name, bounds, title)
end)
self:buttons():select(self:button("select"))
+ -- insert scrollbar
+ self:box():panel():insert(self:scrollbar_box())
+
-- insert choice box
self:box():panel():insert(self:choicebox())
-- disable to select to box (disable Tab switch and only response to buttons)
self:box():option_set("selectable", false)
+
+ -- on resize for panel
+ self:box():panel():action_add(action.ac_on_resized, function (v)
+ self:choicebox():bounds_set(rect:new(0, 0, v:width() - 1, v:height()))
+ self:scrollbar_box():bounds_set(rect:new(v:width() - 1, 0, 1, v:height()))
+ if self:choicebox():scrollable() then
+ self:scrollbar_box():show(true)
+ else
+ self:scrollbar_box():show(false)
+ end
+ end)
+
+ -- show scrollbar?
+ self:choicebox():action_add(action.ac_on_load, function (v)
+ if v:scrollable() then
+ self:scrollbar_box():show(true)
+ else
+ self:scrollbar_box():show(false)
+ end
+ end)
+
+ -- on scroll
+ self:choicebox():action_add(action.ac_on_scrolled, function (v, progress)
+ if self:scrollbar_box():state("visible") then
+ self:scrollbar_box():progress_set(progress)
+ end
+ end)
end
-- get choice box
function choicedialog:choicebox()
if not self._CHOICEBOX then
local bounds = self:box():panel():bounds()
- self._CHOICEBOX = choicebox:new("choicedialog.choicebox", rect:new(0, 0, bounds:width(), bounds:height()))
+ self._CHOICEBOX = choicebox:new("choicedialog.choicebox", rect:new(0, 0, bounds:width() - 1, bounds:height()))
self._CHOICEBOX:state_set("focused", true) -- we can select and highlight selected item
end
return self._CHOICEBOX
end
+-- get box scrollbar
+function choicedialog:scrollbar_box()
+ if not self._SCROLLBAR_BOX then
+ local bounds = self:box():panel():bounds()
+ self._SCROLLBAR_BOX = scrollbar:new("choicedialog.scrollbar", rect:new(bounds:width() - 1, 0, 1, bounds:height()))
+ self._SCROLLBAR_BOX:show(false)
+ end
+ return self._SCROLLBAR_BOX
+end
+
-- on event
function choicedialog:on_event(e)
diff --git a/xmake/core/ui/inputdialog.lua b/xmake/core/ui/inputdialog.lua
index 70daf4a86..6dae62052 100644
--- a/xmake/core/ui/inputdialog.lua
+++ b/xmake/core/ui/inputdialog.lua
@@ -45,7 +45,6 @@ function inputdialog:init(name, bounds, title)
self:text():bounds().ey = 1
self:text():invalidate(true)
self:text():option_set("selectable", false)
- self:text():option_set("progress", false)
-- text changed
self:text():action_set(action.ac_on_text_changed, function (v)
diff --git a/xmake/core/ui/mconfdialog.lua b/xmake/core/ui/mconfdialog.lua
index d430e4a18..7b5fef08f 100644
--- a/xmake/core/ui/mconfdialog.lua
+++ b/xmake/core/ui/mconfdialog.lua
@@ -26,6 +26,7 @@ local event = require("ui/event")
local action = require("ui/action")
local curses = require("ui/curses")
local window = require("ui/window")
+local scrollbar = require("ui/scrollbar")
local menuconf = require("ui/menuconf")
local boxdialog = require("ui/boxdialog")
local textdialog = require("ui/textdialog")
@@ -54,6 +55,9 @@ Pressing <Y> includes, <N> excludes. Enter <Esc> or <Back> to go back, <?> for H
self:button_add("save", "< Save >", function (v, e) self:action_on(action.ac_on_save) end)
self:buttons():select(self:button("select"))
+ -- insert scrollbar
+ self:box():panel():insert(self:scrollbar_menuconf())
+
-- insert menu config
self:box():panel():insert(self:menuconf())
self:box():panel():action_add(action.ac_on_resized, function (v)
@@ -64,6 +68,17 @@ Pressing <Y> includes, <N> excludes. Enter <Esc> or <Back> to go back, <?> for H
-- disable to select to box (disable Tab switch and only response to buttons)
self:box():option_set("selectable", false)
+ -- on resize for panel
+ self:box():panel():action_add(action.ac_on_resized, function (v)
+ self:menuconf():bounds_set(rect:new(0, 0, v:width() - 1, v:height()))
+ self:scrollbar_menuconf():bounds_set(rect:new(v:width() - 1, 0, 1, v:height()))
+ if self:menuconf():scrollable() then
+ self:scrollbar_menuconf():show(true)
+ else
+ self:scrollbar_menuconf():show(false)
+ end
+ end)
+
-- on selected
self:menuconf():action_set(action.ac_on_selected, function (v, config)
@@ -94,6 +109,22 @@ Pressing <Y> includes, <N> excludes. Enter <Esc> or <Back> to go back, <?> for H
return true
end
end)
+
+ -- show scrollbar?
+ self:menuconf():action_add(action.ac_on_load, function (v)
+ if v:scrollable() then
+ self:scrollbar_menuconf():show(true)
+ else
+ self:scrollbar_menuconf():show(false)
+ end
+ end)
+
+ -- on scroll
+ self:menuconf():action_add(action.ac_on_scrolled, function (v, progress)
+ if self:scrollbar_menuconf():state("visible") then
+ self:scrollbar_menuconf():progress_set(progress)
+ end
+ end)
end
-- load configs
@@ -111,17 +142,28 @@ end
function mconfdialog:menuconf()
if not self._MENUCONF then
local bounds = self:box():panel():bounds()
- self._MENUCONF = menuconf:new("mconfdialog.menuconf", rect:new(0, 0, bounds:width(), bounds:height()))
+ self._MENUCONF = menuconf:new("mconfdialog.menuconf", rect:new(0, 0, bounds:width() - 1, bounds:height()))
self._MENUCONF:state_set("focused", true) -- we can select and highlight selected item
end
return self._MENUCONF
end
+-- get menu scrollbar
+function mconfdialog:scrollbar_menuconf()
+ if not self._SCROLLBAR_MENUCONF then
+ local bounds = self:box():panel():bounds()
+ self._SCROLLBAR_MENUCONF = scrollbar:new("mconfdialog.scrollbar", rect:new(bounds:width() - 1, 0, 1, bounds:height()))
+ self._SCROLLBAR_MENUCONF:show(false)
+ end
+ return self._SCROLLBAR_MENUCONF
+end
+
-- get help dialog
function mconfdialog:helpdialog()
if not self._HELPDIALOG then
local helpdialog = textdialog:new("mconfdialog.help", self:bounds(), "help")
helpdialog:button_add("exit", "< Exit >", function (v) helpdialog:quit() end)
+ helpdialog:option_set("scrollable", true)
self._HELPDIALOG = helpdialog
end
return self._HELPDIALOG
@@ -132,6 +174,7 @@ function mconfdialog:resultdialog()
if not self._RESULTDIALOG then
local resultdialog = textdialog:new("mconfdialog.result", self:bounds(), "result")
resultdialog:button_add("exit", "< Exit >", function (v) resultdialog:quit() end)
+ resultdialog:option_set("scrollable", true)
self._RESULTDIALOG = resultdialog
end
return self._RESULTDIALOG
diff --git a/xmake/core/ui/menuconf.lua b/xmake/core/ui/menuconf.lua
index a086aa743..a2fe86d25 100644
--- a/xmake/core/ui/menuconf.lua
+++ b/xmake/core/ui/menuconf.lua
@@ -40,18 +40,138 @@ function menuconf:init(name, bounds)
-- init configs
self._CONFIGS = {}
+
+ -- init items
+ self._ITEMS = {}
+
+ -- init start index
+ self._STARTINDEX = 1
+end
+
+-- load configs
+function menuconf:load(configs)
+
+ -- clear the views first
+ self:clear()
+
+ -- detach the previous config and view
+ local configs_prev = self._CONFIGS._PREV
+ if configs_prev then
+ for _, config in ipairs(configs_prev) do
+ config._view = nil
+ end
+ end
+
+ -- save configs
+ self._CONFIGS = configs
+
+ -- load items
+ local items = {}
+ for idx, config in ipairs(configs) do
+ table.insert(items, self:_load_item(config, idx))
+ end
+ self._ITEMS = items
+
+ -- insert top-n items
+ local startindex = self._STARTINDEX
+ for idx = startindex, startindex + self:height() - 1 do
+ local item = items[idx]
+ if item then
+ self:insert(item)
+ else
+ break
+ end
+ end
+
+ -- select the first item
+ self:select(self:first())
+
+ -- on loaded
+ self:action_on(action.ac_on_load)
+
+ -- invalidate
+ self:invalidate()
+end
+
+-- is scrollable?
+function menuconf:scrollable()
+ return #self:_items() > self:height()
+end
+
+-- scroll
+function menuconf:scroll(count)
+ if self:scrollable() then
+ local items = self:_items()
+ local totalcount = #items
+ local startindex = self._STARTINDEX + count
+ if startindex > totalcount then
+ return
+ elseif startindex < 1 then
+ startindex = 1
+ end
+ self._STARTINDEX = startindex
+ self:clear()
+ for idx = startindex, startindex + self:height() - 1 do
+ local item = items[idx]
+ if item then
+ item:bounds():move2(0, idx - startindex)
+ self:insert(item)
+ else
+ break
+ end
+ end
+ if count > 0 then
+ self:select(self:first())
+ else
+ self:select(self:last())
+ end
+ self:invalidate()
+ end
+end
+
+-- on resize
+function menuconf:on_resize()
+ local items = self:_items()
+ local totalcount = #items
+ local startindex = self._STARTINDEX
+ for idx = 1, totalcount do
+ local item = items[idx]
+ if item then
+ if idx >= startindex and idx < startindex + self:height() then
+ if not self:view(item:name()) then
+ item:bounds():move2(0, idx - startindex)
+ self:insert(item)
+ end
+ else
+ if self:view(item:name()) then
+ self:remove(item)
+ end
+ end
+ end
+ end
+ panel.on_resize(self)
end
-- on event
function menuconf:on_event(e)
-
- -- select config
local back = false
if e.type == event.ev_keyboard then
if e.key_name == "Down" then
- return self:select_next()
+ if self:current() == self:last() then
+ self:scroll(self:height())
+ else
+ self:select_next()
+ end
+ self:_notify_scrolled()
+ return true
elseif e.key_name == "Up" then
- return self:select_prev()
+ if self:current() == self:first() then
+ self:scroll(-self:height())
+ else
+ self:select_prev()
+ end
+ self:_notify_scrolled()
+ return true
elseif e.key_name == "Enter" or e.key_name == " " then
self:_do_select()
return true
@@ -85,54 +205,36 @@ function menuconf:on_event(e)
end
end
--- load configs
-function menuconf:load(configs)
-
- -- clear the views first
- self:clear()
-
- -- detach the previous config and view
- local configs_prev = self._CONFIGS._PREV
- if configs_prev then
- for _, config in ipairs(configs_prev) do
- config._view = nil
- end
- end
-
- -- insert configs
- self._CONFIGS = configs
- for _, config in ipairs(configs) do
- if self:count() < self:height() then
- self:_do_insert(config)
- end
- end
-
- -- select the first item
- self:select(self:first())
-
- -- invalidate
- self:invalidate()
-end
-
--- do insert a config item
-function menuconf:_do_insert(config)
+-- load a config item
+function menuconf:_load_item(config, index)
-- init a config item view
- local item = button:new("menuconf.config." .. self:count(),
- rect:new(0, self:count(), self:width(), 1),
- tostring(config),
- function (v, e)
- self:_do_select()
- end)
+ local item = button:new("menuconf.config." .. index,
+ rect:new(0, index - 1, self:width(), 1),
+ tostring(config),
+ function (v, e)
+ self:_do_select()
+ end)
- -- attach this config
+ -- attach this index and config
+ item:extra_set("index", index)
item:extra_set("config", config)
-- attach this view
config._view = item
+ return item
+end
+
+-- notify scrolled
+function menuconf:_notify_scrolled()
+ local totalcount = #self:_items()
+ local startindex = self:current():extra("index")
+ self:action_on(action.ac_on_scrolled, startindex / totalcount)
+end
- -- insert this config item
- self:insert(item)
+-- get all items
+function menuconf:_items()
+ return self._ITEMS
end
-- do select the current config
diff --git a/xmake/core/ui/panel.lua b/xmake/core/ui/panel.lua
index 3a9b6c042..2d860fc7a 100644
--- a/xmake/core/ui/panel.lua
+++ b/xmake/core/ui/panel.lua
@@ -64,7 +64,7 @@ function panel:init(name, bounds)
local p = v:last()
while p do
- if p:option('selectable') and p:bounds():contains(x, y) then
+ if p:option("selectable") and p:bounds():contains(x, y) then
if p:option("mouseable") then
v:select(p)
return p:action_on(action.ac_on_clicked, x, y)
diff --git a/xmake/core/ui/scrollbar.lua b/xmake/core/ui/scrollbar.lua
new file mode 100644
index 000000000..59ebbb9ec
--- /dev/null
+++ b/xmake/core/ui/scrollbar.lua
@@ -0,0 +1,215 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-2020, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file scrollbar.lua
+--
+
+-- load modules
+local log = require("ui/log")
+local view = require("ui/view")
+local event = require("ui/event")
+local curses = require("ui/curses")
+local action = require("ui/action")
+
+-- define module
+local scrollbar = scrollbar or view()
+
+-- init scrollbar
+function scrollbar:init(name, bounds, vertical)
+
+ -- init view
+ view.init(self, name, bounds)
+
+ -- init bar attribute
+ self:charattr_set("black on black")
+
+ -- init bar vertical
+ self:vertical_set(vertical)
+
+ -- init progress
+ self:progress_set(0)
+
+ -- init character
+ self:char_set(' ')
+end
+
+-- get bar attribute
+function scrollbar:charattr()
+ return self:attr("charattr")
+end
+
+-- set bar attribute, .e.g charattr_set("yellow onblue bold")
+function scrollbar:charattr_set(attr)
+ return self:attr_set("charattr", attr)
+end
+
+-- get the current char attribute value
+function scrollbar:charattr_val()
+
+ -- get text attribute
+ local charattr = self:charattr()
+ if not charattr then
+ return
+ end
+
+ -- no text background? use view's background
+ if self:background() and not charattr:find("on") then
+ charattr = charattr .. " on" .. self:background()
+ end
+
+ -- attempt to get the attribute value from the cache first
+ self._charattr = self._charattr or {}
+ local value = self._charattr[charattr]
+ if value then
+ return value
+ end
+
+ -- update the cache
+ value = curses.calc_attr(charattr:split("%s+"))
+ self._charattr[charattr] = value
+ return value
+end
+
+-- get bar character
+function scrollbar:char()
+ return self:attr("char") or ' '
+end
+
+-- set bar character
+function scrollbar:char_set(char)
+ if char ~= self:char() then
+ self:invalidate()
+ end
+ return self:attr_set("char", char)
+end
+
+-- is vertical bar?
+function scrollbar:vertical()
+ return self:attr("vertical") or true
+end
+
+-- set bar vertical
+function scrollbar:vertical_set(vertical)
+ return self:attr_set("vertical", vertical)
+end
+
+-- get bar progress
+function scrollbar:progress()
+ return self:attr("progress") or 0
+end
+
+-- set bar progress, [0, 1]
+function scrollbar:progress_set(progress)
+ if progress > 1 then
+ progress = 1
+ elseif progress < 0 then
+ progress = 0
+ end
+ if progress ~= self:progress() then
+ self:invalidate()
+ end
+ return self:attr_set("progress", progress)
+end
+
+-- get bar step width
+function scrollbar:stepwidth()
+ return self:attr("stepwidth") or 0.1
+end
+
+-- set bar step width, [0, 1]
+function scrollbar:stepwidth_set(stepwidth)
+ if stepwidth > 1 then
+ stepwidth = 1
+ elseif stepwidth < 0 then
+ stepwidth = 0
+ end
+ if stepwidth ~= self:stepwidth() then
+ self:invalidate()
+ end
+ return self:attr_set("stepwidth", stepwidth)
+end
+
+-- draw scrollbar
+function scrollbar:on_draw(transparent)
+
+ -- draw background
+ view.on_draw(self, transparent)
+
+ -- draw bar
+ local char = self:char()
+ local charattr = self:charattr_val()
+ if self:vertical() then
+ local sn = math.ceil(self:height() * self:stepwidth())
+ local sb = math.floor(self:height() * self:progress())
+ local se = sb + sn
+ if se > self:height() then
+ sb = self:height() - sn
+ se = self:height()
+ end
+ if se > sb and se - sb <= self:height() then
+ for x = 0, self:width() - 1 do
+ self:canvas():attr(charattr):move(x, sb):putchar(char, se - sb, true)
+ end
+ end
+ else
+ local sn = math.ceil(self:width() * self:stepwidth())
+ local sb = math.floor(self:width() * self:progress())
+ local se = sb + sn
+ if se > self:width() then
+ sb = self:width() - sn
+ se = self:width()
+ end
+ if se > sb and se - sb <= self:width() then
+ for y = 0, self:height() - 1 do
+ self:canvas():attr(charattr):move(sb, y):putchar(char, se - sb)
+ end
+ end
+ end
+end
+
+-- scroll bar, e.g. -1 * 0.1, 1 * 0.1
+function scrollbar:scroll(steps)
+ steps = steps or 1
+ self:progress_set(self:progress() + steps * self:stepwidth())
+ self:action_on(action.ac_on_scrolled, self:progress())
+end
+
+-- on event
+function scrollbar:on_event(e)
+ if e.type == event.ev_keyboard then
+ if self:vertical() then
+ if e.key_name == "Up" then
+ self:scroll(-1)
+ return true
+ elseif e.key_name == "Down" then
+ self:scroll(1)
+ return true
+ end
+ else
+ if e.key_name == "Left" then
+ self:scroll(-1)
+ return true
+ elseif e.key_name == "Right" then
+ self:scroll(1)
+ return true
+ end
+ end
+ end
+end
+
+-- return module
+return scrollbar
diff --git a/xmake/core/ui/textarea.lua b/xmake/core/ui/textarea.lua
index 0dc713012..5977d471e 100644
--- a/xmake/core/ui/textarea.lua
+++ b/xmake/core/ui/textarea.lua
@@ -24,6 +24,7 @@ local view = require("ui/view")
local label = require("ui/label")
local event = require("ui/event")
local curses = require("ui/curses")
+local action = require("ui/action")
-- define module
local textarea = textarea or label()
@@ -37,9 +38,6 @@ function textarea:init(name, bounds, text)
-- mark as selectable
self:option_set("selectable", true)
- -- enable progress
- self:option_set("progress", true)
-
-- init start line
self._STARTLINE = 0
self._LINECOUNT = 0
@@ -59,17 +57,6 @@ function textarea:on_draw(transparent)
if strs and #strs > 0 and textattr then
self:canvas():attr(textattr):move(0, 0):putstrs(strs, self._STARTLINE + 1)
end
-
- -- draw progress
- if self:option("progress") then
- local tb = self._STARTLINE
- local fator = self:height() / self._LINECOUNT
- local sb = math.min(math.floor(tb * fator), self:height() - 1)
- local se = math.min(sb + math.ceil(self:height() * fator), self:height())
- if se > sb and se - sb < self:height() then
- self:canvas():attr("black"):move(self:width() - 1, sb):putchar(' ', se - sb, true)
- end
- end
end
-- set text
@@ -80,24 +67,33 @@ function textarea:text_set(text)
return label.text_set(self, text)
end
+-- is scrollable?
+function textarea:scrollable()
+ return self._LINECOUNT > self:height()
+end
+
-- scroll
function textarea:scroll(lines)
- if self._LINECOUNT > self:height() then
+ if self:scrollable() then
self._STARTLINE = self._STARTLINE + lines
if self._STARTLINE < 0 then
self._STARTLINE = 0
end
- if self._STARTLINE > self._LINECOUNT - self:height() then
- self._STARTLINE = self._LINECOUNT - self:height()
+ local startline_end = self._LINECOUNT > self:height() and self._LINECOUNT - self:height() or self._LINECOUNT
+ if self._STARTLINE > startline_end then
+ self._STARTLINE = startline_end
end
+ self:action_on(action.ac_on_scrolled, self._STARTLINE / startline_end)
self:invalidate()
end
end
-- scroll to end
function textarea:scroll_to_end()
- if self._LINECOUNT > self:height() then
- self._STARTLINE = self._LINECOUNT - self:height()
+ if self:scrollable() then
+ local startline_end = self._LINECOUNT > self:height() and self._LINECOUNT - self:height() or self._LINECOUNT
+ self._STARTLINE = startline_end
+ self:action_on(action.ac_on_scrolled, self._STARTLINE / startline_end)
self:invalidate()
end
end
diff --git a/xmake/core/ui/textdialog.lua b/xmake/core/ui/textdialog.lua
index 52ee51cbd..cc4243740 100644
--- a/xmake/core/ui/textdialog.lua
+++ b/xmake/core/ui/textdialog.lua
@@ -19,13 +19,14 @@
--
-- load modules
-local log = require("ui/log")
-local rect = require("ui/rect")
-local event = require("ui/event")
-local dialog = require("ui/dialog")
-local curses = require("ui/curses")
-local textarea = require("ui/textarea")
-local action = require("ui/action")
+local log = require("ui/log")
+local rect = require("ui/rect")
+local event = require("ui/event")
+local dialog = require("ui/dialog")
+local curses = require("ui/curses")
+local textarea = require("ui/textarea")
+local scrollbar = require("ui/scrollbar")
+local action = require("ui/action")
-- define module
local textdialog = textdialog or dialog()
@@ -36,18 +37,62 @@ function textdialog:init(name, bounds, title)
-- init window
dialog.init(self, name, bounds, title)
+ -- mark as scrollable, disabled by default
+ self:option_set("scrollable", false)
+
-- insert text
self:panel():insert(self:text())
+ -- insert scrollbar
+ self:panel():insert(self:scrollbar())
+
-- select buttons by default
self:panel():select(self:buttons())
-- on resize for panel
self:panel():action_add(action.ac_on_resized, function (v)
- self:text():bounds_set(rect:new(0, 0, v:width(), v:height() - 1))
+ if self:option("scrollable") then
+ self:text():bounds_set(rect:new(0, 0, v:width() - 1, v:height() - 1))
+ self:scrollbar():bounds_set(rect:new(v:width() - 1, 0, 1, v:height() - 1))
+ else
+ self:text():bounds_set(rect:new(0, 0, v:width(), v:height() - 1))
+ end
+ end)
+
+ -- show scrollbar?
+ self:text():action_add(action.ac_on_text_changed, function (v)
+ if self:option("scrollable") then
+ if v:scrollable() then
+ self:scrollbar():show(true)
+ else
+ self:scrollbar():show(false)
+ end
+ end
+ end)
+
+ -- on scroll for text and scrollbar
+ self:text():action_add(action.ac_on_scrolled, function (v, progress)
+ if self:scrollbar():state("visible") then
+ self:scrollbar():progress_set(progress)
+ end
end)
end
+-- enable or disable scrollbar
+function textdialog:option_set(name, value)
+ if name == "scrollable" then
+ local oldvalue = self:option(name)
+ if value ~= oldvalue then
+ if value then
+ self:text():bounds():resize(self:panel():width() - 1, self:panel():height() - 1)
+ else
+ self:text():bounds():resize(self:panel():width(), self:panel():height() - 1)
+ end
+ end
+ end
+ dialog.option_set(self, name, value)
+end
+
-- get text
function textdialog:text()
if not self._TEXT then
@@ -56,6 +101,15 @@ function textdialog:text()
return self._TEXT
end
+-- get scrollbar
+function textdialog:scrollbar()
+ if not self._SCROLLBAR then
+ self._SCROLLBAR = scrollbar:new("textdialog.scrollbar", rect:new(self:panel():width() - 1, 0, 1, self:panel():height() - 1))
+ self._SCROLLBAR:show(false)
+ end
+ return self._SCROLLBAR
+end
+
-- on event
function textdialog:on_event(e)
diff --git a/xmake/core/ui/textedit.lua b/xmake/core/ui/textedit.lua
index 94ef2b25f..7589c416d 100644
--- a/xmake/core/ui/textedit.lua
+++ b/xmake/core/ui/textedit.lua
@@ -47,9 +47,6 @@ function textedit:init(name, bounds, text)
self:option_set("mouseable", true)
self:action_set(action.ac_on_clicked, function () return true end)
- -- disable progress
- self:option_set("progress", false)
-
-- enable multiple line
self:option_set("multiline", true)
end