diff options
| author | ruki <[email protected]> | 2020-11-29 22:36:04 +0800 |
|---|---|---|
| committer | ruki <[email protected]> | 2020-11-29 22:36:04 +0800 |
| commit | e4058387bfd67c02aa0e065705655b1e4b0aafbf (patch) | |
| tree | d2dfd95343e3161a9e4dc55691e37abbca69b430 | |
| parent | d48118e840fc0be69f3d78a3894e4bf76b8d801b (diff) | |
support scrollbar
| -rw-r--r-- | tests/ui/dialog.lua | 1 | ||||
| -rw-r--r-- | tests/ui/mconfdialog.lua | 23 | ||||
| -rw-r--r-- | xmake/core/ui/action.lua | 1 | ||||
| -rw-r--r-- | xmake/core/ui/boxdialog.lua | 1 | ||||
| -rw-r--r-- | xmake/core/ui/choicebox.lua | 173 | ||||
| -rw-r--r-- | xmake/core/ui/choicedialog.lua | 43 | ||||
| -rw-r--r-- | xmake/core/ui/inputdialog.lua | 1 | ||||
| -rw-r--r-- | xmake/core/ui/mconfdialog.lua | 45 | ||||
| -rw-r--r-- | xmake/core/ui/menuconf.lua | 190 | ||||
| -rw-r--r-- | xmake/core/ui/panel.lua | 2 | ||||
| -rw-r--r-- | xmake/core/ui/scrollbar.lua | 215 | ||||
| -rw-r--r-- | xmake/core/ui/textarea.lua | 34 | ||||
| -rw-r--r-- | xmake/core/ui/textdialog.lua | 70 | ||||
| -rw-r--r-- | xmake/core/ui/textedit.lua | 3 |
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 |
