@@ -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",
|
||||
|
@@ -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())
|
||||
|
@@ -38,19 +38,125 @@ function choicebox:init(name, bounds)
|
||||
-- init panel
|
||||
panel.init(self, name, bounds)
|
||||
|
||||
-- init values
|
||||
self._VALUES = {}
|
||||
-- init items
|
||||
self._ITEMS = {}
|
||||
|
||||
-- init start index
|
||||
self._STARTINDEX = 1
|
||||
end
|
||||
|
||||
-- load values
|
||||
function choicebox:load(values, selected)
|
||||
|
||||
-- clear the views first
|
||||
self:clear()
|
||||
|
||||
-- load items
|
||||
local items = {}
|
||||
for idx, value in ipairs(values) do
|
||||
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
|
||||
|
||||
-- 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)
|
||||
|
||||
-- select config
|
||||
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
|
||||
@@ -61,44 +167,36 @@ function choicebox:on_event(e)
|
||||
end
|
||||
end
|
||||
|
||||
-- load values
|
||||
function choicebox:load(values, selected)
|
||||
|
||||
-- clear the views first
|
||||
self:clear()
|
||||
|
||||
-- insert values
|
||||
self._VALUES = values
|
||||
for idx, value in ipairs(values) do
|
||||
self:_do_insert(value, idx, idx == selected)
|
||||
end
|
||||
|
||||
-- select the first item
|
||||
self:select(self:first())
|
||||
|
||||
-- invalidate
|
||||
self:invalidate()
|
||||
end
|
||||
|
||||
-- do insert a value item
|
||||
function choicebox:_do_insert(value, index, selected)
|
||||
-- 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
|
||||
|
@@ -25,6 +25,7 @@ local event = require("ltui/event")
|
||||
local action = require("ltui/action")
|
||||
local curses = require("ltui/curses")
|
||||
local window = require("ltui/window")
|
||||
local scrollbar = require("ltui/scrollbar")
|
||||
local choicebox = require("ltui/choicebox")
|
||||
local boxdialog = require("ltui/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)
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -26,6 +26,7 @@ local event = require("ltui/event")
|
||||
local action = require("ltui/action")
|
||||
local curses = require("ltui/curses")
|
||||
local window = require("ltui/window")
|
||||
local scrollbar = require("ltui/scrollbar")
|
||||
local menuconf = require("ltui/menuconf")
|
||||
local boxdialog = require("ltui/boxdialog")
|
||||
local textdialog = require("ltui/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
|
||||
|
@@ -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),
|
||||
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
|
||||
|
||||
-- insert this config item
|
||||
self:insert(item)
|
||||
-- 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
|
||||
|
||||
-- get all items
|
||||
function menuconf:_items()
|
||||
return self._ITEMS
|
||||
end
|
||||
|
||||
-- do select the current config
|
||||
|
@@ -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)
|
||||
|
215
src/ltui/scrollbar.lua
Normal file
215
src/ltui/scrollbar.lua
Normal file
@@ -0,0 +1,215 @@
|
||||
---A cross-platform terminal ui library 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("ltui/base/log")
|
||||
local view = require("ltui/view")
|
||||
local event = require("ltui/event")
|
||||
local curses = require("ltui/curses")
|
||||
local action = require("ltui/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
|
@@ -24,6 +24,7 @@ local view = require("ltui/view")
|
||||
local label = require("ltui/label")
|
||||
local event = require("ltui/event")
|
||||
local curses = require("ltui/curses")
|
||||
local action = require("ltui/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
|
||||
|
@@ -19,13 +19,14 @@
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local rect = require("ltui/rect")
|
||||
local event = require("ltui/event")
|
||||
local dialog = require("ltui/dialog")
|
||||
local curses = require("ltui/curses")
|
||||
local textarea = require("ltui/textarea")
|
||||
local action = require("ltui/action")
|
||||
local log = require("ltui/base/log")
|
||||
local rect = require("ltui/rect")
|
||||
local event = require("ltui/event")
|
||||
local dialog = require("ltui/dialog")
|
||||
local curses = require("ltui/curses")
|
||||
local textarea = require("ltui/textarea")
|
||||
local scrollbar = require("ltui/scrollbar")
|
||||
local action = require("ltui/action")
|
||||
|
||||
-- define module
|
||||
local textdialog = textdialog or dialog()
|
||||
@@ -36,16 +37,60 @@ 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
|
||||
@@ -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)
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -76,6 +76,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
|
||||
|
@@ -60,6 +60,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",
|
||||
@@ -69,7 +90,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}, default = 2, 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)
|
||||
|
Reference in New Issue
Block a user