Merge pull request #23 from tboox/scrollbar

add scrollbar
This commit is contained in:
ruki
2020-11-29 22:18:47 +08:00
committed by GitHub
14 changed files with 679 additions and 119 deletions

View File

@@ -42,6 +42,7 @@ action:register("ac_max",
"ac_on_selected", "ac_on_selected",
"ac_on_clicked", "ac_on_clicked",
"ac_on_resized", "ac_on_resized",
"ac_on_scrolled",
"ac_on_enter", "ac_on_enter",
"ac_on_load", "ac_on_load",
"ac_on_save", "ac_on_save",

View File

@@ -40,7 +40,6 @@ function boxdialog:init(name, bounds, title)
self:text():bounds().ey = self._TEXT_EY self:text():bounds().ey = self._TEXT_EY
self:text():invalidate(true) self:text():invalidate(true)
self:text():option_set("selectable", false) self:text():option_set("selectable", false)
self:text():option_set("progress", false)
-- insert box -- insert box
self:panel():insert(self:box()) self:panel():insert(self:box())

View File

@@ -38,19 +38,125 @@ function choicebox:init(name, bounds)
-- init panel -- init panel
panel.init(self, name, bounds) panel.init(self, name, bounds)
-- init values -- init items
self._VALUES = {} 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 end
-- on event -- on event
function choicebox:on_event(e) function choicebox:on_event(e)
-- select config
if e.type == event.ev_keyboard then if e.type == event.ev_keyboard then
if e.key_name == "Down" 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 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 elseif e.key_name == "Enter" or e.key_name == " " then
self:_do_select() self:_do_select()
return true return true
@@ -61,44 +167,36 @@ function choicebox:on_event(e)
end end
end end
-- load values -- load a item with value
function choicebox:load(values, selected) function choicebox:_load_item(value, index, 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)
-- init text -- init text
local text = (selected and "(X) " or "( ) ") .. tostring(value) local text = (selected and "(X) " or "( ) ") .. tostring(value)
-- init a value item view -- init a value item view
local item = button:new("choicebox.value." .. self:count(), local item = button:new("choicebox.value." .. index,
rect:new(0, self:count(), self:width(), 1), rect:new(0, index - 1, self:width(), 1),
text, text,
function (v, e) function (v, e)
self:_do_select() self:_do_select()
end) end)
-- attach this index -- attach index and value
item:extra_set("index", index) item:extra_set("index", index)
item:extra_set("value", value)
return item
end
-- insert this config item -- notify scrolled
self:insert(item) 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 end
-- do select the current config -- do select the current config
@@ -108,24 +206,17 @@ function choicebox:_do_select()
for v in self:views() do for v in self:views() do
local text = v:text() local text = v:text()
if text and text:startswith("(X) ") then if text and text:startswith("(X) ") then
local i = v:extra("index") local t = v:extra("value")
if i then
local t = self._VALUES[i]
v:text_set("( ) " .. tostring(t)) v:text_set("( ) " .. tostring(t))
end end
end end
end
-- get the current item -- get the current item
local item = self:current() 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 -- do action: on selected
local index = item:extra("index")
local value = item:extra("value")
self:action_on(action.ac_on_selected, index, value) self:action_on(action.ac_on_selected, index, value)
-- update text -- update text

View File

@@ -25,6 +25,7 @@ local event = require("ltui/event")
local action = require("ltui/action") local action = require("ltui/action")
local curses = require("ltui/curses") local curses = require("ltui/curses")
local window = require("ltui/window") local window = require("ltui/window")
local scrollbar = require("ltui/scrollbar")
local choicebox = require("ltui/choicebox") local choicebox = require("ltui/choicebox")
local boxdialog = require("ltui/boxdialog") local boxdialog = require("ltui/boxdialog")
@@ -50,23 +51,63 @@ function choicedialog:init(name, bounds, title)
end) end)
self:buttons():select(self:button("select")) self:buttons():select(self:button("select"))
-- insert scrollbar
self:box():panel():insert(self:scrollbar_box())
-- insert choice box -- insert choice box
self:box():panel():insert(self:choicebox()) self:box():panel():insert(self:choicebox())
-- disable to select to box (disable Tab switch and only response to buttons) -- disable to select to box (disable Tab switch and only response to buttons)
self:box():option_set("selectable", false) 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 end
-- get choice box -- get choice box
function choicedialog:choicebox() function choicedialog:choicebox()
if not self._CHOICEBOX then if not self._CHOICEBOX then
local bounds = self:box():panel():bounds() 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 self._CHOICEBOX:state_set("focused", true) -- we can select and highlight selected item
end end
return self._CHOICEBOX return self._CHOICEBOX
end 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 -- on event
function choicedialog:on_event(e) function choicedialog:on_event(e)

View File

@@ -45,7 +45,6 @@ function inputdialog:init(name, bounds, title)
self:text():bounds().ey = 1 self:text():bounds().ey = 1
self:text():invalidate(true) self:text():invalidate(true)
self:text():option_set("selectable", false) self:text():option_set("selectable", false)
self:text():option_set("progress", false)
-- text changed -- text changed
self:text():action_set(action.ac_on_text_changed, function (v) self:text():action_set(action.ac_on_text_changed, function (v)

View File

@@ -26,6 +26,7 @@ local event = require("ltui/event")
local action = require("ltui/action") local action = require("ltui/action")
local curses = require("ltui/curses") local curses = require("ltui/curses")
local window = require("ltui/window") local window = require("ltui/window")
local scrollbar = require("ltui/scrollbar")
local menuconf = require("ltui/menuconf") local menuconf = require("ltui/menuconf")
local boxdialog = require("ltui/boxdialog") local boxdialog = require("ltui/boxdialog")
local textdialog = require("ltui/textdialog") 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:button_add("save", "< Save >", function (v, e) self:action_on(action.ac_on_save) end)
self:buttons():select(self:button("select")) self:buttons():select(self:button("select"))
-- insert scrollbar
self:box():panel():insert(self:scrollbar_menuconf())
-- insert menu config -- insert menu config
self:box():panel():insert(self:menuconf()) self:box():panel():insert(self:menuconf())
self:box():panel():action_add(action.ac_on_resized, function (v) 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) -- disable to select to box (disable Tab switch and only response to buttons)
self:box():option_set("selectable", false) 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 -- on selected
self:menuconf():action_set(action.ac_on_selected, function (v, config) 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 return true
end end
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 end
-- load configs -- load configs
@@ -111,17 +142,28 @@ end
function mconfdialog:menuconf() function mconfdialog:menuconf()
if not self._MENUCONF then if not self._MENUCONF then
local bounds = self:box():panel():bounds() 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 self._MENUCONF:state_set("focused", true) -- we can select and highlight selected item
end end
return self._MENUCONF return self._MENUCONF
end 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 -- get help dialog
function mconfdialog:helpdialog() function mconfdialog:helpdialog()
if not self._HELPDIALOG then if not self._HELPDIALOG then
local helpdialog = textdialog:new("mconfdialog.help", self:bounds(), "help") local helpdialog = textdialog:new("mconfdialog.help", self:bounds(), "help")
helpdialog:button_add("exit", "< Exit >", function (v) helpdialog:quit() end) helpdialog:button_add("exit", "< Exit >", function (v) helpdialog:quit() end)
helpdialog:option_set("scrollable", true)
self._HELPDIALOG = helpdialog self._HELPDIALOG = helpdialog
end end
return self._HELPDIALOG return self._HELPDIALOG
@@ -132,6 +174,7 @@ function mconfdialog:resultdialog()
if not self._RESULTDIALOG then if not self._RESULTDIALOG then
local resultdialog = textdialog:new("mconfdialog.result", self:bounds(), "result") local resultdialog = textdialog:new("mconfdialog.result", self:bounds(), "result")
resultdialog:button_add("exit", "< Exit >", function (v) resultdialog:quit() end) resultdialog:button_add("exit", "< Exit >", function (v) resultdialog:quit() end)
resultdialog:option_set("scrollable", true)
self._RESULTDIALOG = resultdialog self._RESULTDIALOG = resultdialog
end end
return self._RESULTDIALOG return self._RESULTDIALOG

View File

@@ -40,18 +40,138 @@ function menuconf:init(name, bounds)
-- init configs -- init configs
self._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 end
-- on event -- on event
function menuconf:on_event(e) function menuconf:on_event(e)
-- select config
local back = false local back = false
if e.type == event.ev_keyboard then if e.type == event.ev_keyboard then
if e.key_name == "Down" 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 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 elseif e.key_name == "Enter" or e.key_name == " " then
self:_do_select() self:_do_select()
return true return true
@@ -85,54 +205,36 @@ function menuconf:on_event(e)
end end
end end
-- load configs -- load a config item
function menuconf:load(configs) function menuconf:_load_item(config, index)
-- 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)
-- init a config item view -- init a config item view
local item = button:new("menuconf.config." .. self:count(), local item = button:new("menuconf.config." .. index,
rect:new(0, self:count(), self:width(), 1), rect:new(0, index - 1, self:width(), 1),
tostring(config), tostring(config),
function (v, e) function (v, e)
self:_do_select() self:_do_select()
end) end)
-- attach this config -- attach this index and config
item:extra_set("index", index)
item:extra_set("config", config) item:extra_set("config", config)
-- attach this view -- attach this view
config._view = item config._view = item
return item
end
-- insert this config item -- notify scrolled
self:insert(item) 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 end
-- do select the current config -- do select the current config

View File

@@ -64,7 +64,7 @@ function panel:init(name, bounds)
local p = v:last() local p = v:last()
while p do 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 if p:option("mouseable") then
v:select(p) v:select(p)
return p:action_on(action.ac_on_clicked, x, y) return p:action_on(action.ac_on_clicked, x, y)

215
src/ltui/scrollbar.lua Normal file
View 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

View File

@@ -24,6 +24,7 @@ local view = require("ltui/view")
local label = require("ltui/label") local label = require("ltui/label")
local event = require("ltui/event") local event = require("ltui/event")
local curses = require("ltui/curses") local curses = require("ltui/curses")
local action = require("ltui/action")
-- define module -- define module
local textarea = textarea or label() local textarea = textarea or label()
@@ -37,9 +38,6 @@ function textarea:init(name, bounds, text)
-- mark as selectable -- mark as selectable
self:option_set("selectable", true) self:option_set("selectable", true)
-- enable progress
self:option_set("progress", true)
-- init start line -- init start line
self._STARTLINE = 0 self._STARTLINE = 0
self._LINECOUNT = 0 self._LINECOUNT = 0
@@ -59,17 +57,6 @@ function textarea:on_draw(transparent)
if strs and #strs > 0 and textattr then if strs and #strs > 0 and textattr then
self:canvas():attr(textattr):move(0, 0):putstrs(strs, self._STARTLINE + 1) self:canvas():attr(textattr):move(0, 0):putstrs(strs, self._STARTLINE + 1)
end 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 end
-- set text -- set text
@@ -80,24 +67,33 @@ function textarea:text_set(text)
return label.text_set(self, text) return label.text_set(self, text)
end end
-- is scrollable?
function textarea:scrollable()
return self._LINECOUNT > self:height()
end
-- scroll -- scroll
function textarea:scroll(lines) function textarea:scroll(lines)
if self._LINECOUNT > self:height() then if self:scrollable() then
self._STARTLINE = self._STARTLINE + lines self._STARTLINE = self._STARTLINE + lines
if self._STARTLINE < 0 then if self._STARTLINE < 0 then
self._STARTLINE = 0 self._STARTLINE = 0
end end
if self._STARTLINE > self._LINECOUNT - self:height() then local startline_end = self._LINECOUNT > self:height() and self._LINECOUNT - self:height() or self._LINECOUNT
self._STARTLINE = self._LINECOUNT - self:height() if self._STARTLINE > startline_end then
self._STARTLINE = startline_end
end end
self:action_on(action.ac_on_scrolled, self._STARTLINE / startline_end)
self:invalidate() self:invalidate()
end end
end end
-- scroll to end -- scroll to end
function textarea:scroll_to_end() function textarea:scroll_to_end()
if self._LINECOUNT > self:height() then if self:scrollable() then
self._STARTLINE = self._LINECOUNT - self:height() 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() self:invalidate()
end end
end end

View File

@@ -25,6 +25,7 @@ local event = require("ltui/event")
local dialog = require("ltui/dialog") local dialog = require("ltui/dialog")
local curses = require("ltui/curses") local curses = require("ltui/curses")
local textarea = require("ltui/textarea") local textarea = require("ltui/textarea")
local scrollbar = require("ltui/scrollbar")
local action = require("ltui/action") local action = require("ltui/action")
-- define module -- define module
@@ -36,16 +37,60 @@ function textdialog:init(name, bounds, title)
-- init window -- init window
dialog.init(self, name, bounds, title) dialog.init(self, name, bounds, title)
-- mark as scrollable, disabled by default
self:option_set("scrollable", false)
-- insert text -- insert text
self:panel():insert(self:text()) self:panel():insert(self:text())
-- insert scrollbar
self:panel():insert(self:scrollbar())
-- select buttons by default -- select buttons by default
self:panel():select(self:buttons()) self:panel():select(self:buttons())
-- on resize for panel -- on resize for panel
self:panel():action_add(action.ac_on_resized, function (v) self:panel():action_add(action.ac_on_resized, function (v)
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)) self:text():bounds_set(rect:new(0, 0, v:width(), v:height() - 1))
end
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 end
-- get text -- get text
@@ -56,6 +101,15 @@ function textdialog:text()
return self._TEXT return self._TEXT
end 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 -- on event
function textdialog:on_event(e) function textdialog:on_event(e)

View File

@@ -47,9 +47,6 @@ function textedit:init(name, bounds, text)
self:option_set("mouseable", true) self:option_set("mouseable", true)
self:action_set(action.ac_on_clicked, function () return true end) self:action_set(action.ac_on_clicked, function () return true end)
-- disable progress
self:option_set("progress", false)
-- enable multiple line -- enable multiple line
self:option_set("multiline", true) self:option_set("multiline", true)
end end

View File

@@ -76,6 +76,7 @@ function demo:dialog_help()
local dialog_help = self._DIALOG_HELP local dialog_help = self._DIALOG_HELP
if not dialog_help then if not dialog_help then
dialog_help = textdialog:new("dialog.help", rect {1, 1, self:width() - 1, self:height() - 1}, "README") 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 helptext = nil
local file = io.open("./LICENSE.md", 'r') local file = io.open("./LICENSE.md", 'r')
if file then if file then

View File

@@ -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.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.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 = {} local configs = {}
table.insert(configs, menuconf.boolean {description = "boolean config item"}) table.insert(configs, menuconf.boolean {description = "boolean config item"})
table.insert(configs, menuconf.boolean {default = true, new = false, description = {"boolean config item2", 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.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.string {value = "x86_64", description = "string config item"})
table.insert(configs, menuconf.menu {description = "menu config item", configs = configs_sub}) 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 -- init menu config dialog
self:dialog_mconf():load(configs) self:dialog_mconf():load(configs)