add ui views
This commit is contained in:
@@ -2352,7 +2352,6 @@ __export int luaopen_ltui_curses (lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* initialize the character map table with the known values after
|
||||
** curses initialization (for ACS_xxx values) */
|
||||
static void init_ascii_map()
|
||||
|
@@ -1,8 +1,8 @@
|
||||
-- add target
|
||||
target("lcurses")
|
||||
|
||||
-- make as a static library
|
||||
set_kind("static")
|
||||
-- only make objects
|
||||
set_kind("object")
|
||||
|
||||
-- add deps
|
||||
if is_plat("windows") then
|
||||
|
@@ -1,8 +1,8 @@
|
||||
-- add target
|
||||
target("pdcurses")
|
||||
|
||||
-- make as a static library
|
||||
set_kind("static")
|
||||
-- only make objects
|
||||
set_kind("object")
|
||||
|
||||
-- add the common source files
|
||||
add_files("**.c")
|
||||
|
@@ -31,12 +31,6 @@ end
|
||||
-- add requires
|
||||
add_requires("luajit")
|
||||
|
||||
-- add projects
|
||||
includes("lcurses")
|
||||
if is_plat("windows") then
|
||||
includes("pdcurses")
|
||||
end
|
||||
|
||||
-- add target
|
||||
target("ltui")
|
||||
|
||||
@@ -45,3 +39,12 @@ target("ltui")
|
||||
|
||||
-- add deps
|
||||
add_deps("lcurses")
|
||||
|
||||
-- set target directory
|
||||
set_targetdir("$(buildir)")
|
||||
|
||||
-- add projects
|
||||
includes("lcurses")
|
||||
if is_plat("windows") then
|
||||
includes("pdcurses")
|
||||
end
|
||||
|
53
src/ltui/action.lua
Normal file
53
src/ltui/action.lua
Normal file
@@ -0,0 +1,53 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file action.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local object = require("ltui/object")
|
||||
|
||||
-- define module
|
||||
local action = action or object { }
|
||||
|
||||
-- register action types
|
||||
function action:register(tag, ...)
|
||||
local base = self[tag] or 0
|
||||
local enums = {...}
|
||||
local n = #enums
|
||||
for i = 1, n do
|
||||
self[enums[i]] = i + base
|
||||
end
|
||||
self[tag] = base + n
|
||||
end
|
||||
|
||||
-- register action enums
|
||||
action:register("ac_max",
|
||||
"ac_on_text_changed",
|
||||
"ac_on_selected",
|
||||
"ac_on_enter",
|
||||
"ac_on_load",
|
||||
"ac_on_save",
|
||||
"ac_on_exit")
|
||||
|
||||
-- return module
|
||||
return action
|
124
src/ltui/application.lua
Normal file
124
src/ltui/application.lua
Normal file
@@ -0,0 +1,124 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file application.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local os = require("ltui/base/os")
|
||||
local log = require("ltui/base/log")
|
||||
local rect = require("ltui/rect")
|
||||
local event = require("ltui/event")
|
||||
local curses = require("ltui/curses")
|
||||
local program = require("ltui/program")
|
||||
local desktop = require("ltui/desktop")
|
||||
local menubar = require("ltui/menubar")
|
||||
local statusbar = require("ltui/statusbar")
|
||||
|
||||
-- define module
|
||||
local application = application or program()
|
||||
|
||||
-- init application
|
||||
function application:init(name, argv)
|
||||
|
||||
-- init log
|
||||
log:clear()
|
||||
-- log:enable(false)
|
||||
|
||||
-- trace
|
||||
log:print("<application: %s>: init ..", name)
|
||||
|
||||
-- init program
|
||||
program.init(self, name, argv)
|
||||
|
||||
-- trace
|
||||
log:print("<application: %s>: init ok", name)
|
||||
end
|
||||
|
||||
-- exit application
|
||||
function application:exit()
|
||||
|
||||
-- exit program
|
||||
program.exit(self)
|
||||
|
||||
-- flush log
|
||||
log:flush()
|
||||
end
|
||||
|
||||
-- get menubar
|
||||
function application:menubar()
|
||||
if not self._MENUBAR then
|
||||
self._MENUBAR = menubar:new("menubar", rect{0, 0, self:width(), 1})
|
||||
end
|
||||
return self._MENUBAR
|
||||
end
|
||||
|
||||
-- get desktop
|
||||
function application:desktop()
|
||||
if not self._DESKTOP then
|
||||
self._DESKTOP = desktop:new("desktop", rect{0, 1, self:width(), self:height() - 1})
|
||||
end
|
||||
return self._DESKTOP
|
||||
end
|
||||
|
||||
-- get statusbar
|
||||
function application:statusbar()
|
||||
if not self._STATUSBAR then
|
||||
self._STATUSBAR = statusbar:new("statusbar", rect{0, self:height() - 1, self:width(), self:height()})
|
||||
end
|
||||
return self._STATUSBAR
|
||||
end
|
||||
|
||||
-- on event
|
||||
function application:event_on(e)
|
||||
program.event_on(self, e)
|
||||
end
|
||||
|
||||
-- run application
|
||||
function application:run(...)
|
||||
|
||||
-- init runner
|
||||
local argv = {...}
|
||||
local runner = function ()
|
||||
|
||||
-- new an application
|
||||
local app = self:new(argv)
|
||||
if app then
|
||||
app:loop()
|
||||
app:exit()
|
||||
end
|
||||
end
|
||||
|
||||
-- run application
|
||||
local ok, errors = xpcall(runner, debug.traceback)
|
||||
|
||||
-- exit curses
|
||||
if not ok then
|
||||
if not curses.isdone() then
|
||||
curses.done()
|
||||
end
|
||||
log:flush()
|
||||
os.raise(errors)
|
||||
end
|
||||
end
|
||||
|
||||
-- return module
|
||||
return application
|
213
src/ltui/base/dlist.lua
Normal file
213
src/ltui/base/dlist.lua
Normal file
@@ -0,0 +1,213 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file dlist.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local object = require("ltui/object")
|
||||
|
||||
-- define module
|
||||
local dlist = dlist or object { _init = {"_length"} } {0}
|
||||
|
||||
-- clear list
|
||||
function dlist:clear()
|
||||
self._length = 0
|
||||
self._first = nil
|
||||
self._last = nil
|
||||
end
|
||||
|
||||
-- push item to tail
|
||||
function dlist:push(t)
|
||||
assert(t)
|
||||
if self._last then
|
||||
self._last._next = t
|
||||
t._prev = self._last
|
||||
self._last = t
|
||||
else
|
||||
self._first = t
|
||||
self._last = t
|
||||
end
|
||||
self._length = self._length + 1
|
||||
end
|
||||
|
||||
-- insert item after the given item
|
||||
function dlist:insert(t, after)
|
||||
assert(t)
|
||||
if not after then
|
||||
return self:push(t)
|
||||
end
|
||||
assert(t ~= after)
|
||||
if after._next then
|
||||
after._next._prev = t
|
||||
t._next = after._next
|
||||
else
|
||||
self._last = t
|
||||
end
|
||||
t._prev = after
|
||||
after._next = t
|
||||
self._length = self._length + 1
|
||||
end
|
||||
|
||||
-- pop item from tail
|
||||
function dlist:pop()
|
||||
if not self._last then return end
|
||||
local t = self._last
|
||||
if t._prev then
|
||||
t._prev._next = nil
|
||||
self._last = t._prev
|
||||
t._prev = nil
|
||||
else
|
||||
self._first = nil
|
||||
self._last = nil
|
||||
end
|
||||
self._length = self._length - 1
|
||||
return t
|
||||
end
|
||||
|
||||
-- shift item: 1 2 3 <- 2 3
|
||||
function dlist:shift()
|
||||
if not self._first then return end
|
||||
local t = self._first
|
||||
if t._next then
|
||||
t._next._prev = nil
|
||||
self._first = t._next
|
||||
t._next = nil
|
||||
else
|
||||
self._first = nil
|
||||
self._last = nil
|
||||
end
|
||||
self._length = self._length - 1
|
||||
return t
|
||||
end
|
||||
|
||||
-- unshift item: 1 2 -> t 1 2
|
||||
function dlist:unshift(t)
|
||||
assert(t)
|
||||
if self._first then
|
||||
self._first._prev = t
|
||||
t._next = self._first
|
||||
self._first = t
|
||||
else
|
||||
self._first = t
|
||||
self._last = t
|
||||
end
|
||||
self._length = self._length + 1
|
||||
end
|
||||
|
||||
-- remove item
|
||||
function dlist:remove(t)
|
||||
assert(t)
|
||||
if t._next then
|
||||
if t._prev then
|
||||
t._next._prev = t._prev
|
||||
t._prev._next = t._next
|
||||
else
|
||||
assert(t == self._first)
|
||||
t._next._prev = nil
|
||||
self._first = t._next
|
||||
end
|
||||
elseif t._prev then
|
||||
assert(t == self._last)
|
||||
t._prev._next = nil
|
||||
self._last = t._prev
|
||||
else
|
||||
assert(t == self._first and t == self._last)
|
||||
self._first = nil
|
||||
self._last = nil
|
||||
end
|
||||
t._next = nil
|
||||
t._prev = nil
|
||||
self._length = self._length - 1
|
||||
return t
|
||||
end
|
||||
|
||||
-- get first item
|
||||
function dlist:first()
|
||||
return self._first
|
||||
end
|
||||
|
||||
-- get last item
|
||||
function dlist:last()
|
||||
return self._last
|
||||
end
|
||||
|
||||
-- get next item
|
||||
function dlist:next(last)
|
||||
if last then
|
||||
return last._next
|
||||
else
|
||||
return self._first
|
||||
end
|
||||
end
|
||||
|
||||
-- get the previous item
|
||||
function dlist:prev(last)
|
||||
if last then
|
||||
return last._prev
|
||||
else
|
||||
return self._last
|
||||
end
|
||||
end
|
||||
|
||||
-- get list size
|
||||
function dlist:size()
|
||||
return self._length
|
||||
end
|
||||
|
||||
-- is empty?
|
||||
function dlist:empty()
|
||||
return self:size() == 0
|
||||
end
|
||||
|
||||
-- get items
|
||||
--
|
||||
-- .e.g
|
||||
--
|
||||
-- for item in dlist:items() do
|
||||
-- print(item)
|
||||
-- end
|
||||
--
|
||||
function dlist:items()
|
||||
|
||||
-- init iterator
|
||||
local iter = function (list, item)
|
||||
return list:next(item)
|
||||
end
|
||||
|
||||
-- return iterator and initialized state
|
||||
return iter, self, nil
|
||||
end
|
||||
|
||||
-- get reverse items
|
||||
function dlist:ritems()
|
||||
|
||||
-- init iterator
|
||||
local iter = function (list, item)
|
||||
return list:prev(item)
|
||||
end
|
||||
|
||||
-- return iterator and initialized state
|
||||
return iter, self, nil
|
||||
end
|
||||
|
||||
-- return module: dlist
|
||||
return dlist
|
144
src/ltui/base/log.lua
Normal file
144
src/ltui/base/log.lua
Normal file
@@ -0,0 +1,144 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--o
|
||||
-- @author ruki
|
||||
-- @file log.lua
|
||||
--
|
||||
|
||||
-- define module: log
|
||||
local log = log or {}
|
||||
|
||||
-- get the log file
|
||||
function log:file()
|
||||
|
||||
-- disable?
|
||||
if self._ENABLE ~= nil and not self._ENABLE then
|
||||
return
|
||||
end
|
||||
|
||||
-- get the output file
|
||||
if self._FILE == nil then
|
||||
local outputfile = self:outputfile()
|
||||
if outputfile then
|
||||
|
||||
-- get directory
|
||||
local i = outputfile:find_last("[/\\]")
|
||||
if i then
|
||||
if i > 1 then i = i - 1 end
|
||||
dir = outputfile:sub(1, i)
|
||||
else
|
||||
dir = "."
|
||||
end
|
||||
|
||||
-- ensure the directory
|
||||
if not os.isdir(dir) then
|
||||
os.mkdir(dir)
|
||||
end
|
||||
|
||||
-- open the log file
|
||||
self._FILE = io.open(outputfile, 'w+')
|
||||
end
|
||||
self._FILE = self._FILE or false
|
||||
end
|
||||
return self._FILE
|
||||
end
|
||||
|
||||
-- get the output file
|
||||
function log:outputfile()
|
||||
if self._LOGFILE == nil then
|
||||
self._LOGFILE = os.getenv("LTUI_LOGFILE") or false
|
||||
end
|
||||
return self._LOGFILE
|
||||
end
|
||||
|
||||
-- clear log
|
||||
function log:clear(state)
|
||||
if os.isfile(self:outputfile()) then
|
||||
io.writefile(self:outputfile(), "")
|
||||
end
|
||||
end
|
||||
|
||||
-- enable log
|
||||
function log:enable(state)
|
||||
self._ENABLE = state
|
||||
end
|
||||
|
||||
-- flush log to file
|
||||
function log:flush()
|
||||
local file = self:file()
|
||||
if file then
|
||||
io.flush(file)
|
||||
end
|
||||
end
|
||||
|
||||
-- close the log file
|
||||
function log:close()
|
||||
local file = self:file()
|
||||
if file then
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
-- print log to the log file
|
||||
function log:print(...)
|
||||
local file = self:file()
|
||||
if file then
|
||||
file:write(string.format(...) .. "\n")
|
||||
end
|
||||
end
|
||||
|
||||
-- print variables to the log file
|
||||
function log:printv(...)
|
||||
local file = self:file()
|
||||
if file then
|
||||
local values = {...}
|
||||
for i, v in ipairs(values) do
|
||||
-- dump basic type
|
||||
if type(v) == "string" or type(v) == "boolean" or type(v) == "number" then
|
||||
file:write(tostring(v))
|
||||
else
|
||||
file:write("<" .. tostring(v) .. ">")
|
||||
end
|
||||
if i ~= #values then
|
||||
file:write(" ")
|
||||
end
|
||||
end
|
||||
file:write('\n')
|
||||
end
|
||||
end
|
||||
|
||||
-- printf log to the log file
|
||||
function log:printf(...)
|
||||
local file = self:file()
|
||||
if file then
|
||||
file:write(string.format(...))
|
||||
end
|
||||
end
|
||||
|
||||
-- write log the log file
|
||||
function log:write(...)
|
||||
local file = self:file()
|
||||
if file then
|
||||
file:write(...)
|
||||
end
|
||||
end
|
||||
|
||||
-- return module: log
|
||||
return log
|
109
src/ltui/base/os.lua
Normal file
109
src/ltui/base/os.lua
Normal file
@@ -0,0 +1,109 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file os.lua
|
||||
--
|
||||
|
||||
-- define module
|
||||
local os = os or {}
|
||||
|
||||
-- load modules
|
||||
local string = require("ltui/base/string")
|
||||
|
||||
-- raise an exception and abort the current script
|
||||
--
|
||||
-- the parent function will capture it if we uses pcall or xpcall
|
||||
--
|
||||
function os.raise(msg, ...)
|
||||
|
||||
-- raise it
|
||||
if msg then
|
||||
error(string.tryformat(msg, ...))
|
||||
else
|
||||
error()
|
||||
end
|
||||
end
|
||||
|
||||
-- run program
|
||||
function os.run(cmd, ...)
|
||||
return os.execute(string.tryformat(cmd, ...))
|
||||
end
|
||||
|
||||
-- run program and get io output
|
||||
function os.iorun(cmd, ...)
|
||||
local ok = false
|
||||
local outs = nil
|
||||
local file = io.popen(string.tryformat(cmd, ...), "r")
|
||||
if file then
|
||||
outs = file:read("*a"):trim()
|
||||
file:close()
|
||||
ok = true
|
||||
end
|
||||
return ok, outs
|
||||
end
|
||||
|
||||
-- get host name
|
||||
function os.host()
|
||||
if os._HOST == nil then
|
||||
local ok, result = os.iorun("uname")
|
||||
if ok then
|
||||
if result:lower():find("linux", 1, true) then
|
||||
os._HOST = "linux"
|
||||
elseif result:lower():find("darwin", 1, true) then
|
||||
os._HOST = "macosx"
|
||||
end
|
||||
elseif os.run("cmd /c ver") == 0 then
|
||||
os._HOST = "windows"
|
||||
end
|
||||
end
|
||||
return os._HOST
|
||||
end
|
||||
|
||||
-- read string data from pasteboard
|
||||
function os.pbpaste()
|
||||
if os.host() == "macosx" then
|
||||
local ok, result = os.iorun("pbpaste")
|
||||
if ok then
|
||||
return result
|
||||
end
|
||||
elseif os.host() == "linux" then
|
||||
local ok, result = os.iorun("xsel --clipboard --output")
|
||||
if ok then
|
||||
return result
|
||||
end
|
||||
else
|
||||
-- TODO
|
||||
end
|
||||
end
|
||||
|
||||
-- copy string data to pasteboard
|
||||
function os.pbcopy(data)
|
||||
if os.host() == "macosx" then
|
||||
os.run("bash -c \"echo '" .. data .. "' | pbcopy\"")
|
||||
elseif os.host() == "linux" then
|
||||
os.run("bash -c \"echo '" .. data .. "' | xsel --clipboard --input\"")
|
||||
else
|
||||
-- TODO
|
||||
end
|
||||
end
|
||||
|
||||
-- return module
|
||||
return os
|
127
src/ltui/base/path.lua
Normal file
127
src/ltui/base/path.lua
Normal file
@@ -0,0 +1,127 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file path.lua
|
||||
--
|
||||
|
||||
-- define module: path
|
||||
local path = path or {}
|
||||
|
||||
-- load modules
|
||||
local string = require("ltui/base/string")
|
||||
|
||||
-- get the directory of the path
|
||||
function path.directory(p)
|
||||
local i = p:find_last("[/\\]")
|
||||
if i then
|
||||
if i > 1 then i = i - 1 end
|
||||
return p:sub(1, i)
|
||||
else
|
||||
return "."
|
||||
end
|
||||
end
|
||||
|
||||
-- get the filename of the path
|
||||
function path.filename(p)
|
||||
local i = p:find_last("[/\\]")
|
||||
if i then
|
||||
return p:sub(i + 1)
|
||||
else
|
||||
return p
|
||||
end
|
||||
end
|
||||
|
||||
-- get the basename of the path
|
||||
function path.basename(p)
|
||||
local name = path.filename(p)
|
||||
local i = name:find_last(".", true)
|
||||
if i then
|
||||
return name:sub(1, i - 1)
|
||||
else
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
-- get the file extension of the path: .xxx
|
||||
function path.extension(p)
|
||||
|
||||
-- check
|
||||
assert(p)
|
||||
|
||||
-- get extension
|
||||
local i = p:find_last(".", true)
|
||||
if i then
|
||||
return p:sub(i)
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
-- join path
|
||||
function path.join(p, ...)
|
||||
|
||||
-- check
|
||||
assert(p)
|
||||
|
||||
-- join them
|
||||
for _, name in ipairs({...}) do
|
||||
p = p .. "/" .. name
|
||||
end
|
||||
|
||||
-- translate path
|
||||
return path.translate(p)
|
||||
end
|
||||
|
||||
-- split path by the separator
|
||||
function path.split(p)
|
||||
return p:split("/\\")
|
||||
end
|
||||
|
||||
-- get the path seperator
|
||||
function path.seperator()
|
||||
return xmake._HOST == "windows" and '\\' or '/'
|
||||
end
|
||||
|
||||
-- the last character is the path seperator?
|
||||
function path.islastsep(p)
|
||||
local sep = p:sub(#p, #p)
|
||||
return xmake._HOST == "windows" and (sep == '\\' or sep == '/') or (sep == '/')
|
||||
end
|
||||
|
||||
-- convert path pattern to a lua pattern
|
||||
function path.pattern(pattern)
|
||||
|
||||
-- translate wildcards, .e.g *, **
|
||||
pattern = pattern:gsub("([%+%.%-%^%$%(%)%%])", "%%%1")
|
||||
pattern = pattern:gsub("%*%*", "\001")
|
||||
pattern = pattern:gsub("%*", "\002")
|
||||
pattern = pattern:gsub("\001", ".*")
|
||||
pattern = pattern:gsub("\002", "[^/]*")
|
||||
|
||||
-- case-insensitive filesystem?
|
||||
if not os.fscase() then
|
||||
pattern = string.ipattern(pattern, true)
|
||||
end
|
||||
return pattern
|
||||
end
|
||||
|
||||
-- return module: path
|
||||
return path
|
215
src/ltui/base/string.lua
Normal file
215
src/ltui/base/string.lua
Normal file
@@ -0,0 +1,215 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file string.lua
|
||||
--
|
||||
|
||||
-- define module: string
|
||||
local string = string or {}
|
||||
|
||||
-- find the last substring with the given pattern
|
||||
function string:find_last(pattern, plain)
|
||||
|
||||
-- find the last substring
|
||||
local curr = 0
|
||||
repeat
|
||||
local next = self:find(pattern, curr + 1, plain)
|
||||
if next then
|
||||
curr = next
|
||||
end
|
||||
until (not next)
|
||||
|
||||
-- found?
|
||||
if curr > 0 then
|
||||
return curr
|
||||
end
|
||||
end
|
||||
|
||||
-- split string with the given characters
|
||||
--
|
||||
-- ("1\n\n2\n3"):split('\n') => 1, 2, 3
|
||||
-- ("1\n\n2\n3"):split('\n', true) => 1, , 2, 3
|
||||
--
|
||||
function string:split(delimiter, strict)
|
||||
local result = {}
|
||||
if strict then
|
||||
for match in (self .. delimiter):gmatch("(.-)" .. delimiter) do
|
||||
table.insert(result, match)
|
||||
end
|
||||
else
|
||||
self:gsub("[^" .. delimiter .."]+", function(v) table.insert(result, v) end)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- trim the spaces
|
||||
function string:trim()
|
||||
return (self:gsub("^%s*(.-)%s*$", "%1"))
|
||||
end
|
||||
|
||||
-- trim the left spaces
|
||||
function string:ltrim()
|
||||
return (self:gsub("^%s*", ""))
|
||||
end
|
||||
|
||||
-- trim the right spaces
|
||||
function string:rtrim()
|
||||
local n = #self
|
||||
while n > 0 and s:find("^%s", n) do n = n - 1 end
|
||||
return self:sub(1, n)
|
||||
end
|
||||
|
||||
-- append a substring with a given separator
|
||||
function string:append(substr, separator)
|
||||
|
||||
-- check
|
||||
assert(self)
|
||||
|
||||
-- not substr? return self
|
||||
if not substr then
|
||||
return self
|
||||
end
|
||||
|
||||
-- append it
|
||||
local s = self
|
||||
if #s == 0 then
|
||||
s = substr
|
||||
else
|
||||
s = string.format("%s%s%s", s, separator or "", substr)
|
||||
end
|
||||
|
||||
-- ok
|
||||
return s
|
||||
end
|
||||
|
||||
-- encode: ' ', '=', '\"', '<'
|
||||
function string:encode()
|
||||
|
||||
-- null?
|
||||
if self == nil then return end
|
||||
|
||||
-- done
|
||||
return (self:gsub("[%s=\"<]", function (w) return string.format("%%%x", w:byte()) end))
|
||||
end
|
||||
|
||||
-- decode: ' ', '=', '\"'
|
||||
function string:decode()
|
||||
|
||||
-- null?
|
||||
if self == nil then return end
|
||||
|
||||
-- done
|
||||
return (self:gsub("%%(%x%x)", function (w) return string.char(tonumber(w, 16)) end))
|
||||
end
|
||||
|
||||
-- join array to string with the given separator
|
||||
function string.join(items, sep)
|
||||
|
||||
-- join them
|
||||
local str = ""
|
||||
local index = 1
|
||||
local count = #items
|
||||
for _, item in ipairs(items) do
|
||||
str = str .. item
|
||||
if index ~= count and sep ~= nil then
|
||||
str = str .. sep
|
||||
end
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
-- ok?
|
||||
return str
|
||||
end
|
||||
|
||||
-- try to format
|
||||
function string.tryformat(format, ...)
|
||||
|
||||
-- attempt to format it
|
||||
local ok, str = pcall(string.format, format, ...)
|
||||
if ok then
|
||||
return str
|
||||
else
|
||||
return format
|
||||
end
|
||||
end
|
||||
|
||||
-- case-insensitive pattern-matching
|
||||
--
|
||||
-- print(("src/dadasd.C"):match(string.ipattern("sR[cd]/.*%.c", true)))
|
||||
-- print(("src/dadasd.C"):match(string.ipattern("src/.*%.c", true)))
|
||||
--
|
||||
-- print(string.ipattern("sR[cd]/.*%.c"))
|
||||
-- [sS][rR][cd]/.*%.[cC]
|
||||
--
|
||||
-- print(string.ipattern("sR[cd]/.*%.c", true))
|
||||
-- [sS][rR][cCdD]/.*%.[cC]
|
||||
--
|
||||
function string.ipattern(pattern, brackets)
|
||||
local tmp = {}
|
||||
local i = 1
|
||||
while i <= #pattern do
|
||||
|
||||
-- get current charactor
|
||||
local char = pattern:sub(i, i)
|
||||
|
||||
-- escape?
|
||||
if char == '%' then
|
||||
tmp[#tmp + 1] = char
|
||||
i = i + 1
|
||||
char = pattern:sub(i,i)
|
||||
tmp[#tmp + 1] = char
|
||||
|
||||
-- '%bxy'? add next 2 chars
|
||||
if char == 'b' then
|
||||
tmp[#tmp + 1] = pattern:sub(i + 1, i + 2)
|
||||
i = i + 2
|
||||
end
|
||||
-- brackets?
|
||||
elseif char == '[' then
|
||||
tmp[#tmp + 1] = char
|
||||
i = i + 1
|
||||
while i <= #pattern do
|
||||
char = pattern:sub(i, i)
|
||||
if char == '%' then
|
||||
tmp[#tmp + 1] = char
|
||||
tmp[#tmp + 1] = pattern:sub(i + 1, i + 1)
|
||||
i = i + 1
|
||||
elseif char:match("%a") then
|
||||
tmp[#tmp + 1] = not brackets and char or char:lower() .. char:upper()
|
||||
else
|
||||
tmp[#tmp + 1] = char
|
||||
end
|
||||
if char == ']' then break end
|
||||
i = i + 1
|
||||
end
|
||||
-- letter, [aA]
|
||||
elseif char:match("%a") then
|
||||
tmp[#tmp + 1] = '[' .. char:lower() .. char:upper() .. ']'
|
||||
else
|
||||
tmp[#tmp + 1] = char
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return table.concat(tmp)
|
||||
end
|
||||
|
||||
-- return module: string
|
||||
return string
|
344
src/ltui/base/table.lua
Normal file
344
src/ltui/base/table.lua
Normal file
@@ -0,0 +1,344 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file table.lua
|
||||
--
|
||||
|
||||
-- define module: table
|
||||
local table = table or {}
|
||||
|
||||
-- clear the table
|
||||
function table.clear(self)
|
||||
for k in next, self do
|
||||
rawset(self, k, nil)
|
||||
end
|
||||
end
|
||||
|
||||
-- join all objects and tables
|
||||
function table.join(...)
|
||||
|
||||
local result = {}
|
||||
for _, t in ipairs({...}) do
|
||||
if type(t) == "table" then
|
||||
for k, v in pairs(t) do
|
||||
if type(k) == "number" then table.insert(result, v)
|
||||
else result[k] = v end
|
||||
end
|
||||
else
|
||||
table.insert(result, t)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- join all objects and tables to self
|
||||
function table.join2(self, ...)
|
||||
|
||||
for _, t in ipairs({...}) do
|
||||
if type(t) == "table" then
|
||||
for k, v in pairs(t) do
|
||||
if type(k) == "number" then table.insert(self, v)
|
||||
else self[k] = v end
|
||||
end
|
||||
else
|
||||
table.insert(self, t)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-- append all objects to array
|
||||
function table.append(array, ...)
|
||||
for _, value in ipairs({...}) do
|
||||
table.insert(array, value)
|
||||
end
|
||||
return array
|
||||
end
|
||||
|
||||
-- copy the table to self
|
||||
function table.copy(copied)
|
||||
|
||||
-- init it
|
||||
copied = copied or {}
|
||||
|
||||
-- copy it
|
||||
local result = {}
|
||||
for k, v in pairs(table.wrap(copied)) do
|
||||
result[k] = v
|
||||
end
|
||||
|
||||
-- ok
|
||||
return result
|
||||
end
|
||||
|
||||
-- copy the table to self
|
||||
function table.copy2(self, copied)
|
||||
|
||||
-- check
|
||||
assert(self)
|
||||
|
||||
-- init it
|
||||
copied = copied or {}
|
||||
|
||||
-- clear self first
|
||||
table.clear(self)
|
||||
|
||||
-- copy it
|
||||
for k, v in pairs(table.wrap(copied)) do
|
||||
self[k] = v
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- inherit interfaces and create a new instance
|
||||
function table.inherit(...)
|
||||
|
||||
-- init instance
|
||||
local classes = {...}
|
||||
local instance = {}
|
||||
local metainfo = {}
|
||||
for _, clasz in ipairs(classes) do
|
||||
for k, v in pairs(clasz) do
|
||||
if type(v) == "function" then
|
||||
if k:startswith("__") then
|
||||
if metainfo[k] == nil then
|
||||
metainfo[k] = v
|
||||
end
|
||||
else
|
||||
if instance[k] == nil then
|
||||
instance[k] = v
|
||||
else
|
||||
instance["_super_" .. k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
setmetatable(instance, metainfo)
|
||||
|
||||
-- ok?
|
||||
return instance
|
||||
end
|
||||
|
||||
-- inherit interfaces from the given class
|
||||
function table.inherit2(self, ...)
|
||||
|
||||
-- check
|
||||
assert(self)
|
||||
|
||||
-- init instance
|
||||
local classes = {...}
|
||||
local metainfo = getmetatable(self) or {}
|
||||
for _, clasz in ipairs(classes) do
|
||||
for k, v in pairs(clasz) do
|
||||
if type(v) == "function" then
|
||||
if k:startswith("__") then
|
||||
if metainfo[k] == nil then
|
||||
metainfo[k] = v
|
||||
end
|
||||
else
|
||||
if self[k] == nil then
|
||||
self[k] = v
|
||||
else
|
||||
self["_super_" .. k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ok?
|
||||
return self
|
||||
end
|
||||
|
||||
-- slice table array
|
||||
function table.slice(self, first, last, step)
|
||||
|
||||
-- slice it
|
||||
local sliced = {}
|
||||
for i = first or 1, last or #self, step or 1 do
|
||||
sliced[#sliced + 1] = self[i]
|
||||
end
|
||||
return sliced
|
||||
end
|
||||
|
||||
-- is array?
|
||||
function table.is_array(array)
|
||||
return type(array) == "table" and array[1] ~= nil
|
||||
end
|
||||
|
||||
-- is dictionary?
|
||||
function table.is_dictionary(dict)
|
||||
return type(dict) == "table" and dict[1] == nil
|
||||
end
|
||||
|
||||
-- dump it with the level
|
||||
function table._dump(self, exclude, level)
|
||||
|
||||
-- dump basic type
|
||||
if type(self) == "string" or type(self) == "boolean" or type(self) == "number" then
|
||||
io.write(tostring(self))
|
||||
elseif type(self) == "table" and (getmetatable(self) or {}).__tostring then
|
||||
io.write(tostring(self))
|
||||
-- dump table
|
||||
elseif type(self) == "table" then
|
||||
|
||||
-- dump head
|
||||
io.write("\n")
|
||||
for l = 1, level do
|
||||
io.write(" ")
|
||||
end
|
||||
io.write("{\n")
|
||||
|
||||
-- dump body
|
||||
local i = 0
|
||||
for k, v in pairs(self) do
|
||||
|
||||
-- exclude some keys
|
||||
if not exclude or type(k) ~= "string" or not k:find(exclude) then
|
||||
|
||||
-- dump spaces and separator
|
||||
for l = 1, level do
|
||||
io.write(" ")
|
||||
end
|
||||
|
||||
if i == 0 then
|
||||
io.write(" ")
|
||||
else
|
||||
io.write(", ")
|
||||
end
|
||||
|
||||
-- dump key
|
||||
if type(k) == "string" then
|
||||
io.write(k, " = ")
|
||||
end
|
||||
|
||||
-- dump value
|
||||
table._dump(v, exclude, level + 1)
|
||||
|
||||
-- dump newline
|
||||
io.write("\n")
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- dump tail
|
||||
for l = 1, level do
|
||||
io.write(" ")
|
||||
end
|
||||
io.write("}\n")
|
||||
elseif self ~= nil then
|
||||
io.write("<" .. tostring(self) .. ">")
|
||||
else
|
||||
io.write("nil")
|
||||
end
|
||||
end
|
||||
|
||||
-- dump it
|
||||
function table.dump(self, exclude, prefix)
|
||||
|
||||
-- dump prefix
|
||||
if prefix then
|
||||
io.write(prefix)
|
||||
end
|
||||
|
||||
-- dump it
|
||||
table._dump(self, exclude, 0)
|
||||
|
||||
-- end
|
||||
print("")
|
||||
|
||||
-- return it
|
||||
return self
|
||||
end
|
||||
|
||||
-- unwrap object if be only one
|
||||
function table.unwrap(object)
|
||||
if type(object) == "table" then
|
||||
if #object == 1 then
|
||||
return object[1]
|
||||
end
|
||||
end
|
||||
return object
|
||||
end
|
||||
|
||||
-- wrap object to table
|
||||
function table.wrap(object)
|
||||
|
||||
-- no object?
|
||||
if nil == object then
|
||||
return {}
|
||||
end
|
||||
|
||||
-- wrap it if not table
|
||||
if type(object) ~= "table" then
|
||||
return {object}
|
||||
end
|
||||
|
||||
-- ok
|
||||
return object
|
||||
end
|
||||
|
||||
-- remove repeat from the given array
|
||||
function table.unique(array, barrier)
|
||||
|
||||
-- remove repeat
|
||||
if type(array) == "table" then
|
||||
|
||||
-- not only one?
|
||||
if table.getn(array) ~= 1 then
|
||||
|
||||
-- done
|
||||
local exists = {}
|
||||
local unique = {}
|
||||
for _, v in ipairs(array) do
|
||||
|
||||
-- exists barrier? clear the current existed items
|
||||
if barrier and barrier(v) then
|
||||
exists = {}
|
||||
end
|
||||
|
||||
-- add unique item
|
||||
if type(v) == "string" then
|
||||
if not exists[v] then
|
||||
exists[v] = true
|
||||
table.insert(unique, v)
|
||||
end
|
||||
else
|
||||
local key = "\"" .. tostring(v) .. "\""
|
||||
if not exists[key] then
|
||||
exists[key] = true
|
||||
table.insert(unique, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- update it
|
||||
array = unique
|
||||
end
|
||||
end
|
||||
|
||||
-- ok
|
||||
return array
|
||||
end
|
||||
|
||||
-- return module: table
|
||||
return table
|
109
src/ltui/border.lua
Normal file
109
src/ltui/border.lua
Normal file
@@ -0,0 +1,109 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file border.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local rect = require("ltui/rect")
|
||||
local view = require("ltui/view")
|
||||
local label = require("ltui/label")
|
||||
local curses = require("ltui/curses")
|
||||
|
||||
-- define module
|
||||
local border = border or view()
|
||||
|
||||
-- init border
|
||||
function border:init(name, bounds)
|
||||
|
||||
-- init view
|
||||
view.init(self, name, bounds)
|
||||
|
||||
-- check bounds
|
||||
assert(self:width() > 2 and self:height() > 2, string.format("%s: too small!", self))
|
||||
end
|
||||
|
||||
-- draw border
|
||||
function border:draw(transparent)
|
||||
|
||||
-- draw background (transparent)
|
||||
view.draw(self, true)
|
||||
|
||||
-- get corner attribute
|
||||
local cornerattr = self:cornerattr()
|
||||
|
||||
-- the left-upper attribute
|
||||
local attr_ul = curses.color_pair(cornerattr[1], self:background())
|
||||
if self:background() == cornerattr[1] then
|
||||
attr_ul = {attr_ul, "standout"}
|
||||
end
|
||||
|
||||
-- the right-lower attribute
|
||||
local attr_rl = curses.color_pair(cornerattr[2], self:background())
|
||||
if self:background() == cornerattr[2] then
|
||||
attr_rl = {attr_rl, "standout"}
|
||||
end
|
||||
|
||||
-- the border characters
|
||||
-- @note acs character will use 2 width on borders (pdcurses), so we use acsii characters instead of them.
|
||||
local iswin = os.host() == "windows"
|
||||
local hline = iswin and '-' or "hline"
|
||||
local vline = iswin and '|' or "vline"
|
||||
local ulcorner = iswin and ' ' or "ulcorner"
|
||||
local llcorner = iswin and ' ' or "llcorner"
|
||||
local urcorner = iswin and ' ' or "urcorner"
|
||||
local lrcorner = iswin and ' ' or "lrcorner"
|
||||
|
||||
-- draw left and top border
|
||||
self:canvas():attr(attr_ul)
|
||||
self:canvas():move(0, 0):putchar(hline, self:width())
|
||||
self:canvas():move(0, 0):putchar(ulcorner)
|
||||
self:canvas():move(0, 1):putchar(vline, self:height() - 1, true)
|
||||
self:canvas():move(0, self:height() - 1):putchar(llcorner)
|
||||
|
||||
-- draw bottom and right border
|
||||
self:canvas():attr(attr_rl)
|
||||
self:canvas():move(1, self:height() - 1):putchar(hline, self:width() - 1)
|
||||
self:canvas():move(self:width() - 1, 0):putchar(urcorner)
|
||||
self:canvas():move(self:width() - 1, 1):putchar(vline, self:height() - 1, true)
|
||||
self:canvas():move(self:width() - 1, self:height() - 1):putchar(lrcorner)
|
||||
end
|
||||
|
||||
-- get border corner attribute
|
||||
function border:cornerattr()
|
||||
return self._CORNERATTR or {"white", "black"}
|
||||
end
|
||||
|
||||
-- set border corner attribute
|
||||
function border:cornerattr_set(attr_ul, attr_rl)
|
||||
self._CORNERATTR = {attr_ul or "white", attr_rl or attr_ul or "black"}
|
||||
self:invalidate()
|
||||
end
|
||||
|
||||
-- swap border corner attribute
|
||||
function border:cornerattr_swap()
|
||||
local cornerattr = self:cornerattr()
|
||||
self:cornerattr_set(cornerattr[2], cornerattr[1])
|
||||
end
|
||||
|
||||
-- return module
|
||||
return border
|
78
src/ltui/boxdialog.lua
Normal file
78
src/ltui/boxdialog.lua
Normal file
@@ -0,0 +1,78 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file boxdialog.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ui/log")
|
||||
local rect = require("ui/rect")
|
||||
local action = require("ui/action")
|
||||
local curses = require("ui/curses")
|
||||
local window = require("ui/window")
|
||||
local textdialog = require("ui/textdialog")
|
||||
|
||||
-- define module
|
||||
local boxdialog = boxdialog or textdialog()
|
||||
|
||||
-- init dialog
|
||||
function boxdialog:init(name, bounds, title)
|
||||
|
||||
-- init window
|
||||
textdialog.init(self, name, bounds, title)
|
||||
|
||||
-- insert box
|
||||
self:panel():insert(self:box())
|
||||
|
||||
-- resize text
|
||||
self:text():bounds().ey = 3
|
||||
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)
|
||||
if v:text() then
|
||||
local lines = #self:text():splitext(v:text())
|
||||
if lines > 0 and lines < self:height() then
|
||||
self:box():bounds().sy = lines
|
||||
self:text():bounds().ey = lines
|
||||
self:box():invalidate(true)
|
||||
self:text():invalidate(true)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- select buttons by default
|
||||
self:panel():select(self:buttons())
|
||||
end
|
||||
|
||||
-- get box
|
||||
function boxdialog:box()
|
||||
if not self._BOX then
|
||||
self._BOX = window:new("boxdialog.box", rect{0, 3, self:panel():width(), self:panel():height() - 1})
|
||||
self._BOX:border():cornerattr_set("black", "white")
|
||||
end
|
||||
return self._BOX
|
||||
end
|
||||
|
||||
-- return module
|
||||
return boxdialog
|
105
src/ltui/button.lua
Normal file
105
src/ltui/button.lua
Normal file
@@ -0,0 +1,105 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file button.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ui/log")
|
||||
local view = require("ui/view")
|
||||
local event = require("ui/event")
|
||||
local label = require("ui/label")
|
||||
local action = require("ui/action")
|
||||
local curses = require("ui/curses")
|
||||
|
||||
-- define module
|
||||
local button = button or label()
|
||||
|
||||
-- init button
|
||||
function button:init(name, bounds, text, on_action)
|
||||
|
||||
-- init label
|
||||
label.init(self, name, bounds, text)
|
||||
|
||||
-- mark as selectable
|
||||
self:option_set("selectable", true)
|
||||
|
||||
-- show cursor
|
||||
self:cursor_show(true)
|
||||
|
||||
-- init action
|
||||
self:action_set(action.ac_on_enter, on_action)
|
||||
end
|
||||
|
||||
-- draw button
|
||||
function button:draw(transparent)
|
||||
|
||||
-- draw background
|
||||
view.draw(self, transparent)
|
||||
|
||||
-- strip text string
|
||||
local str = self:text()
|
||||
if str and #str > 0 then
|
||||
str = string.sub(str, 1, self:width())
|
||||
end
|
||||
if not str or #str == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- get the text attribute value
|
||||
local textattr = self:textattr_val()
|
||||
|
||||
-- selected?
|
||||
if self:state("selected") and self:state("focused") then
|
||||
textattr = {textattr, "reverse"}
|
||||
end
|
||||
|
||||
-- draw text
|
||||
self:canvas():attr(textattr):move(0, 0):putstr(str)
|
||||
end
|
||||
|
||||
-- on event
|
||||
function button:event_on(e)
|
||||
|
||||
-- selected?
|
||||
if not self:state("selected") then
|
||||
return
|
||||
end
|
||||
|
||||
-- enter this button?
|
||||
if e.type == event.ev_keyboard then
|
||||
if e.key_name == "Enter" then
|
||||
self:action_on(action.ac_on_enter)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- set state
|
||||
function button:state_set(name, enable)
|
||||
if name == "focused" and self:state(name) ~= enable then
|
||||
self:invalidate()
|
||||
end
|
||||
return view.state_set(self, name, enable)
|
||||
end
|
||||
|
||||
-- return module
|
||||
return button
|
161
src/ltui/canvas.lua
Normal file
161
src/ltui/canvas.lua
Normal file
@@ -0,0 +1,161 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file canvas.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local point = require("ltui/point")
|
||||
local curses = require("ltui/curses")
|
||||
local object = require("ltui/object")
|
||||
|
||||
-- define module
|
||||
local line = line or object()
|
||||
local canvas = canvas or object()
|
||||
|
||||
-- new canvas instance
|
||||
function canvas:new(view, window)
|
||||
|
||||
-- create instance
|
||||
self = self()
|
||||
|
||||
-- save view and window
|
||||
self._view = view
|
||||
self._window = window
|
||||
assert(view and window, "cannot new canvas instance without view and window!")
|
||||
|
||||
-- set the default attributes
|
||||
self:attr()
|
||||
|
||||
-- done
|
||||
return self
|
||||
end
|
||||
|
||||
-- clear canvas
|
||||
function canvas:clear()
|
||||
self._window:clear()
|
||||
return self
|
||||
end
|
||||
|
||||
-- move canvas to the given position
|
||||
function canvas:move(x, y)
|
||||
self._window:move(y, x)
|
||||
return self
|
||||
end
|
||||
|
||||
-- get the current position
|
||||
function canvas:pos()
|
||||
local y, x = self._window:getyx()
|
||||
return x, y
|
||||
end
|
||||
|
||||
-- get the canvas size
|
||||
function canvas:size()
|
||||
local y, x = self._window:getmaxyx()
|
||||
return point {x + 1, y + 1}
|
||||
end
|
||||
|
||||
-- get the canvas width
|
||||
function canvas:width()
|
||||
local _, x = self._window:getmaxyx()
|
||||
return x + 1
|
||||
end
|
||||
|
||||
-- get the canvas height
|
||||
function canvas:height()
|
||||
local y, _ = self._window:getmaxyx()
|
||||
return y + 1
|
||||
end
|
||||
|
||||
-- put character to canvas
|
||||
function canvas:putchar(ch, n, vertical)
|
||||
|
||||
-- acs character?
|
||||
if type(ch) == "string" and #ch > 1 then
|
||||
ch = curses.acs(ch)
|
||||
end
|
||||
|
||||
-- draw characters
|
||||
n = n or 1
|
||||
if vertical then
|
||||
local x, y = self:pos()
|
||||
while n > 0 do
|
||||
self:move(x, y)
|
||||
self._window:addch(ch)
|
||||
n = n - 1
|
||||
y = y + 1
|
||||
end
|
||||
else
|
||||
while n > 0 do
|
||||
self._window:addch(ch)
|
||||
n = n - 1
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-- put a string to canvas
|
||||
function canvas:putstr(str)
|
||||
self._window:addstr(str)
|
||||
return self
|
||||
end
|
||||
|
||||
-- put strings to canvas
|
||||
function canvas:putstrs(strs, startline)
|
||||
|
||||
-- draw strings
|
||||
local sy, sx = self._window:getyx()
|
||||
local ey, _ = self._window:getmaxyx()
|
||||
for idx = startline or 1, #strs do
|
||||
local _, y = self:pos()
|
||||
self._window:addstr(strs[idx])
|
||||
if y + 1 < ey and idx < #strs then
|
||||
self:move(sx, y + 1)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-- set canvas attributes
|
||||
--
|
||||
-- set attr: canvas:attr("bold")
|
||||
-- add attr: canvas:attr("bold", true)
|
||||
-- remove attr: canvas:attr("bold", false)
|
||||
--
|
||||
function canvas:attr(attrs, modify)
|
||||
|
||||
-- calculate the attributes
|
||||
local attr = curses.calc_attr(attrs)
|
||||
if modify == nil then
|
||||
self._window:attrset(attr)
|
||||
elseif modify == false then
|
||||
self._window:attroff(attr)
|
||||
else
|
||||
self._window:attron(attr)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-- return module
|
||||
return canvas
|
123
src/ltui/choicebox.lua
Normal file
123
src/ltui/choicebox.lua
Normal file
@@ -0,0 +1,123 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file choicebox.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ui/log")
|
||||
local view = require("ui/view")
|
||||
local rect = require("ui/rect")
|
||||
local panel = require("ui/panel")
|
||||
local event = require("ui/event")
|
||||
local action = require("ui/action")
|
||||
local curses = require("ui/curses")
|
||||
local button = require("ui/button")
|
||||
local object = require("ui/object")
|
||||
|
||||
-- define module
|
||||
local choicebox = choicebox or panel()
|
||||
|
||||
-- init choicebox
|
||||
function choicebox:init(name, bounds)
|
||||
|
||||
-- init panel
|
||||
panel.init(self, name, bounds)
|
||||
|
||||
-- init values
|
||||
self._VALUES = {}
|
||||
end
|
||||
|
||||
-- on event
|
||||
function choicebox:event_on(e)
|
||||
|
||||
-- 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
|
||||
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)
|
||||
|
||||
-- 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), text)
|
||||
|
||||
-- attach this index
|
||||
item:extra_set("index", index)
|
||||
|
||||
-- insert this config item
|
||||
self:insert(item)
|
||||
end
|
||||
|
||||
-- do select the current config
|
||||
function choicebox:_do_select()
|
||||
|
||||
-- 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
|
||||
self:action_on(action.ac_on_selected, index, value)
|
||||
|
||||
-- update text
|
||||
item:text_set("(X) " .. tostring(value))
|
||||
end
|
||||
|
||||
-- return module
|
||||
return choicebox
|
93
src/ltui/choicedialog.lua
Normal file
93
src/ltui/choicedialog.lua
Normal file
@@ -0,0 +1,93 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file choicedialog.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ui/log")
|
||||
local rect = require("ui/rect")
|
||||
local event = require("ui/event")
|
||||
local action = require("ui/action")
|
||||
local curses = require("ui/curses")
|
||||
local window = require("ui/window")
|
||||
local choicebox = require("ui/choicebox")
|
||||
local boxdialog = require("ui/boxdialog")
|
||||
|
||||
-- define module
|
||||
local choicedialog = choicedialog or boxdialog()
|
||||
|
||||
-- init dialog
|
||||
function choicedialog:init(name, bounds, title)
|
||||
|
||||
-- init window
|
||||
boxdialog.init(self, name, bounds, title)
|
||||
|
||||
-- init text
|
||||
self:text():text_set("Use the arrow keys to navigate this window or press the hotkey of the item you wish to select followed by the <SPACEBAR>. Press <?> for additional information about this")
|
||||
|
||||
-- init buttons
|
||||
self:button_add("select", "< Select >", function (v, e)
|
||||
self:choicebox():event_on(event.command {"cm_enter"})
|
||||
self:quit()
|
||||
end)
|
||||
self:button_add("cancel", "< Cancel >", function (v, e)
|
||||
self:quit()
|
||||
end)
|
||||
self:buttons():select(self:button("select"))
|
||||
|
||||
-- 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)
|
||||
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:state_set("focused", true) -- we can select and highlight selected item
|
||||
end
|
||||
return self._CHOICEBOX
|
||||
end
|
||||
|
||||
-- on event
|
||||
function choicedialog:event_on(e)
|
||||
|
||||
-- load values first
|
||||
if e.type == event.ev_idle then
|
||||
if not self._LOADED then
|
||||
self:action_on(action.ac_on_load)
|
||||
self._LOADED = true
|
||||
end
|
||||
-- select value
|
||||
elseif e.type == event.ev_keyboard then
|
||||
if e.key_name == "Down" or e.key_name == "Up" or e.key_name == " " then
|
||||
return self:choicebox():event_on(e)
|
||||
end
|
||||
end
|
||||
return boxdialog.event_on(self, e)
|
||||
end
|
||||
|
||||
-- return module
|
||||
return choicedialog
|
219
src/ltui/curses.lua
Normal file
219
src/ltui/curses.lua
Normal file
@@ -0,0 +1,219 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file curses.lua
|
||||
--
|
||||
|
||||
--[[ Console User Interface (cui) ]-----------------------------------------
|
||||
Author: Tiago Dionizio (tngd@mega.ist.utl.pt)
|
||||
$Id: core.lua 18 2007-06-21 20:43:52Z tngd $
|
||||
--------------------------------------------------------------------------]]
|
||||
|
||||
-- load modules
|
||||
local curses = require("ltui.curses")
|
||||
local os = require("ltui/base/os")
|
||||
local log = require("ltui/base/log")
|
||||
|
||||
-- get color from the given name
|
||||
function curses.color(name)
|
||||
if name == 'black' then return curses.COLOR_BLACK
|
||||
elseif name == 'red' then return curses.COLOR_RED
|
||||
elseif name == 'green' then return curses.COLOR_GREEN
|
||||
elseif name == 'yellow' then return curses.COLOR_YELLOW
|
||||
elseif name == 'blue' then return curses.COLOR_BLUE
|
||||
elseif name == 'magenta' then return curses.COLOR_MAGENTA
|
||||
elseif name == 'cyan' then return curses.COLOR_CYAN
|
||||
elseif name == 'white' then return curses.COLOR_WHITE
|
||||
else return curses.COLOR_BLACK
|
||||
end
|
||||
end
|
||||
|
||||
-- is color?
|
||||
local colors = {black = true, red = true, green = true, yellow = true, blue = true, magenta = true, cyan = true, white = true}
|
||||
function curses.iscolor(name)
|
||||
return colors[name] or colors[name:sub(3) or ""]
|
||||
end
|
||||
|
||||
-- get attr from the given name
|
||||
function curses.attr(name)
|
||||
if name == 'normal' then return curses.A_NORMAL
|
||||
elseif name == 'standout' then return curses.A_STANDOUT
|
||||
elseif name == 'underline' then return curses.A_UNDERLINE
|
||||
elseif name == 'reverse' then return curses.A_REVERSE
|
||||
elseif name == 'blink' then return curses.A_BLINK
|
||||
elseif name == 'dim' then return curses.A_DIM
|
||||
elseif name == 'bold' then return curses.A_BOLD
|
||||
elseif name == 'protect' then return curses.A_PROTECT
|
||||
elseif name == 'invis' then return curses.A_INVIS
|
||||
elseif name == 'alt' then return curses.A_ALTCHARSET
|
||||
else return curses.A_NORMAL
|
||||
end
|
||||
end
|
||||
|
||||
-- get acs character from the given name
|
||||
function curses.acs(name)
|
||||
if name == 'block' then return curses.ACS_BLOCK
|
||||
elseif name == 'board' then return curses.ACS_BOARD
|
||||
elseif name == 'btee' then return curses.ACS_BTEE
|
||||
elseif name == 'bullet' then return curses.ACS_BULLET
|
||||
elseif name == 'ckboard' then return curses.ACS_CKBOARD
|
||||
elseif name == 'darrow' then return curses.ACS_DARROW
|
||||
elseif name == 'degree' then return curses.ACS_DEGREE
|
||||
elseif name == 'diamond' then return curses.ACS_DIAMOND
|
||||
elseif name == 'gequal' then return curses.ACS_GEQUAL
|
||||
elseif name == 'hline' then return curses.ACS_HLINE
|
||||
elseif name == 'lantern' then return curses.ACS_LANTERN
|
||||
elseif name == 'larrow' then return curses.ACS_LARROW
|
||||
elseif name == 'lequal' then return curses.ACS_LEQUAL
|
||||
elseif name == 'llcorner' then return curses.ACS_LLCORNER
|
||||
elseif name == 'lrcorner' then return curses.ACS_LRCORNER
|
||||
elseif name == 'ltee' then return curses.ACS_LTEE
|
||||
elseif name == 'nequal' then return curses.ACS_NEQUAL
|
||||
elseif name == 'pi' then return curses.ACS_PI
|
||||
elseif name == 'plminus' then return curses.ACS_PLMINUS
|
||||
elseif name == 'plus' then return curses.ACS_PLUS
|
||||
elseif name == 'rarrow' then return curses.ACS_RARROW
|
||||
elseif name == 'rtee' then return curses.ACS_RTEE
|
||||
elseif name == 's1' then return curses.ACS_S1
|
||||
elseif name == 's3' then return curses.ACS_S3
|
||||
elseif name == 's7' then return curses.ACS_S7
|
||||
elseif name == 's9' then return curses.ACS_S9
|
||||
elseif name == 'sterling' then return curses.ACS_STERLING
|
||||
elseif name == 'ttee' then return curses.ACS_TTEE
|
||||
elseif name == 'uarrow' then return curses.ACS_UARROW
|
||||
elseif name == 'ulcorner' then return curses.ACS_ULCORNER
|
||||
elseif name == 'urcorner' then return curses.ACS_URCORNER
|
||||
elseif name == 'vline' then return curses.ACS_VLINE
|
||||
elseif type(name) == 'string' and #name == 1 then
|
||||
return name
|
||||
else
|
||||
return ' '
|
||||
end
|
||||
end
|
||||
|
||||
-- calculate attr from the attributes list
|
||||
--
|
||||
-- local attr = curses.calc_attr("bold")
|
||||
-- local attr = curses.calc_attr("yellow")
|
||||
-- local attr = curses.calc_attr{ "yellow", "ongreen" }
|
||||
-- local attr = curses.calc_attr{ "yellow", "ongreen", "bold" }
|
||||
-- local attr = curses.calc_attr{ curses.color_pair("yellow", "green"), "bold" }
|
||||
--
|
||||
function curses.calc_attr(attrs)
|
||||
|
||||
-- curses.calc_attr(curses.A_BOLD)
|
||||
-- curses.calc_attr(curses.color_pair("yellow", "green"))
|
||||
local atype = type(attrs)
|
||||
if atype == "number" then
|
||||
return attrs
|
||||
-- curses.calc_attr("bold")
|
||||
-- curses.calc_attr("yellow")
|
||||
elseif atype == "string" then
|
||||
if curses.iscolor(attrs) then
|
||||
local color = attrs
|
||||
if color:startswith("on") then
|
||||
color = color:sub(3)
|
||||
end
|
||||
return curses.color_pair(color, color)
|
||||
end
|
||||
return curses.attr(attrs)
|
||||
-- curses.calc_attr{ "yellow", "ongreen", "bold" }
|
||||
-- curses.calc_attr{ curses.color_pair("yellow", "green"), "bold" }
|
||||
elseif atype == "table" then
|
||||
local v = 0
|
||||
local set = {}
|
||||
local fg = nil
|
||||
local bg = nil
|
||||
for _, a in ipairs(attrs) do
|
||||
if not set[a] and a then
|
||||
set[a] = true
|
||||
if type(a) == "number" then
|
||||
v = v + a
|
||||
elseif curses.iscolor(a) then
|
||||
if a:startswith("on") then
|
||||
bg = a:sub(3)
|
||||
else
|
||||
fg = a
|
||||
end
|
||||
else
|
||||
v = v + curses.attr(a)
|
||||
end
|
||||
end
|
||||
end
|
||||
if fg or bg then
|
||||
v = v + curses.color_pair(fg or bg, bg or fg)
|
||||
end
|
||||
return v
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
-- get attr from the color pair
|
||||
curses._color_pair = curses._color_pair or curses.color_pair
|
||||
function curses.color_pair(fg, bg)
|
||||
|
||||
-- get foreground and backround color
|
||||
fg = curses.color(fg)
|
||||
bg = curses.color(bg)
|
||||
|
||||
-- attempt to get color from the cache first
|
||||
local key = fg .. ':' .. bg
|
||||
local colors = curses._COLORS or {}
|
||||
if colors[key] then
|
||||
return colors[key]
|
||||
end
|
||||
|
||||
-- no colors?
|
||||
if not curses.has_colors() then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- update the colors count
|
||||
curses._NCOLORS = (curses._NCOLORS or 0) + 1
|
||||
|
||||
-- init the color pair
|
||||
if not curses.init_pair(curses._NCOLORS, fg, bg) then
|
||||
os.raise("failed to initialize color pair (%d, %s, %s)", curses._NCOLORS, fg, bg)
|
||||
end
|
||||
|
||||
-- get the color attr
|
||||
local attr = curses._color_pair(curses._NCOLORS)
|
||||
|
||||
-- save to cache
|
||||
colors[key] = attr
|
||||
curses._COLORS = colors
|
||||
|
||||
-- ok
|
||||
return attr
|
||||
end
|
||||
|
||||
-- set cursor state
|
||||
curses._cursor_set = curses._cursor_set or curses.cursor_set
|
||||
function curses.cursor_set(state)
|
||||
if curses._CURSOR_STATE ~= state then
|
||||
curses._CURSOR_STATE = state
|
||||
curses._cursor_set(state)
|
||||
end
|
||||
end
|
||||
|
||||
-- return module: curses
|
||||
return curses
|
46
src/ltui/desktop.lua
Normal file
46
src/ltui/desktop.lua
Normal file
@@ -0,0 +1,46 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file desktop.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local rect = require("ltui/rect")
|
||||
local view = require("ltui/view")
|
||||
local panel = require("ltui/panel")
|
||||
local curses = require("ltui/curses")
|
||||
|
||||
-- define module
|
||||
local desktop = desktop or panel()
|
||||
|
||||
-- init desktop
|
||||
function desktop:init(name, bounds)
|
||||
|
||||
-- init panel
|
||||
panel.init(self, name, bounds)
|
||||
|
||||
-- init background
|
||||
self:background_set("blue")
|
||||
end
|
||||
|
||||
-- return module
|
||||
return desktop
|
118
src/ltui/dialog.lua
Normal file
118
src/ltui/dialog.lua
Normal file
@@ -0,0 +1,118 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file dialog.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ui/log")
|
||||
local rect = require("ui/rect")
|
||||
local event = require("ui/event")
|
||||
local label = require("ui/label")
|
||||
local panel = require("ui/panel")
|
||||
local action = require("ui/action")
|
||||
local button = require("ui/button")
|
||||
local window = require("ui/window")
|
||||
local curses = require("ui/curses")
|
||||
|
||||
-- define module
|
||||
local dialog = dialog or window()
|
||||
|
||||
-- init dialog
|
||||
function dialog:init(name, bounds, title)
|
||||
|
||||
-- init window
|
||||
window.init(self, name, bounds, title, true)
|
||||
|
||||
-- insert buttons
|
||||
self:panel():insert(self:buttons())
|
||||
end
|
||||
|
||||
-- get buttons
|
||||
function dialog:buttons()
|
||||
if not self._BUTTONS then
|
||||
self._BUTTONS = panel:new("dialog.buttons", rect:new(0, self:panel():height() - 1, self:panel():width(), 1))
|
||||
end
|
||||
return self._BUTTONS
|
||||
end
|
||||
|
||||
-- get button from the given button name
|
||||
function dialog:button(name)
|
||||
return self:buttons():view(name)
|
||||
end
|
||||
|
||||
-- add button
|
||||
function dialog:button_add(name, text, command)
|
||||
|
||||
-- init button
|
||||
local btn = button:new(name, rect:new(0, 0, #text, 1), text, command)
|
||||
|
||||
-- insert button
|
||||
self:buttons():insert(btn)
|
||||
|
||||
-- update the position of all buttons
|
||||
local index = 1
|
||||
local width = self:buttons():width()
|
||||
local count = self:buttons():count()
|
||||
local padding = math.floor(width / 8)
|
||||
for v in self:buttons():views() do
|
||||
local x = padding + index * math.floor((width - padding * 2) / (count + 1)) - math.floor(v:width() / 2)
|
||||
if x + v:width() > width then
|
||||
x = math.max(0, width - v:width())
|
||||
end
|
||||
v:bounds():move2(x, 0)
|
||||
v:invalidate(true)
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
-- invalidate
|
||||
self:invalidate()
|
||||
|
||||
-- ok
|
||||
return btn
|
||||
end
|
||||
|
||||
-- select button from the given button name
|
||||
function dialog:button_select(name)
|
||||
self:buttons():select(self:button(name))
|
||||
return self
|
||||
end
|
||||
|
||||
-- quit dialog
|
||||
function dialog:quit()
|
||||
local parent = self:parent()
|
||||
if parent then
|
||||
self:action_on(action.ac_on_exit)
|
||||
parent:remove(self)
|
||||
end
|
||||
end
|
||||
|
||||
-- on event
|
||||
function dialog:event_on(e)
|
||||
if e.type == event.ev_keyboard and e.key_name == "Esc" then
|
||||
self:quit()
|
||||
return true
|
||||
end
|
||||
return window.event_on(self, e)
|
||||
end
|
||||
|
||||
-- return module
|
||||
return dialog
|
90
src/ltui/event.lua
Normal file
90
src/ltui/event.lua
Normal file
@@ -0,0 +1,90 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file event.lua
|
||||
--
|
||||
|
||||
--[[ Console User Interface (cui) ]-----------------------------------------
|
||||
Author: Tiago Dionizio (tiago.dionizio AT gmail.com)
|
||||
$Id: event.lua 18 2007-06-21 20:43:52Z tngd $
|
||||
--------------------------------------------------------------------------]]
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local object = require("ltui/object")
|
||||
|
||||
-- define module
|
||||
local event = event or object { _init = {"type", "command", "extra"} }
|
||||
|
||||
-- register event types
|
||||
function event:register(tag, ...)
|
||||
local base = self[tag] or 0
|
||||
local enums = {...}
|
||||
local n = #enums
|
||||
for i = 1, n do
|
||||
self[enums[i]] = i + base
|
||||
end
|
||||
self[tag] = base + n
|
||||
end
|
||||
|
||||
-- is key?
|
||||
function event:is_key(key_name)
|
||||
return self.type == event.ev_keyboard and self.key_name == key_name
|
||||
end
|
||||
|
||||
-- is command event: cm_xxx?
|
||||
function event:is_command(command)
|
||||
return self.type == event.ev_command and self.command == command
|
||||
end
|
||||
|
||||
-- dump event
|
||||
function event:dump()
|
||||
if self.type == event.ev_keyboard then
|
||||
log:print("event(key): %s %s ..", self.key_name, self.key_code)
|
||||
elseif self.type == event.ev_command then
|
||||
log:print("event(cmd): %s ..", self.command)
|
||||
else
|
||||
log:print("event(%s): ..", self.type)
|
||||
end
|
||||
end
|
||||
|
||||
-- register event types, event.ev_keyboard = 1, event.ev_mouse = 2, ... , event.ev_idle = 5, event.ev_max = 5
|
||||
event:register("ev_max", "ev_keyboard", "ev_mouse", "ev_command", "ev_text", "ev_idle")
|
||||
|
||||
-- register command event types (ev_command)
|
||||
event:register("cm_max", "cm_quit", "cm_exit", "cm_enter")
|
||||
|
||||
-- define keyboard event
|
||||
--
|
||||
-- keyname = key name
|
||||
-- keycode = key code
|
||||
-- keymeta = ALT key was pressed
|
||||
--
|
||||
event.keyboard = object {_init = { "key_code", "key_name", "key_meta" }, type = event.ev_keyboard}
|
||||
|
||||
-- define command event
|
||||
event.command = object {_init = { "command", "extra" }, type = event.ev_command}
|
||||
|
||||
-- define idle event
|
||||
event.idle = object {_init = {}, type = event.ev_idle}
|
||||
|
||||
-- return module
|
||||
return event
|
77
src/ltui/inputdialog.lua
Normal file
77
src/ltui/inputdialog.lua
Normal file
@@ -0,0 +1,77 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file inputdialog.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ui/log")
|
||||
local rect = require("ui/rect")
|
||||
local view = require("ui/view")
|
||||
local event = require("ui/event")
|
||||
local action = require("ui/action")
|
||||
local curses = require("ui/curses")
|
||||
local window = require("ui/window")
|
||||
local textedit = require("ui/textedit")
|
||||
local textdialog = require("ui/textdialog")
|
||||
|
||||
-- define module
|
||||
local inputdialog = inputdialog or textdialog()
|
||||
|
||||
-- init dialog
|
||||
function inputdialog:init(name, bounds, title)
|
||||
|
||||
-- init window
|
||||
textdialog.init(self, name, bounds, title)
|
||||
|
||||
-- insert textedit
|
||||
self:panel():insert(self:textedit())
|
||||
|
||||
-- resize text
|
||||
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)
|
||||
if v:text() then
|
||||
local lines = #self:text():splitext(v:text()) + 1
|
||||
if lines > 0 and lines < self:height() then
|
||||
self:text():bounds().ey = lines
|
||||
self:textedit():bounds().sy = lines
|
||||
self:text():invalidate(true)
|
||||
self:textedit():invalidate(true)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- get textedit
|
||||
function inputdialog:textedit()
|
||||
if not self._TEXTEDIT then
|
||||
self._TEXTEDIT = textedit:new("inputdialog.textedit", rect{0, 1, self:panel():width(), self:panel():height() - 1})
|
||||
end
|
||||
return self._TEXTEDIT
|
||||
end
|
||||
|
||||
-- return module
|
||||
return inputdialog
|
143
src/ltui/label.lua
Normal file
143
src/ltui/label.lua
Normal file
@@ -0,0 +1,143 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file label.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local view = require("ltui/view")
|
||||
local event = require("ltui/event")
|
||||
local action = require("ltui/action")
|
||||
local curses = require("ltui/curses")
|
||||
|
||||
-- define module
|
||||
local label = label or view()
|
||||
|
||||
-- init label
|
||||
function label:init(name, bounds, text)
|
||||
|
||||
-- init view
|
||||
view.init(self, name, bounds)
|
||||
|
||||
-- init text
|
||||
self:text_set(text)
|
||||
|
||||
-- init text attribute
|
||||
self:textattr_set("black")
|
||||
end
|
||||
|
||||
-- draw view
|
||||
function label:draw(transparent)
|
||||
|
||||
-- draw background
|
||||
view.draw(self, transparent)
|
||||
|
||||
-- get the text attribute value
|
||||
local textattr = self:textattr_val()
|
||||
|
||||
-- draw text string
|
||||
local str = self:text()
|
||||
if str and #str > 0 and textattr then
|
||||
self:canvas():attr(textattr):move(0, 0):putstrs(self:splitext(str))
|
||||
end
|
||||
end
|
||||
|
||||
-- get text
|
||||
function label:text()
|
||||
return self._TEXT
|
||||
end
|
||||
|
||||
-- set text
|
||||
function label:text_set(text)
|
||||
|
||||
-- set text
|
||||
text = text or ""
|
||||
local changed = self._TEXT ~= text
|
||||
self._TEXT = text
|
||||
|
||||
-- do action
|
||||
if changed then
|
||||
self:action_on(action.ac_on_text_changed)
|
||||
end
|
||||
self:invalidate()
|
||||
return self
|
||||
end
|
||||
|
||||
-- get text attribute
|
||||
function label:textattr()
|
||||
return self:attr("textattr")
|
||||
end
|
||||
|
||||
-- set text attribute, .e.g textattr_set("yellow onblue bold")
|
||||
function label:textattr_set(attr)
|
||||
return self:attr_set("textattr", attr)
|
||||
end
|
||||
|
||||
-- get the current text attribute value
|
||||
function label:textattr_val()
|
||||
|
||||
-- get text attribute
|
||||
local textattr = self:textattr()
|
||||
if not textattr then
|
||||
return
|
||||
end
|
||||
|
||||
-- no text background? use view's background
|
||||
if self:background() and not textattr:find("on") then
|
||||
textattr = textattr .. " on" .. self:background()
|
||||
end
|
||||
|
||||
-- attempt to get the attribute value from the cache first
|
||||
self._TEXTATTR = self._TEXTATTR or {}
|
||||
local value = self._TEXTATTR[textattr]
|
||||
if value then
|
||||
return value
|
||||
end
|
||||
|
||||
-- update the cache
|
||||
value = curses.calc_attr(textattr:split("%s+"))
|
||||
self._TEXTATTR[textattr] = value
|
||||
return value
|
||||
end
|
||||
|
||||
-- split text by width
|
||||
function label:splitext(text, width)
|
||||
|
||||
-- get width
|
||||
width = width or self:width()
|
||||
|
||||
-- split text first
|
||||
local result = {}
|
||||
local lines = text:split('\n', true)
|
||||
for idx = 1, #lines do
|
||||
local line = lines[idx]
|
||||
while #line > width do
|
||||
table.insert(result, line:sub(1, width))
|
||||
line = line:sub(width + 1)
|
||||
end
|
||||
table.insert(result, line)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- return module
|
||||
return label
|
313
src/ltui/mconfdialog.lua
Normal file
313
src/ltui/mconfdialog.lua
Normal file
@@ -0,0 +1,313 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file mconfdialog.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ui/log")
|
||||
local rect = require("ui/rect")
|
||||
local event = require("ui/event")
|
||||
local action = require("ui/action")
|
||||
local curses = require("ui/curses")
|
||||
local window = require("ui/window")
|
||||
local menuconf = require("ui/menuconf")
|
||||
local boxdialog = require("ui/boxdialog")
|
||||
local textdialog = require("ui/textdialog")
|
||||
local inputdialog = require("ui/inputdialog")
|
||||
local choicedialog = require("ui/choicedialog")
|
||||
|
||||
-- define module
|
||||
local mconfdialog = mconfdialog or boxdialog()
|
||||
|
||||
-- init dialog
|
||||
function mconfdialog:init(name, bounds, title)
|
||||
|
||||
-- init window
|
||||
boxdialog.init(self, name, bounds, title)
|
||||
|
||||
-- init text
|
||||
self:text():text_set([[Arrow keys navigate the menu. <Enter> selects submenus ---> (or empty submenus ----).
|
||||
Pressing <Y> includes, <N> excludes. Enter <Esc> or <Back> to go back, <?> for Help, </> for Search. Legend: [*] built-in [ ] excluded
|
||||
]])
|
||||
|
||||
-- init buttons
|
||||
self:button_add("select", "< Select >", function (v, e) self:menuconf():event_on(event.command {"cm_enter"}) end)
|
||||
self:button_add("back", "< Back >", function (v, e) self:menuconf():event_on(event.command {"cm_back"}) end)
|
||||
self:button_add("exit", "< Exit >", function (v, e) self:quit() end)
|
||||
self:button_add("help", "< Help >", function (v, e) self:show_help() end)
|
||||
self:button_add("save", "< Save >", function (v, e) self:action_on(action.ac_on_save) end)
|
||||
self:buttons():select(self:button("select"))
|
||||
|
||||
-- insert menu config
|
||||
self:box():panel():insert(self:menuconf())
|
||||
|
||||
-- disable to select to box (disable Tab switch and only response to buttons)
|
||||
self:box():option_set("selectable", false)
|
||||
|
||||
-- on selected
|
||||
self:menuconf():action_set(action.ac_on_selected, function (v, config)
|
||||
|
||||
-- show input dialog
|
||||
if config.kind == "string" or config.kind == "number" then
|
||||
local dialog_input = self:inputdialog()
|
||||
dialog_input:extra_set("config", config)
|
||||
dialog_input:title():text_set(config:prompt())
|
||||
dialog_input:textedit():text_set(tostring(config.value))
|
||||
dialog_input:panel():select(dialog_input:textedit())
|
||||
if config.kind == "string" then
|
||||
dialog_input:text():text_set("Please enter a string value. Use the <TAB> key to move from the input fields to buttons below it.")
|
||||
else
|
||||
dialog_input:text():text_set("Please enter a decimal value. Fractions will not be accepted. Use the <TAB> key to move from the input field to the buttons below it.")
|
||||
end
|
||||
self:insert(dialog_input, {centerx = true, centery = true})
|
||||
return true
|
||||
|
||||
-- show choice dialog
|
||||
elseif config.kind == "choice" and config.values and #config.values > 0 then
|
||||
local dialog_choice = self:choicedialog()
|
||||
dialog_choice:title():text_set(config:prompt())
|
||||
dialog_choice:choicebox():load(config.values, config.value)
|
||||
dialog_choice:choicebox():action_set(action.ac_on_selected, function (v, index, value)
|
||||
config.value = index
|
||||
end)
|
||||
self:insert(dialog_choice, {centerx = true, centery = true})
|
||||
return true
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- load configs
|
||||
function mconfdialog:load(configs)
|
||||
self._CONFIGS = configs
|
||||
return self:menuconf():load(configs)
|
||||
end
|
||||
|
||||
-- get configs
|
||||
function mconfdialog:configs()
|
||||
return self._CONFIGS
|
||||
end
|
||||
|
||||
-- get menu config
|
||||
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:state_set("focused", true) -- we can select and highlight selected item
|
||||
end
|
||||
return self._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)
|
||||
self._HELPDIALOG = helpdialog
|
||||
end
|
||||
return self._HELPDIALOG
|
||||
end
|
||||
|
||||
-- get result dialog
|
||||
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)
|
||||
self._RESULTDIALOG = resultdialog
|
||||
end
|
||||
return self._RESULTDIALOG
|
||||
end
|
||||
|
||||
-- get input dialog
|
||||
function mconfdialog:inputdialog()
|
||||
if not self._INPUTDIALOG then
|
||||
local dialog_input = inputdialog:new("mconfdialog.input", rect {0, 0, math.min(80, self:width() - 8), math.min(8, self:height())}, "input dialog")
|
||||
dialog_input:background_set(self:frame():background())
|
||||
dialog_input:frame():background_set("cyan")
|
||||
dialog_input:textedit():option_set("multiline", false)
|
||||
dialog_input:button_add("ok", "< Ok >", function (v)
|
||||
local config = dialog_input:extra("config")
|
||||
if config.kind == "string" then
|
||||
config.value = dialog_input:textedit():text()
|
||||
elseif config.kind == "number" then
|
||||
local value = tonumber(dialog_input:textedit():text())
|
||||
if value ~= nil then
|
||||
config.value = value
|
||||
end
|
||||
end
|
||||
dialog_input:quit()
|
||||
end)
|
||||
dialog_input:button_add("cancel", "< Cancel >", function (v)
|
||||
dialog_input:quit()
|
||||
end)
|
||||
dialog_input:button_select("ok")
|
||||
self._INPUTDIALOG = dialog_input
|
||||
end
|
||||
return self._INPUTDIALOG
|
||||
end
|
||||
|
||||
-- get choice dialog
|
||||
function mconfdialog:choicedialog()
|
||||
if not self._CHOICEDIALOG then
|
||||
local dialog_choice = choicedialog:new("mconfdialog.choice", rect {0, 0, math.min(80, self:width() - 8), math.min(20, self:height())}, "input dialog")
|
||||
dialog_choice:background_set(self:frame():background())
|
||||
dialog_choice:frame():background_set("cyan")
|
||||
dialog_choice:box():frame():background_set("cyan")
|
||||
self._CHOICEDIALOG = dialog_choice
|
||||
end
|
||||
return self._CHOICEDIALOG
|
||||
end
|
||||
|
||||
-- get search dialog
|
||||
function mconfdialog:searchdialog()
|
||||
if not self._SEARCHDIALOG then
|
||||
local dialog_search = inputdialog:new("mconfdialog.input", rect {0, 0, math.min(80, self:width() - 8), math.min(8, self:height())}, "Search Configuration Parameter")
|
||||
dialog_search:background_set(self:frame():background())
|
||||
dialog_search:frame():background_set("cyan")
|
||||
dialog_search:textedit():option_set("multiline", false)
|
||||
dialog_search:text():text_set("Enter (sub)string or lua pattern string to search for configuration")
|
||||
dialog_search:button_add("ok", "< Ok >", function (v)
|
||||
local configs = self:search(self:configs(), dialog_search:textedit():text())
|
||||
local results = "Search('" .. dialog_search:textedit():text() .. "') results:"
|
||||
for _, config in ipairs(configs) do
|
||||
results = results .. "\n" .. config:prompt()
|
||||
if config.kind then
|
||||
results = results .. "\nkind: " .. config.kind
|
||||
end
|
||||
if config.default then
|
||||
results = results .. "\ndefault: " .. config.default
|
||||
end
|
||||
if config.path then
|
||||
results = results .. "\npath: " .. config.path
|
||||
end
|
||||
if config.sourceinfo then
|
||||
results = results .. "\nposition: " .. (config.sourceinfo.file or "") .. ":" .. (config.sourceinfo.line or "-1")
|
||||
end
|
||||
results = results .. "\n"
|
||||
end
|
||||
self:show_result(results)
|
||||
dialog_search:quit()
|
||||
end)
|
||||
dialog_search:button_add("cancel", "< Cancel >", function (v)
|
||||
dialog_search:quit()
|
||||
end)
|
||||
dialog_search:button_select("ok")
|
||||
self._SEARCHDIALOG = dialog_search
|
||||
end
|
||||
return self._SEARCHDIALOG
|
||||
end
|
||||
|
||||
-- search configs via the given text
|
||||
function mconfdialog:search(configs, text)
|
||||
local results = {}
|
||||
for _, config in ipairs(configs) do
|
||||
local prompt = config:prompt()
|
||||
if prompt and prompt:find(text) then
|
||||
table.insert(results, config)
|
||||
end
|
||||
if config.kind == "menu" then
|
||||
table.join2(results, self:search(config.configs, text))
|
||||
end
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
-- show help dialog
|
||||
function mconfdialog:show_help()
|
||||
if self:parent() then
|
||||
|
||||
-- get the current config item
|
||||
local item = self:menuconf():current()
|
||||
|
||||
-- get the current config
|
||||
local config = item:extra("config")
|
||||
|
||||
-- set help title
|
||||
self:helpdialog():title():text_set(config:prompt())
|
||||
|
||||
-- set help text
|
||||
local text = config.description
|
||||
if type(text) == "table" then
|
||||
text = table.concat(text, '\n')
|
||||
end
|
||||
if config.kind then
|
||||
text = text .. "\ntype: " .. config.kind
|
||||
end
|
||||
if config.default then
|
||||
text = text .. "\ndefault: " .. tostring(config.default)
|
||||
end
|
||||
if config.kind == "choice" then
|
||||
text = text .. "\nvalues: "
|
||||
for _, value in ipairs(config.values) do
|
||||
text = text .. "\n - " .. value
|
||||
end
|
||||
end
|
||||
if config.path then
|
||||
text = text .. "\npath: " .. config.path
|
||||
end
|
||||
if config.sourceinfo then
|
||||
text = text .. "\nposition: " .. (config.sourceinfo.file or "") .. ":" .. (config.sourceinfo.line or "-1")
|
||||
end
|
||||
self:helpdialog():text():text_set(text)
|
||||
|
||||
-- show help
|
||||
self:parent():insert(self:helpdialog())
|
||||
end
|
||||
end
|
||||
|
||||
-- show search dialog
|
||||
function mconfdialog:show_search()
|
||||
local dialog_search = self:searchdialog()
|
||||
dialog_search:panel():select(dialog_search:textedit())
|
||||
self:insert(dialog_search, {centerx = true, centery = true})
|
||||
end
|
||||
|
||||
-- show result dialog
|
||||
function mconfdialog:show_result(text)
|
||||
local dialog_result = self:resultdialog()
|
||||
dialog_result:text():text_set(text)
|
||||
if not self:view("mconfdialog.result") then
|
||||
self:insert(dialog_result, {centerx = true, centery = true})
|
||||
else
|
||||
self:select(dialog_result)
|
||||
end
|
||||
end
|
||||
|
||||
-- on event
|
||||
function mconfdialog:event_on(e)
|
||||
|
||||
-- select config
|
||||
if e.type == event.ev_keyboard then
|
||||
if e.key_name == "Down" or e.key_name == "Up" or e.key_name == " " or e.key_name == "Esc" or e.key_name:lower() == "y" or e.key_name:lower() == "n" then
|
||||
return self:menuconf():event_on(e)
|
||||
elseif e.key_name == "?" then
|
||||
self:show_help()
|
||||
return true
|
||||
elseif e.key_name == "/" then
|
||||
self:show_search()
|
||||
return true
|
||||
end
|
||||
end
|
||||
return boxdialog.event_on(self, e)
|
||||
end
|
||||
|
||||
-- return module
|
||||
return mconfdialog
|
56
src/ltui/menubar.lua
Normal file
56
src/ltui/menubar.lua
Normal file
@@ -0,0 +1,56 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file menubar.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local rect = require("ltui/rect")
|
||||
local label = require("ltui/label")
|
||||
local panel = require("ltui/panel")
|
||||
local curses = require("ltui/curses")
|
||||
|
||||
-- define module
|
||||
local menubar = menubar or panel()
|
||||
|
||||
-- init menubar
|
||||
function menubar:init(name, bounds)
|
||||
|
||||
-- init panel
|
||||
panel.init(self, name, bounds)
|
||||
|
||||
-- init title
|
||||
self._TITLE = label:new("menubar.title", rect{0, 0, self:width(), self:height()}, "Menu Bar")
|
||||
self:insert(self:title())
|
||||
self:title():textattr_set("red")
|
||||
|
||||
-- init background
|
||||
self:background_set("white")
|
||||
end
|
||||
|
||||
-- get title
|
||||
function menubar:title()
|
||||
return self._TITLE
|
||||
end
|
||||
|
||||
-- return module
|
||||
return menubar
|
286
src/ltui/menuconf.lua
Normal file
286
src/ltui/menuconf.lua
Normal file
@@ -0,0 +1,286 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file menuconf.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ui/log")
|
||||
local view = require("ui/view")
|
||||
local rect = require("ui/rect")
|
||||
local panel = require("ui/panel")
|
||||
local event = require("ui/event")
|
||||
local action = require("ui/action")
|
||||
local curses = require("ui/curses")
|
||||
local button = require("ui/button")
|
||||
local object = require("ui/object")
|
||||
|
||||
-- define module
|
||||
local menuconf = menuconf or panel()
|
||||
|
||||
-- init menuconf
|
||||
function menuconf:init(name, bounds)
|
||||
|
||||
-- init panel
|
||||
panel.init(self, name, bounds)
|
||||
|
||||
-- init configs
|
||||
self._CONFIGS = {}
|
||||
end
|
||||
|
||||
-- on event
|
||||
function menuconf:event_on(e)
|
||||
|
||||
-- select config
|
||||
local back = false
|
||||
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
|
||||
elseif e.key_name:lower() == "y" then
|
||||
self:_do_include(true)
|
||||
return true
|
||||
elseif e.key_name:lower() == "n" then
|
||||
self:_do_include(false)
|
||||
return true
|
||||
elseif e.key_name == "Esc" then
|
||||
back = true
|
||||
end
|
||||
elseif e.type == event.ev_command then
|
||||
if e.command == "cm_enter" then
|
||||
self:_do_select()
|
||||
return true
|
||||
elseif e.command == "cm_back" then
|
||||
back = true
|
||||
end
|
||||
end
|
||||
|
||||
-- back?
|
||||
if back then
|
||||
-- load the previous menu configs
|
||||
local configs_prev = self._CONFIGS._PREV
|
||||
if configs_prev then
|
||||
self._CONFIGS._PREV = configs_prev._PREV
|
||||
self:load(configs_prev)
|
||||
return true
|
||||
end
|
||||
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)
|
||||
|
||||
-- init a config item view
|
||||
local item = button:new("menuconf.config." .. self:count(), rect:new(0, self:count(), self:width(), 1), tostring(config))
|
||||
|
||||
-- attach this config
|
||||
item:extra_set("config", config)
|
||||
|
||||
-- attach this view
|
||||
config._view = item
|
||||
|
||||
-- insert this config item
|
||||
self:insert(item)
|
||||
end
|
||||
|
||||
-- do select the current config
|
||||
function menuconf:_do_select()
|
||||
|
||||
-- get the current item
|
||||
local item = self:current()
|
||||
|
||||
-- get the current config
|
||||
local config = item:extra("config")
|
||||
|
||||
-- clear new state
|
||||
config.new = false
|
||||
|
||||
-- do action: on selected
|
||||
if self:action_on(action.ac_on_selected, config) then
|
||||
return
|
||||
end
|
||||
|
||||
-- select the boolean config
|
||||
if config.kind == "boolean" then
|
||||
config.value = not config.value
|
||||
-- show sub-menu configs
|
||||
elseif config.kind == "menu" and config.configs and #config.configs > 0 then
|
||||
local configs_prev = self._CONFIGS
|
||||
self:load(config.configs)
|
||||
self._CONFIGS._PREV = configs_prev
|
||||
end
|
||||
end
|
||||
|
||||
-- do include
|
||||
function menuconf:_do_include(enabled)
|
||||
|
||||
-- get the current item
|
||||
local item = self:current()
|
||||
|
||||
-- get the current config
|
||||
local config = item:extra("config")
|
||||
|
||||
-- clear new state
|
||||
config.new = false
|
||||
|
||||
-- select the boolean config
|
||||
if config.kind == "boolean" then
|
||||
config.value = enabled
|
||||
end
|
||||
end
|
||||
|
||||
-- init config object
|
||||
--
|
||||
-- kind
|
||||
-- - {kind = "number/boolean/string/choice/menu"}
|
||||
--
|
||||
-- description
|
||||
-- - {description = "config item description"}
|
||||
-- - {description = {"config item description", "line2", "line3", "more description ..."}}
|
||||
--
|
||||
-- boolean config
|
||||
-- - {name = "...", kind = "boolean", value = true, default = true, description = "boolean config item", new = true/false}
|
||||
--
|
||||
-- number config
|
||||
-- - {name = "...", kind = "number", value = 10, default = 0, description = "number config item", new = true/false}
|
||||
--
|
||||
-- string config
|
||||
-- - {name = "...", kind = "string", value = "xmake", default = "", description = "string config item", new = true/false}
|
||||
--
|
||||
-- choice config (value is index)
|
||||
-- - {name = "...", kind = "choice", value = 1, default = 1, description = "choice config item", values = {2, 2, 3, 4, 5}}
|
||||
--
|
||||
-- menu config
|
||||
-- - {name = "...", kind = "menu", description = "menu config item", configs = {...}}
|
||||
--
|
||||
local config = config or object{new = true,
|
||||
__index = function (tbl, key)
|
||||
if key == "value" then
|
||||
local val = rawget(tbl, "_value")
|
||||
if val == nil then
|
||||
val = rawget(tbl, "default")
|
||||
end
|
||||
return val
|
||||
end
|
||||
return rawget(tbl, key)
|
||||
end,
|
||||
__newindex = function (tbl, key, val)
|
||||
if key == "value" then
|
||||
key = "_value"
|
||||
end
|
||||
rawset(tbl, key, val)
|
||||
if key == "_value" then
|
||||
local v = rawget(tbl, "_view") -- update the config item text in view
|
||||
if v then
|
||||
v:text_set(tostring(tbl))
|
||||
end
|
||||
end
|
||||
end}
|
||||
|
||||
-- the prompt info
|
||||
function config:prompt()
|
||||
|
||||
-- get text (first line in description)
|
||||
local text = self.description or ""
|
||||
if type(text) == "table" then
|
||||
text = text[1] or ""
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
||||
-- to string
|
||||
function config:__tostring()
|
||||
|
||||
-- get text (first line in description)
|
||||
local text = self:prompt()
|
||||
|
||||
-- get value
|
||||
local value = self.value
|
||||
|
||||
-- update text
|
||||
if self.kind == "boolean" or (not self.kind and type(value) == "boolean") then -- boolean config?
|
||||
text = (value and "[*] " or "[ ] ") .. text
|
||||
elseif self.kind == "number" or (not self.kind and type(value) == "number") then -- number config?
|
||||
text = " " .. text .. " (" .. tostring(value or 0) .. ")"
|
||||
elseif self.kind == "string" or (not self.kind and type(value) == "string") then -- string config?
|
||||
text = " " .. text .. " (" .. tostring(value or "") .. ")"
|
||||
elseif self.kind == "choice" then -- choice config?
|
||||
if self.values and #self.values > 0 then
|
||||
text = " " .. text .. " (" .. tostring(self.values[value or 1]) .. ")" .. " --->"
|
||||
else
|
||||
text = " " .. text .. " () ----"
|
||||
end
|
||||
elseif self.kind == "menu" then -- menu config?
|
||||
text = " " .. text .. (self.configs and #self.configs > 0 and " --->" or " ----")
|
||||
end
|
||||
|
||||
-- new config?
|
||||
if self.new and self.kind ~= "choice" and self.kind ~= "menu" then
|
||||
text = text .. " (NEW)"
|
||||
end
|
||||
|
||||
-- ok
|
||||
return text
|
||||
end
|
||||
|
||||
-- save config objects
|
||||
menuconf.config = menuconf.config or config
|
||||
menuconf.menu = menuconf.menu or config { kind = "menu", configs = {} }
|
||||
menuconf.number = menuconf.number or config { kind = "number", default = 0 }
|
||||
menuconf.string = menuconf.string or config { kind = "string", default = "" }
|
||||
menuconf.choice = menuconf.choice or config { kind = "choice", default = 1, values = {} }
|
||||
menuconf.boolean = menuconf.boolean or config { kind = "boolean", default = false }
|
||||
|
||||
-- return module
|
||||
return menuconf
|
99
src/ltui/object.lua
Normal file
99
src/ltui/object.lua
Normal file
@@ -0,0 +1,99 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file object.lua
|
||||
--
|
||||
|
||||
-- define module: object
|
||||
local object = object or {}
|
||||
|
||||
-- taken from 'std' library: http://luaforge.net/projects/stdlib/
|
||||
-- and http://lua-cui.sourceforge.net/
|
||||
--
|
||||
-- local point = object { _init = {"x", "y"} }
|
||||
--
|
||||
-- local p1 = point {1, 2}
|
||||
-- > p1 {x = 1, y = 2}
|
||||
--
|
||||
|
||||
-- permute some indices of a table
|
||||
local function permute (p, t)
|
||||
local u = {}
|
||||
for i, v in pairs (t) do
|
||||
if p[i] ~= nil then
|
||||
u[p[i]] = v
|
||||
else
|
||||
u[i] = v
|
||||
end
|
||||
end
|
||||
return u
|
||||
end
|
||||
|
||||
-- make a shallow copy of a table, including any
|
||||
local function clone (t)
|
||||
local u = setmetatable ({}, getmetatable (t))
|
||||
for i, v in pairs (t) do
|
||||
u[i] = v
|
||||
end
|
||||
return u
|
||||
end
|
||||
|
||||
-- merge two tables
|
||||
--
|
||||
-- If there are duplicate fields, u's will be used. The metatable of
|
||||
-- the returned table is that of t
|
||||
--
|
||||
local function merge (t, u)
|
||||
local r = clone (t)
|
||||
for i, v in pairs (u) do
|
||||
r[i] = v
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
-- root object
|
||||
--
|
||||
-- List of fields to be initialised by the
|
||||
-- constructor: assuming the default _clone, the
|
||||
-- numbered values in an object constructor are
|
||||
-- assigned to the fields given in _init
|
||||
--
|
||||
local object = { _init = {} }
|
||||
setmetatable (object, object)
|
||||
|
||||
-- object constructor
|
||||
--
|
||||
-- @param initial values for fields in
|
||||
--
|
||||
-- @return new object
|
||||
--
|
||||
function object:_clone (values)
|
||||
local object = merge(self, permute(self._init, values or {}))
|
||||
return setmetatable (object, object)
|
||||
end
|
||||
|
||||
-- local x = object {}
|
||||
function object.__call (...)
|
||||
return (...)._clone (...)
|
||||
end
|
||||
|
||||
-- return module: object
|
||||
return object
|
394
src/ltui/panel.lua
Normal file
394
src/ltui/panel.lua
Normal file
@@ -0,0 +1,394 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file panel.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local view = require("ltui/view")
|
||||
local rect = require("ltui/rect")
|
||||
local event = require("ltui/event")
|
||||
local point = require("ltui/point")
|
||||
local curses = require("ltui/curses")
|
||||
local dlist = require("ltui/base/dlist")
|
||||
|
||||
-- define module
|
||||
local panel = panel or view()
|
||||
|
||||
-- init panel
|
||||
function panel:init(name, bounds)
|
||||
|
||||
-- init view
|
||||
view.init(self, name, bounds)
|
||||
|
||||
-- mark as panel
|
||||
self:type_set("panel")
|
||||
|
||||
-- mark as selectable
|
||||
self:option_set("selectable", true)
|
||||
|
||||
-- init child views
|
||||
self._VIEWS = dlist()
|
||||
|
||||
-- init views cache
|
||||
self._VIEWS_CACHE = {}
|
||||
end
|
||||
|
||||
-- get all child views
|
||||
function panel:views()
|
||||
return self._VIEWS:items()
|
||||
end
|
||||
|
||||
-- get views count
|
||||
function panel:count()
|
||||
return self._VIEWS:size()
|
||||
end
|
||||
|
||||
-- is empty?
|
||||
function panel:empty()
|
||||
return self._VIEWS:empty()
|
||||
end
|
||||
|
||||
-- get the first view
|
||||
function panel:first()
|
||||
return self._VIEWS:first()
|
||||
end
|
||||
|
||||
-- get the next view
|
||||
function panel:next(v)
|
||||
return self._VIEWS:next(v)
|
||||
end
|
||||
|
||||
-- get the previous view
|
||||
function panel:prev(v)
|
||||
return self._VIEWS:prev(v)
|
||||
end
|
||||
|
||||
-- get the current selected child view
|
||||
function panel:current()
|
||||
return self._CURRENT
|
||||
end
|
||||
|
||||
-- get view from the given name
|
||||
function panel:view(name)
|
||||
return self._VIEWS_CACHE[name]
|
||||
end
|
||||
|
||||
-- insert view
|
||||
function panel:insert(v, opt)
|
||||
|
||||
-- check
|
||||
assert(not v:parent() or v:parent() == self)
|
||||
assert(not self:view(v:name()), v:name() .. " has been in this panel!")
|
||||
|
||||
-- this view has been inserted into this panel? remove it first
|
||||
if v:parent() == self then
|
||||
self:remove(v)
|
||||
end
|
||||
|
||||
-- center this view if centerx or centery are set
|
||||
local bounds = v:bounds()
|
||||
local center = false
|
||||
local org = point {bounds.sx, bounds.sy}
|
||||
if opt and opt.centerx then
|
||||
org.x = math.floor((self:width() - v:width()) / 2)
|
||||
center = true
|
||||
end
|
||||
if opt and opt.centery then
|
||||
org.y = math.floor((self:height() - v:height()) / 2)
|
||||
center = true
|
||||
end
|
||||
if center then
|
||||
bounds:move(org.x - bounds.sx, org.y - bounds.sy)
|
||||
v:invalidate(true)
|
||||
end
|
||||
|
||||
-- insert this view
|
||||
self._VIEWS:push(v)
|
||||
|
||||
-- cache this view
|
||||
self._VIEWS_CACHE[v:name()] = v
|
||||
|
||||
-- set it's parent view
|
||||
v:parent_set(self)
|
||||
|
||||
-- select this view
|
||||
if v:option("selectable") then
|
||||
self:select(v)
|
||||
end
|
||||
|
||||
-- invalidate it
|
||||
self:invalidate()
|
||||
end
|
||||
|
||||
-- remove view
|
||||
function panel:remove(v)
|
||||
|
||||
-- check
|
||||
assert(v:parent() == self)
|
||||
|
||||
-- remove view
|
||||
self._VIEWS:remove(v)
|
||||
self._VIEWS_CACHE[v:name()] = nil
|
||||
|
||||
-- clear parent
|
||||
v:parent_set(nil)
|
||||
|
||||
-- select next view
|
||||
if self:current() == v then
|
||||
self:select_next(nil, true)
|
||||
end
|
||||
|
||||
-- invalidate it
|
||||
self:invalidate()
|
||||
end
|
||||
|
||||
-- clear views
|
||||
function panel:clear()
|
||||
|
||||
-- clear parents
|
||||
for v in self:views() do
|
||||
v:parent_set(nil)
|
||||
end
|
||||
|
||||
-- clear views and cache
|
||||
self._VIEWS:clear()
|
||||
self._VIEWS_CACHE = {}
|
||||
|
||||
-- reset the current view
|
||||
self._CURRENT = nil
|
||||
|
||||
-- invalidate
|
||||
self:invalidate()
|
||||
end
|
||||
|
||||
-- select the child view
|
||||
function panel:select(v)
|
||||
|
||||
-- check
|
||||
assert(v == nil or (v:parent() == self and v:option("selectable")))
|
||||
|
||||
-- get the current selected view
|
||||
local current = self:current()
|
||||
if v == current then
|
||||
return
|
||||
end
|
||||
|
||||
-- undo the previous selected view
|
||||
if current then
|
||||
|
||||
-- undo the current view first
|
||||
if self:state("focused") then
|
||||
current:state_set("focused", false)
|
||||
end
|
||||
current:state_set("selected", false)
|
||||
end
|
||||
|
||||
-- update the current selected view
|
||||
self._CURRENT = v
|
||||
|
||||
-- update the new selected view
|
||||
if v then
|
||||
|
||||
-- select and focus this view
|
||||
v:state_set("selected", true)
|
||||
if self:state("focused") then
|
||||
v:state_set("focused", true)
|
||||
end
|
||||
end
|
||||
|
||||
-- ok
|
||||
return v
|
||||
end
|
||||
|
||||
-- select the next view
|
||||
function panel:select_next(start, reset)
|
||||
|
||||
-- is empty?
|
||||
if self:empty() then
|
||||
return
|
||||
end
|
||||
|
||||
-- reset?
|
||||
if reset then
|
||||
self._CURRENT = nil
|
||||
end
|
||||
|
||||
-- get current view
|
||||
local current = start or self:current()
|
||||
|
||||
-- select the next view
|
||||
local next = self:next(current)
|
||||
while next ~= current do
|
||||
if next and next:option("selectable") and next:state("visible") then
|
||||
return self:select(next)
|
||||
end
|
||||
next = self:next(next)
|
||||
end
|
||||
end
|
||||
|
||||
-- select the previous view
|
||||
function panel:select_prev(start)
|
||||
|
||||
-- is empty?
|
||||
if self:empty() then
|
||||
return
|
||||
end
|
||||
|
||||
-- reset?
|
||||
if reset then
|
||||
self._CURRENT = nil
|
||||
end
|
||||
|
||||
-- get current view
|
||||
local current = start or self:current()
|
||||
|
||||
-- select the previous view
|
||||
local prev = self:prev(current)
|
||||
while prev ~= current do
|
||||
if prev and prev:option("selectable") and prev:state("visible") then
|
||||
return self:select(prev)
|
||||
end
|
||||
prev = self:prev(prev)
|
||||
end
|
||||
end
|
||||
|
||||
-- on event
|
||||
function panel:event_on(e)
|
||||
|
||||
-- select view?
|
||||
if e.type == event.ev_keyboard then
|
||||
if e.key_name == "Right" then
|
||||
return self:select_next()
|
||||
elseif e.key_name == "Left" then
|
||||
return self:select_prev()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- set state
|
||||
function panel:state_set(name, enable)
|
||||
view.state_set(self, name, enable)
|
||||
if name == "focused" and self:current() then
|
||||
self:current():state_set(name, enable)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-- draw panel
|
||||
function panel:draw(transparent)
|
||||
|
||||
-- redraw panel?
|
||||
local redraw = self:state("redraw")
|
||||
|
||||
-- draw panel background first
|
||||
if redraw then
|
||||
view.draw(self, transparent)
|
||||
end
|
||||
|
||||
-- draw all child views
|
||||
for v in self:views() do
|
||||
if redraw then
|
||||
v:state_set("redraw", true)
|
||||
end
|
||||
if v:state("visible") and (v:state("redraw") or v:type() == "panel") then
|
||||
v:draw(transparent)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- resize panel
|
||||
function panel:resize()
|
||||
|
||||
-- resize panel?
|
||||
local resize = self:state("resize")
|
||||
if resize then
|
||||
view.resize(self)
|
||||
end
|
||||
|
||||
-- resize all child views
|
||||
for v in self:views() do
|
||||
if resize then
|
||||
v:state_set("resize", true)
|
||||
end
|
||||
if v:state("visible") and (v:state("resize") or v:type() == "panel") then
|
||||
v:resize()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- refresh panel
|
||||
function panel:refresh()
|
||||
|
||||
-- need not refresh? do not refresh it
|
||||
if not self:state("refresh") or not self:state("visible") then
|
||||
return
|
||||
end
|
||||
|
||||
-- refresh all child views
|
||||
for v in self:views() do
|
||||
if v:state("refresh") then
|
||||
v:refresh()
|
||||
v:state_set("refresh", false)
|
||||
end
|
||||
end
|
||||
|
||||
-- refresh it
|
||||
view.refresh(self)
|
||||
|
||||
-- clear mark
|
||||
self:state_set("refresh", false)
|
||||
end
|
||||
|
||||
-- dump all views
|
||||
function panel:dump()
|
||||
log:print("%s", self:_tostring(1))
|
||||
end
|
||||
|
||||
-- tostring(panel, level)
|
||||
function panel:_tostring(level)
|
||||
local str = ""
|
||||
if self.views then
|
||||
str = str .. string.format("<%s %s>", self:name(), tostring(self:bounds()))
|
||||
if not self:empty() then
|
||||
str = str .. "\n"
|
||||
end
|
||||
for v in self:views() do
|
||||
for l = 1, level do
|
||||
str = str .. " "
|
||||
end
|
||||
str = str .. panel._tostring(v, level + 1) .. "\n"
|
||||
end
|
||||
else
|
||||
str = tostring(self)
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
-- tostring(panel)
|
||||
function panel:__tostring()
|
||||
return string.format("<panel(%s) %s>", self:name(), tostring(self:bounds()))
|
||||
end
|
||||
|
||||
|
||||
-- return module
|
||||
return panel
|
104
src/ltui/point.lua
Normal file
104
src/ltui/point.lua
Normal file
@@ -0,0 +1,104 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file point.lua
|
||||
--
|
||||
|
||||
--[[ Console User Interface (cui) ]-----------------------------------------
|
||||
Author: Tiago Dionizio (tiago.dionizio AT gmail.com)
|
||||
$Id: point.lua 18 2007-06-21 20:43:52Z tngd $
|
||||
--------------------------------------------------------------------------]]
|
||||
|
||||
-- load modules
|
||||
local object = require("ltui/object")
|
||||
|
||||
-- define module
|
||||
local point = point or object { _init = {"x", "y"} }
|
||||
|
||||
-- add delta x and y
|
||||
function point:addxy(dx, dy)
|
||||
self.x = self.x + dx
|
||||
self.y = self.y + dy
|
||||
return self
|
||||
end
|
||||
|
||||
-- add point
|
||||
function point:add(p)
|
||||
return self:addxy(p.x, p.y)
|
||||
end
|
||||
|
||||
-- sub delta x and y
|
||||
function point:subxy(dx, dy)
|
||||
return self:addxy(-dx, -dy)
|
||||
end
|
||||
|
||||
-- sub point
|
||||
function point:sub(p)
|
||||
return self:addxy(-p.x, -p.y)
|
||||
end
|
||||
|
||||
-- p1 + p2
|
||||
function point:__add(p)
|
||||
local np = self()
|
||||
np.x = np.x + p.x
|
||||
np.y = np.y + p.y
|
||||
return np
|
||||
end
|
||||
|
||||
-- p1 - p2
|
||||
function point:__sub(p)
|
||||
local np = self()
|
||||
np.x = np.x - p.x
|
||||
np.y = np.y - p.y
|
||||
return np
|
||||
end
|
||||
|
||||
-- -p
|
||||
function point:__unm()
|
||||
local p = self()
|
||||
p.x = -p.x
|
||||
p.y = -p.y
|
||||
return p
|
||||
end
|
||||
|
||||
-- p1 == p2?
|
||||
function point:__eq(p)
|
||||
return self.x == p.x and self.y == p.y
|
||||
end
|
||||
|
||||
-- tostring(p)
|
||||
function point:__tostring()
|
||||
return '(' .. self.x .. ', ' .. self.y .. ')'
|
||||
end
|
||||
|
||||
-- p1 .. p2
|
||||
function point.__concat(op1, op2)
|
||||
if type(op1) == 'string' then
|
||||
return op1 .. op2:__tostring()
|
||||
elseif type(op2) == 'string' then
|
||||
return op1:__tostring() .. op2
|
||||
else
|
||||
return op1:__tostring() .. op2:__tostring()
|
||||
end
|
||||
end
|
||||
|
||||
-- return module
|
||||
return point
|
456
src/ltui/program.lua
Normal file
456
src/ltui/program.lua
Normal file
@@ -0,0 +1,456 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file program.lua
|
||||
--
|
||||
|
||||
--[[ Console User Interface (cui) ]-----------------------------------------
|
||||
Author: Tiago Dionizio (tiago.dionizio AT gmail.com)
|
||||
$Id: program.lua 18 2007-06-21 20:43:52Z tngd $
|
||||
--------------------------------------------------------------------------]]
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local rect = require("ltui/rect")
|
||||
local point = require("ltui/point")
|
||||
local panel = require("ltui/panel")
|
||||
local event = require("ltui/event")
|
||||
local curses = require("ltui/curses")
|
||||
|
||||
-- define module
|
||||
local program = program or panel()
|
||||
|
||||
-- init program
|
||||
function program:init(name, argv)
|
||||
|
||||
-- init main window
|
||||
local main_window = self:main_window()
|
||||
|
||||
-- disable echo
|
||||
curses.echo(false)
|
||||
|
||||
-- disable input cache
|
||||
curses.cbreak(true)
|
||||
|
||||
-- disable newline
|
||||
curses.nl(false)
|
||||
|
||||
-- to filter characters being output to the screen
|
||||
-- this will filter all characters where a chtype or chstr is used
|
||||
curses.map_output(true)
|
||||
|
||||
-- on WIN32 ALT keys need to be mapped, so to make sure you get the wanted keys,
|
||||
-- only makes sense when using keypad(true) and echo(false)
|
||||
curses.map_keyboard(true)
|
||||
|
||||
-- init colors
|
||||
if (curses.has_colors()) then
|
||||
curses.start_color()
|
||||
end
|
||||
|
||||
-- disable main window cursor
|
||||
main_window:leaveok(false)
|
||||
|
||||
-- enable special key map
|
||||
main_window:keypad(true)
|
||||
|
||||
-- non-block for getch()
|
||||
main_window:nodelay(true)
|
||||
|
||||
-- get 8-bits character for getch()
|
||||
main_window:meta(true)
|
||||
|
||||
-- save the current arguments
|
||||
self._ARGV = argv
|
||||
|
||||
-- init panel
|
||||
panel.init(self, name, rect {0, 0, curses.columns(), curses.lines()})
|
||||
|
||||
-- init state
|
||||
self:state_set("focused", true)
|
||||
self:state_set("selected", true)
|
||||
end
|
||||
|
||||
-- exit program
|
||||
function program:exit()
|
||||
|
||||
-- exit panel
|
||||
panel.exit(self)
|
||||
|
||||
-- (attempt to) make sure the screen will be cleared
|
||||
-- if not restored by the curses driver
|
||||
self:main_window():clear()
|
||||
self:main_window():noutrefresh()
|
||||
curses.doupdate()
|
||||
|
||||
-- exit curses
|
||||
assert(not curses.isdone())
|
||||
curses.done()
|
||||
end
|
||||
|
||||
-- get the main window
|
||||
function program:main_window()
|
||||
|
||||
-- init main window if not exists
|
||||
local main_window = self._MAIN_WINDOW
|
||||
if not main_window then
|
||||
|
||||
-- init main window
|
||||
main_window = curses.init()
|
||||
assert(main_window, "cannot init main window!")
|
||||
|
||||
-- save main window
|
||||
self._MAIN_WINDOW = main_window
|
||||
end
|
||||
return main_window
|
||||
end
|
||||
|
||||
-- get the command arguments
|
||||
function program:argv()
|
||||
return self._ARGV
|
||||
end
|
||||
|
||||
-- get the current event
|
||||
function program:event()
|
||||
|
||||
-- get event from the event queue first
|
||||
local event_queue = self._EVENT_QUEUE
|
||||
if event_queue then
|
||||
local e = event_queue[1]
|
||||
if e then
|
||||
table.remove(event_queue, 1)
|
||||
return e
|
||||
end
|
||||
end
|
||||
|
||||
-- get input key
|
||||
local key_code, key_name, key_meta = self:_input_key()
|
||||
if key_code then
|
||||
return event.keyboard{key_code, key_name, key_meta}
|
||||
end
|
||||
end
|
||||
|
||||
-- on event
|
||||
function program:event_on(e)
|
||||
|
||||
-- get the top focused view
|
||||
local focused_view = self
|
||||
while focused_view:type() == "panel" and focused_view:current() do
|
||||
focused_view = focused_view:current()
|
||||
end
|
||||
|
||||
-- do event for focused views
|
||||
while focused_view and focused_view ~= self do
|
||||
local parent = focused_view:parent()
|
||||
if focused_view:event_on(e) then
|
||||
return true
|
||||
end
|
||||
focused_view = parent
|
||||
end
|
||||
|
||||
-- quit program?
|
||||
if e.type == event.ev_keyboard and e.key_name == "CtrlC" then
|
||||
self:send("cm_exit")
|
||||
return true
|
||||
elseif event.is_command(e, "cm_exit") then
|
||||
self:quit()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- put an event to view
|
||||
function program:event_put(e)
|
||||
|
||||
-- init event queue
|
||||
self._EVENT_QUEUE = self._EVENT_QUEUE or {}
|
||||
|
||||
-- put event to queue
|
||||
table.insert(self._EVENT_QUEUE, e)
|
||||
end
|
||||
|
||||
-- send command
|
||||
function program:send(command, extra)
|
||||
self:event_put(event.command {command, extra})
|
||||
end
|
||||
|
||||
-- quit program
|
||||
function program:quit()
|
||||
self:send("cm_quit")
|
||||
end
|
||||
|
||||
-- run program loop
|
||||
function program:loop(argv)
|
||||
|
||||
-- do message loop
|
||||
local e = nil
|
||||
local sleep = true
|
||||
while true do
|
||||
|
||||
-- get the current event
|
||||
e = self:event()
|
||||
|
||||
-- do event
|
||||
if e then
|
||||
event.dump(e)
|
||||
self:event_on(e)
|
||||
sleep = false
|
||||
else
|
||||
-- do idle event
|
||||
self:event_on(event.idle())
|
||||
sleep = true
|
||||
end
|
||||
|
||||
-- quit?
|
||||
if e and event.is_command(e, "cm_quit") then
|
||||
break
|
||||
end
|
||||
|
||||
-- resize views
|
||||
self:resize()
|
||||
|
||||
-- draw views
|
||||
self:draw()
|
||||
|
||||
-- refresh views
|
||||
self:refresh()
|
||||
|
||||
-- wait some time, 50ms
|
||||
if sleep then
|
||||
curses.napms(50)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- refresh program
|
||||
function program:refresh()
|
||||
|
||||
-- need not refresh? do not refresh it
|
||||
if not self:state("refresh") then
|
||||
return
|
||||
end
|
||||
|
||||
-- refresh views
|
||||
panel.refresh(self)
|
||||
|
||||
-- trace
|
||||
log:print("%s: refresh ..", self)
|
||||
|
||||
-- get main window
|
||||
local main_window = curses.main_window()
|
||||
|
||||
-- refresh main window
|
||||
self:window():copy(main_window, 0, 0, 0, 0, self:height() - 1, self:width() - 1)
|
||||
|
||||
-- refresh cursor
|
||||
self:_refresh_cursor()
|
||||
|
||||
-- mark as refresh
|
||||
main_window:noutrefresh()
|
||||
|
||||
-- do update
|
||||
curses.doupdate()
|
||||
end
|
||||
|
||||
-- get key map
|
||||
function program:_key_map()
|
||||
if not self._KEYMAP then
|
||||
self._KEYMAP =
|
||||
{
|
||||
[ 1] = "CtrlA", [ 2] = "CtrlB", [ 3] = "CtrlC",
|
||||
[ 4] = "CtrlD", [ 5] = "CtrlE", [ 6] = "CtrlF",
|
||||
[ 7] = "CtrlG", [ 8] = "CtrlH", [ 9] = "CtrlI",
|
||||
[10] = "CtrlJ", [11] = "CtrlK", [12] = "CtrlL",
|
||||
[13] = "CtrlM", [14] = "CtrlN", [15] = "CtrlO",
|
||||
[16] = "CtrlP", [17] = "CtrlQ", [18] = "CtrlR",
|
||||
[19] = "CtrlS", [20] = "CtrlT", [21] = "CtrlU",
|
||||
[22] = "CtrlV", [23] = "CtrlW", [24] = "CtrlX",
|
||||
[25] = "CtrlY", [26] = "CtrlZ",
|
||||
|
||||
[ 8] = "Backspace",
|
||||
[ 9] = "Tab",
|
||||
[ 10] = "Enter",
|
||||
[ 13] = "Enter",
|
||||
[ 27] = "Esc",
|
||||
[ 31] = "CtrlBackspace",
|
||||
[127] = "Backspace",
|
||||
|
||||
[curses.KEY_DOWN ] = "Down",
|
||||
[curses.KEY_UP ] = "Up",
|
||||
[curses.KEY_LEFT ] = "Left",
|
||||
[curses.KEY_RIGHT ] = "Right",
|
||||
[curses.KEY_HOME ] = "Home",
|
||||
[curses.KEY_END ] = "End",
|
||||
[curses.KEY_NPAGE ] = "PageDown",
|
||||
[curses.KEY_PPAGE ] = "PageUp",
|
||||
[curses.KEY_IC ] = "Insert",
|
||||
[curses.KEY_DC ] = "Delete",
|
||||
[curses.KEY_BACKSPACE ] = "Backspace",
|
||||
[curses.KEY_F1 ] = "F1",
|
||||
[curses.KEY_F2 ] = "F2",
|
||||
[curses.KEY_F3 ] = "F3",
|
||||
[curses.KEY_F4 ] = "F4",
|
||||
[curses.KEY_F5 ] = "F5",
|
||||
[curses.KEY_F6 ] = "F6",
|
||||
[curses.KEY_F7 ] = "F7",
|
||||
[curses.KEY_F8 ] = "F8",
|
||||
[curses.KEY_F9 ] = "F9",
|
||||
[curses.KEY_F10 ] = "F10",
|
||||
[curses.KEY_F11 ] = "F11",
|
||||
[curses.KEY_F12 ] = "F12",
|
||||
|
||||
[curses.KEY_RESIZE ] = "Resize",
|
||||
[curses.KEY_REFRESH ] = "Refresh",
|
||||
|
||||
[curses.KEY_BTAB ] = "ShiftTab",
|
||||
[curses.KEY_SDC ] = "ShiftDelete",
|
||||
[curses.KEY_SIC ] = "ShiftInsert",
|
||||
[curses.KEY_SEND ] = "ShiftEnd",
|
||||
[curses.KEY_SHOME ] = "ShiftHome",
|
||||
[curses.KEY_SLEFT ] = "ShiftLeft",
|
||||
[curses.KEY_SRIGHT ] = "ShiftRight",
|
||||
}
|
||||
end
|
||||
return self._KEYMAP
|
||||
end
|
||||
|
||||
-- get input key
|
||||
function program:_input_key()
|
||||
|
||||
-- get main window
|
||||
local main_window = self:main_window()
|
||||
|
||||
-- get input character
|
||||
local ch = main_window:getch()
|
||||
if not ch then
|
||||
return
|
||||
end
|
||||
|
||||
-- this is the time limit in ms within Esc-key sequences are detected as
|
||||
-- Alt-letter sequences. useful when we can't generate Alt-letter sequences
|
||||
-- directly. sometimes this pause may be longer than expected since the
|
||||
-- curses driver may also pause waiting for another key (ncurses-5.3)
|
||||
local esc_delay = 400
|
||||
|
||||
-- get key map
|
||||
local key_map = self:_key_map()
|
||||
|
||||
-- is alt?
|
||||
local alt = ch == 27
|
||||
if alt then
|
||||
|
||||
-- get the next input character
|
||||
ch = main_window:getch()
|
||||
if not ch then
|
||||
|
||||
-- since there is no way to know the time with millisecond precision
|
||||
-- we pause the the program until we get a key or the time limit
|
||||
-- is reached
|
||||
local t = 0
|
||||
while true do
|
||||
ch = main_window:getch()
|
||||
if ch or t >= esc_delay then
|
||||
break
|
||||
end
|
||||
|
||||
-- wait some time, 50ms
|
||||
curses.napms(50)
|
||||
t = t + 50
|
||||
end
|
||||
|
||||
-- nothing was typed... return Esc
|
||||
if not ch then
|
||||
return 27, "Esc", false
|
||||
end
|
||||
end
|
||||
if ch > 96 and ch < 123 then
|
||||
ch = ch - 32
|
||||
end
|
||||
end
|
||||
|
||||
-- map character to key
|
||||
local key = key_map[ch]
|
||||
local key_name = nil
|
||||
if key then
|
||||
key_name = alt and "Alt".. key or key
|
||||
elseif (ch < 256) then
|
||||
key_name = alt and "Alt".. string.char(ch) or string.char(ch)
|
||||
else
|
||||
return ch, '(noname)', alt
|
||||
end
|
||||
|
||||
-- return key info
|
||||
return ch, key_name, alt
|
||||
end
|
||||
|
||||
-- refresh cursor
|
||||
function program:_refresh_cursor()
|
||||
|
||||
-- get the top focused view
|
||||
local focused_view = self
|
||||
while focused_view:type() == "panel" and focused_view:current() do
|
||||
focused_view = focused_view:current()
|
||||
end
|
||||
|
||||
-- get the cursor state of the top focused view
|
||||
local cursor_state = 0
|
||||
if focused_view and focused_view:state("cursor_visible") then
|
||||
cursor_state = focused_view:state("block_cursor") and 2 or 1
|
||||
end
|
||||
|
||||
-- get the cursor position
|
||||
local cursor = focused_view and focused_view:cursor()() or point{0, 0}
|
||||
if cursor_state ~= 0 then
|
||||
local v = focused_view
|
||||
while v:parent() do
|
||||
|
||||
-- update the cursor position
|
||||
cursor:addxy(v:bounds().sx, v:bounds().sy)
|
||||
|
||||
-- is cursor visible?
|
||||
if cursor.x < 0 or cursor.y < 0 or cursor.x >= v:parent():width() or cursor.y >= v:parent():height() then
|
||||
cursor_state = 0
|
||||
break
|
||||
end
|
||||
|
||||
-- get the parent view
|
||||
v = v:parent()
|
||||
end
|
||||
end
|
||||
|
||||
-- update the cursor state
|
||||
curses.cursor_set(cursor_state)
|
||||
|
||||
-- get main window
|
||||
local main_window = curses.main_window()
|
||||
|
||||
-- trace
|
||||
log:print("cursor(%s): %s, %d", focused_view, cursor, cursor_state)
|
||||
|
||||
-- move cursor position
|
||||
if cursor_state ~= 0 then
|
||||
main_window:move(cursor.y, cursor.x)
|
||||
else
|
||||
main_window:move(self:height() - 1, self:width() - 1)
|
||||
end
|
||||
end
|
||||
|
||||
-- return module
|
||||
return program
|
179
src/ltui/rect.lua
Normal file
179
src/ltui/rect.lua
Normal file
@@ -0,0 +1,179 @@
|
||||
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file rect.lua
|
||||
--
|
||||
|
||||
--[[ Console User Interface (cui) ]-----------------------------------------
|
||||
Author: Tiago Dionizio (tiago.dionizio AT gmail.com)
|
||||
$Id: rect.lua 18 2007-06-21 20:43:52Z tngd $
|
||||
--------------------------------------------------------------------------]]
|
||||
|
||||
-- load modules
|
||||
local point = require("ltui/point")
|
||||
local object = require("ltui/object")
|
||||
|
||||
-- define module
|
||||
local rect = rect or object { _init = {"sx", "sy", "ex", "ey"} }
|
||||
|
||||
-- make rect
|
||||
function rect:new(x, y, w, h)
|
||||
return rect { x, y, x + w, y + h }
|
||||
end
|
||||
|
||||
-- get rect size
|
||||
function rect:size()
|
||||
return point { self.ex - self.sx, self.ey - self.sy }
|
||||
end
|
||||
|
||||
-- get width
|
||||
function rect:width()
|
||||
return self.ex - self.sx
|
||||
end
|
||||
|
||||
-- get height
|
||||
function rect:height()
|
||||
return self.ey - self.sy
|
||||
end
|
||||
|
||||
-- resize rect
|
||||
function rect:resize(w, h)
|
||||
self.ex = self.sx + w
|
||||
self.ey = self.sy + h
|
||||
end
|
||||
|
||||
-- move rect
|
||||
function rect:move(dx, dy)
|
||||
self.sx = self.sx + dx
|
||||
self.sy = self.sy + dy
|
||||
self.ex = self.ex + dx
|
||||
self.ey = self.ey + dy
|
||||
return self
|
||||
end
|
||||
|
||||
-- move rect to the given position
|
||||
function rect:move2(x, y)
|
||||
local w = self.ex - self.sx
|
||||
local h = self.ey - self.sy
|
||||
self.sx = x
|
||||
self.sy = y
|
||||
self.ex = x + w
|
||||
self.ey = y + h
|
||||
return self
|
||||
end
|
||||
|
||||
-- move top right corner of the rect
|
||||
function rect:moves(dx, dy)
|
||||
self.sx = self.sx + dx
|
||||
self.sy = self.sy + dy
|
||||
return self
|
||||
end
|
||||
|
||||
-- move bottom left corner of the rect
|
||||
function rect:movee(dx, dy)
|
||||
self.ex = self.ex + dx
|
||||
self.ey = self.ey + dy
|
||||
return self
|
||||
end
|
||||
|
||||
-- expand rect area
|
||||
function rect:grow(dx, dy)
|
||||
self.sx = self.sx - dx
|
||||
self.sy = self.sy - dy
|
||||
self.ex = self.ex + dx
|
||||
self.ey = self.ey + dy
|
||||
return self
|
||||
end
|
||||
|
||||
-- is intersect?
|
||||
function rect:is_intersect(r)
|
||||
return not self():intersect(r):empty()
|
||||
end
|
||||
|
||||
-- set rect with shared area between this rect and a given rect
|
||||
function rect:intersect(r)
|
||||
self.sx = math.max(self.sx, r.sx)
|
||||
self.sy = math.max(self.sy, r.sy)
|
||||
self.ex = math.min(self.ex, r.ex)
|
||||
self.ey = math.min(self.ey, r.ey)
|
||||
return self
|
||||
end
|
||||
|
||||
-- get rect with shared area between two rects: local rect_new = r1 / r2
|
||||
function rect:__div(r)
|
||||
return self():intersect(r)
|
||||
end
|
||||
|
||||
-- set union rect
|
||||
function rect:union(r)
|
||||
self.sx = math.min(self.sx, r.sx)
|
||||
self.sy = math.min(self.sy, r.sy)
|
||||
self.ex = math.max(self.ex, r.ex)
|
||||
self.ey = math.max(self.ey, r.ey)
|
||||
return self
|
||||
end
|
||||
|
||||
-- get union rect: local rect_new = r1 + r2
|
||||
function rect:__add(r)
|
||||
return self():union(r)
|
||||
end
|
||||
|
||||
-- r1 == r1?
|
||||
function rect:__eq(r)
|
||||
return
|
||||
self.sx == r.sx and
|
||||
self.sy == r.sy and
|
||||
self.ex == r.ex and
|
||||
self.ey == r.ey
|
||||
end
|
||||
|
||||
-- contains the given point in rect?
|
||||
function rect:contains(x, y)
|
||||
return x >= self.sx and x < self.ex and y >= self.sy and y < self.ey
|
||||
end
|
||||
|
||||
-- empty rect?
|
||||
function rect:empty()
|
||||
return self.sx >= self.ex or self.sy >= self.ey
|
||||
end
|
||||
|
||||
-- tostring(r)
|
||||
function rect:__tostring()
|
||||
if self:empty() then
|
||||
return '[]'
|
||||
end
|
||||
return string.format("[%d, %d, %d, %d]", self.sx, self.sy, self.ex, self.ey)
|
||||
end
|
||||
|
||||
-- r1 .. r2
|
||||
function rect.__concat(op1, op2)
|
||||
if type(op1) == 'string' then
|
||||
return op1 .. op2:__tostring()
|
||||
elseif type(op2) == 'string' then
|
||||
return op1:__tostring() .. op2
|
||||
else
|
||||
return op1:__tostring() .. op2:__tostring()
|
||||
end
|
||||
end
|
||||
|
||||
-- return module
|
||||
return rect
|
58
src/ltui/statusbar.lua
Normal file
58
src/ltui/statusbar.lua
Normal file
@@ -0,0 +1,58 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file statusbar.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local rect = require("ltui/rect")
|
||||
local panel = require("ltui/panel")
|
||||
local label = require("ltui/label")
|
||||
local event = require("ltui/event")
|
||||
local curses = require("ltui/curses")
|
||||
|
||||
-- define module
|
||||
local statusbar = statusbar or panel()
|
||||
|
||||
-- init statusbar
|
||||
function statusbar:init(name, bounds)
|
||||
|
||||
-- init panel
|
||||
panel.init(self, name, bounds)
|
||||
|
||||
-- init info
|
||||
self._INFO = label:new("statusbar.info", rect{0, 0, self:width(), self:height()})
|
||||
self:insert(self:info())
|
||||
self:info():text_set("Status Bar")
|
||||
self:info():textattr_set("blue")
|
||||
|
||||
-- init background
|
||||
self:background_set("white")
|
||||
end
|
||||
|
||||
-- get status info
|
||||
function statusbar:info()
|
||||
return self._INFO
|
||||
end
|
||||
|
||||
-- return module
|
||||
return statusbar
|
120
src/ltui/textarea.lua
Normal file
120
src/ltui/textarea.lua
Normal file
@@ -0,0 +1,120 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file textarea.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ui/log")
|
||||
local view = require("ui/view")
|
||||
local label = require("ui/label")
|
||||
local event = require("ui/event")
|
||||
local curses = require("ui/curses")
|
||||
|
||||
-- define module
|
||||
local textarea = textarea or label()
|
||||
|
||||
-- init textarea
|
||||
function textarea:init(name, bounds, text)
|
||||
|
||||
-- init label
|
||||
label.init(self, 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
|
||||
end
|
||||
|
||||
-- draw textarea
|
||||
function textarea:draw(transparent)
|
||||
|
||||
-- draw background
|
||||
view.draw(self, transparent)
|
||||
|
||||
-- get the text attribute value
|
||||
local textattr = self:textattr_val()
|
||||
|
||||
-- draw text string
|
||||
local strs = self._SPLITTEXT
|
||||
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 progress = (self._STARTLINE + math.min(self:height(), self._LINECOUNT)) * 100 / self._LINECOUNT
|
||||
if (self._STARTLINE > 0 or progress < 100) and self:width() > 20 then
|
||||
self:canvas():move(self:width() - 10, self:height() - 1):putstr(string.format("(%%%d)", progress))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- set text
|
||||
function textarea:text_set(text)
|
||||
self._STARTLINE = 0
|
||||
self._SPLITTEXT = text and self:splitext(text) or {}
|
||||
self._LINECOUNT = #self._SPLITTEXT
|
||||
return label.text_set(self, text)
|
||||
end
|
||||
|
||||
-- scroll
|
||||
function textarea:scroll(lines)
|
||||
if self._LINECOUNT > self:height() 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()
|
||||
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()
|
||||
self:invalidate()
|
||||
end
|
||||
end
|
||||
|
||||
-- on event
|
||||
function textarea:event_on(e)
|
||||
if e.type == event.ev_keyboard then
|
||||
if e.key_name == "Up" then
|
||||
self:scroll(-5)
|
||||
return true
|
||||
elseif e.key_name == "Down" then
|
||||
self:scroll(5)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- return module
|
||||
return textarea
|
72
src/ltui/textdialog.lua
Normal file
72
src/ltui/textdialog.lua
Normal file
@@ -0,0 +1,72 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file textdialog.lua
|
||||
--
|
||||
|
||||
-- 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")
|
||||
|
||||
-- define module
|
||||
local textdialog = textdialog or dialog()
|
||||
|
||||
-- init dialog
|
||||
function textdialog:init(name, bounds, title)
|
||||
|
||||
-- init window
|
||||
dialog.init(self, name, bounds, title)
|
||||
|
||||
-- insert text
|
||||
self:panel():insert(self:text())
|
||||
|
||||
-- select buttons by default
|
||||
self:panel():select(self:buttons())
|
||||
end
|
||||
|
||||
-- get text
|
||||
function textdialog:text()
|
||||
if not self._TEXT then
|
||||
self._TEXT = textarea:new("textdialog.text", rect:new(0, 0, self:panel():width(), self:panel():height() - 1))
|
||||
end
|
||||
return self._TEXT
|
||||
end
|
||||
|
||||
-- on event
|
||||
function textdialog:event_on(e)
|
||||
|
||||
-- pass event to dialog
|
||||
if dialog.event_on(self, e) then
|
||||
return true
|
||||
end
|
||||
|
||||
-- pass keyboard event to text area to scroll
|
||||
if e.type == event.ev_keyboard then
|
||||
return self:text():event_on(e)
|
||||
end
|
||||
end
|
||||
|
||||
-- return module
|
||||
return textdialog
|
108
src/ltui/textedit.lua
Normal file
108
src/ltui/textedit.lua
Normal file
@@ -0,0 +1,108 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file textedit.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ui/log")
|
||||
local view = require("ui/view")
|
||||
local label = require("ui/label")
|
||||
local event = require("ui/event")
|
||||
local border = require("ui/border")
|
||||
local curses = require("ui/curses")
|
||||
local textarea = require("ui/textarea")
|
||||
|
||||
-- define module
|
||||
local textedit = textedit or textarea()
|
||||
|
||||
-- init textedit
|
||||
function textedit:init(name, bounds, text)
|
||||
|
||||
-- init label
|
||||
textarea.init(self, name, bounds, text)
|
||||
|
||||
-- show cursor
|
||||
self:cursor_show(true)
|
||||
|
||||
-- mark as selectable
|
||||
self:option_set("selectable", true)
|
||||
|
||||
-- disable progress
|
||||
self:option_set("progress", false)
|
||||
|
||||
-- enable multiple line
|
||||
self:option_set("multiline", true)
|
||||
end
|
||||
|
||||
-- draw textedit
|
||||
function textedit:draw(transparent)
|
||||
|
||||
-- draw label
|
||||
textarea.draw(self, transparent)
|
||||
|
||||
-- move cursor
|
||||
if not self:text() or #self:text() == 0 then
|
||||
self:cursor_move(0, 0)
|
||||
else
|
||||
self:cursor_move(self:canvas():pos())
|
||||
end
|
||||
end
|
||||
|
||||
-- set text
|
||||
function textedit:text_set(text)
|
||||
textarea.text_set(self, text)
|
||||
self:scroll_to_end()
|
||||
return self
|
||||
end
|
||||
|
||||
-- on event
|
||||
function textedit:event_on(e)
|
||||
|
||||
-- update text
|
||||
if e.type == event.ev_keyboard then
|
||||
if e.key_code > 0x1f and e.key_code < 0x7f then
|
||||
self:text_set(self:text() .. e.key_name)
|
||||
return true
|
||||
elseif e.key_name == "Enter" and self:option("multiline") then
|
||||
self:text_set(self:text() .. '\n')
|
||||
return true
|
||||
elseif e.key_name == "Backspace" then
|
||||
local text = self:text()
|
||||
if #text > 0 then
|
||||
self:text_set(text:sub(1, #text - 1))
|
||||
end
|
||||
return true
|
||||
elseif e.key_name == "CtrlV" then
|
||||
local pastetext = os.pbpaste()
|
||||
if pastetext then
|
||||
self:text_set(self:text() .. pastetext)
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- do textarea event
|
||||
return textarea.event_on(self, e)
|
||||
end
|
||||
|
||||
-- return module
|
||||
return textedit
|
481
src/ltui/view.lua
Normal file
481
src/ltui/view.lua
Normal file
@@ -0,0 +1,481 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file view.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ltui/base/log")
|
||||
local rect = require("ltui/rect")
|
||||
local point = require("ltui/point")
|
||||
local object = require("ltui/object")
|
||||
local canvas = require("ltui/canvas")
|
||||
local curses = require("ltui/curses")
|
||||
|
||||
-- define module
|
||||
local view = view or object()
|
||||
|
||||
-- new view instance
|
||||
function view:new(name, bounds, ...)
|
||||
|
||||
-- create instance
|
||||
self = self()
|
||||
|
||||
-- init view
|
||||
self:init(name, bounds, ...)
|
||||
|
||||
-- done
|
||||
return self
|
||||
end
|
||||
|
||||
-- init view
|
||||
function view:init(name, bounds)
|
||||
|
||||
-- check
|
||||
assert(name and type(bounds) == 'table')
|
||||
|
||||
-- init type
|
||||
self._TYPE = "view"
|
||||
|
||||
-- init state
|
||||
local state = object()
|
||||
state.visible = true -- view visibility
|
||||
state.cursor_visible = false -- cursor visibility
|
||||
state.block_cursor = false -- block cursor
|
||||
state.selected = false -- is selected?
|
||||
state.focused = false -- is focused?
|
||||
state.redraw = true -- need redraw
|
||||
state.refresh = true -- need refresh
|
||||
state.resize = true -- need resize
|
||||
self._STATE = state
|
||||
|
||||
-- init options
|
||||
local options = object()
|
||||
options.selectable = false -- true if window can be selected
|
||||
self._OPTIONS = options
|
||||
|
||||
-- init attributes
|
||||
self._ATTRS = object()
|
||||
|
||||
-- init actions
|
||||
self._ACTIONS = object()
|
||||
|
||||
-- init extras
|
||||
self._EXTRAS = object()
|
||||
|
||||
-- init name
|
||||
self._NAME = name
|
||||
|
||||
-- init cursor
|
||||
self._CURSOR = point{0, 0}
|
||||
|
||||
-- init bounds and window
|
||||
self:bounds_set(bounds)
|
||||
end
|
||||
|
||||
-- exit view
|
||||
function view:exit()
|
||||
|
||||
-- close window
|
||||
if self:window() then
|
||||
self:window():close()
|
||||
self._WINDOW = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- get view name
|
||||
function view:name()
|
||||
return self._NAME
|
||||
end
|
||||
|
||||
-- get view bounds
|
||||
function view:bounds()
|
||||
return self._BOUNDS
|
||||
end
|
||||
|
||||
-- set window bounds
|
||||
function view:bounds_set(bounds)
|
||||
if bounds and self:bounds() ~= bounds then
|
||||
self._BOUNDS = bounds()
|
||||
self:invalidate(true)
|
||||
end
|
||||
end
|
||||
|
||||
-- get view width
|
||||
function view:width()
|
||||
return self:bounds():width()
|
||||
end
|
||||
|
||||
-- get view height
|
||||
function view:height()
|
||||
return self:bounds():height()
|
||||
end
|
||||
|
||||
-- get view size
|
||||
function view:size()
|
||||
return self:bounds():size()
|
||||
end
|
||||
|
||||
-- get the parent view
|
||||
function view:parent()
|
||||
return self._PARENT
|
||||
end
|
||||
|
||||
-- set the parent view
|
||||
function view:parent_set(parent)
|
||||
self._PARENT = parent
|
||||
end
|
||||
|
||||
-- get the application
|
||||
function view:application()
|
||||
if not self._APPLICATION then
|
||||
local app = self
|
||||
while app:parent() do
|
||||
app = app:parent()
|
||||
end
|
||||
self._APPLICATION = app
|
||||
end
|
||||
return self._APPLICATION
|
||||
end
|
||||
|
||||
-- get the view window
|
||||
function view:window()
|
||||
return self._WINDOW
|
||||
end
|
||||
|
||||
-- get the view canvas
|
||||
function view:canvas()
|
||||
if not self._CANVAS then
|
||||
self._CANVAS = canvas:new(self, self:window())
|
||||
end
|
||||
return self._CANVAS
|
||||
end
|
||||
|
||||
-- draw view
|
||||
function view:draw(transparent)
|
||||
|
||||
-- trace
|
||||
log:print("%s: draw ..", self)
|
||||
|
||||
-- draw background
|
||||
if not transparent then
|
||||
local background = self:background()
|
||||
if background then
|
||||
background = curses.color_pair(background, background)
|
||||
self:canvas():attr(background):move(0, 0):putchar(' ', self:width() * self:height())
|
||||
else
|
||||
self:canvas():clear()
|
||||
end
|
||||
end
|
||||
|
||||
-- clear mark
|
||||
self:state_set("redraw", false)
|
||||
self:_mark_refresh()
|
||||
end
|
||||
|
||||
-- refresh view
|
||||
function view:refresh()
|
||||
|
||||
-- refresh to the parent view
|
||||
local parent = self:parent()
|
||||
if parent and self:state("visible") then
|
||||
|
||||
-- clip bounds with the parent view
|
||||
local bounds = self:bounds()
|
||||
local r = bounds():intersect(rect{0, 0, parent:width(), parent:height()})
|
||||
if not r:empty() then
|
||||
|
||||
-- trace
|
||||
log:print("%s: refresh to %s(%d, %d, %d, %d) ..", self, parent:name(), r.sx, r.sy, r.ex, r.ey)
|
||||
|
||||
-- copy this view to parent view
|
||||
self:window():copy(parent:window(), 0, 0, r.sy, r.sx, r.ey - 1, r.ex - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- resize bounds of inner child views (abstract)
|
||||
function view:resize()
|
||||
|
||||
-- trace
|
||||
log:print("%s: resize ..", self)
|
||||
|
||||
-- close the previous windows first
|
||||
if self:window() then
|
||||
self:window():close()
|
||||
self._WINDOW = nil
|
||||
end
|
||||
|
||||
-- need renew canvas
|
||||
self._CANVAS = nil
|
||||
|
||||
-- create a new window
|
||||
self._WINDOW = curses.new_pad(self:height() > 0 and self:height() or 1, self:width() > 0 and self:width() or 1)
|
||||
assert(self._WINDOW, "cannot create window!")
|
||||
|
||||
-- disable cursor
|
||||
self:window():leaveok(true)
|
||||
|
||||
-- clear mark
|
||||
self:state_set("resize", false)
|
||||
end
|
||||
|
||||
-- show view?
|
||||
--
|
||||
-- .e.g
|
||||
-- v:show(false)
|
||||
-- v:show(true, {focused = true})
|
||||
--
|
||||
function view:show(visible, opt)
|
||||
if self:state("visible") ~= visible then
|
||||
local parent = self:parent()
|
||||
if parent and parent:current() == self and not visible then
|
||||
parent:select_next(nil, true)
|
||||
elseif parent and visible and opt and opt.focused then
|
||||
parent:select(self)
|
||||
end
|
||||
self:state_set("visible", visible)
|
||||
self:invalidate()
|
||||
end
|
||||
end
|
||||
|
||||
-- invalidate view to redraw it
|
||||
function view:invalidate(bounds)
|
||||
if bounds then
|
||||
self:_mark_resize()
|
||||
end
|
||||
self:_mark_redraw()
|
||||
end
|
||||
|
||||
-- on event (abstract)
|
||||
--
|
||||
-- @return true: done and break dispatching, false/nil: continous to dispatch to other views
|
||||
--
|
||||
function view:event_on(e)
|
||||
end
|
||||
|
||||
-- get the current event
|
||||
function view:event()
|
||||
return self:parent() and self:parent():event()
|
||||
end
|
||||
|
||||
-- put an event to view
|
||||
function view:event_put(e)
|
||||
return self:parent() and self:parent():event_put(e)
|
||||
end
|
||||
|
||||
-- get type
|
||||
function view:type()
|
||||
return self._TYPE
|
||||
end
|
||||
|
||||
-- set type
|
||||
function view:type_set(t)
|
||||
self._TYPE = t or "view"
|
||||
return self
|
||||
end
|
||||
|
||||
-- get state
|
||||
function view:state(name)
|
||||
return self._STATE[name]
|
||||
end
|
||||
|
||||
-- set state
|
||||
function view:state_set(name, enable)
|
||||
|
||||
-- state is not changed?
|
||||
enable = enable or false
|
||||
if self:state(name) == enable then
|
||||
return self
|
||||
end
|
||||
|
||||
-- change state
|
||||
self._STATE[name] = enable
|
||||
return self
|
||||
end
|
||||
|
||||
-- get option
|
||||
function view:option(name)
|
||||
return self._OPTIONS[name]
|
||||
end
|
||||
|
||||
-- set option
|
||||
function view:option_set(name, enable)
|
||||
|
||||
-- state is not changed?
|
||||
enable = enable or false
|
||||
if self:option(name) == enable then
|
||||
return
|
||||
end
|
||||
|
||||
-- set option
|
||||
self._OPTIONS[name] = enable
|
||||
end
|
||||
|
||||
-- get attribute
|
||||
function view:attr(name)
|
||||
return self._ATTRS[name]
|
||||
end
|
||||
|
||||
-- set attribute
|
||||
function view:attr_set(name, value)
|
||||
self._ATTRS[name] = value
|
||||
self:invalidate()
|
||||
return self
|
||||
end
|
||||
|
||||
-- get extra data
|
||||
function view:extra(name)
|
||||
return self._EXTRAS[name]
|
||||
end
|
||||
|
||||
-- set extra data
|
||||
function view:extra_set(name, value)
|
||||
self._EXTRAS[name] = value
|
||||
return self
|
||||
end
|
||||
|
||||
-- get action
|
||||
function view:action(name)
|
||||
return self._ACTIONS[name]
|
||||
end
|
||||
|
||||
-- set action
|
||||
function view:action_set(name, on_action)
|
||||
self._ACTIONS[name] = on_action
|
||||
return self
|
||||
end
|
||||
|
||||
-- do action
|
||||
function view:action_on(name, ...)
|
||||
local on_action = self:action(name)
|
||||
if on_action then
|
||||
if type(on_action) == "string" then
|
||||
-- send command
|
||||
if self:application() then
|
||||
self:application():send(on_action)
|
||||
end
|
||||
elseif type(on_action) == "function" then
|
||||
-- do action script
|
||||
return on_action(self, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- get cursor position
|
||||
function view:cursor()
|
||||
return self._CURSOR
|
||||
end
|
||||
|
||||
-- move cursor to the given position
|
||||
function view:cursor_move(x, y)
|
||||
self._CURSOR = point{ self:_limit(x, 0, self:width() - 1), self:_limit(y, 0, self:height() - 1) }
|
||||
return self
|
||||
end
|
||||
|
||||
-- show cursor?
|
||||
function view:cursor_show(visible)
|
||||
if self:state("cursor_visible") ~= visible then
|
||||
self:state_set("cursor_visible", visible)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-- get background
|
||||
function view:background()
|
||||
local background = self:attr("background")
|
||||
if not background and self:parent() then
|
||||
background = self:parent():background()
|
||||
end
|
||||
return background
|
||||
end
|
||||
|
||||
-- set background, .e.g background_set("blue")
|
||||
function view:background_set(color)
|
||||
return self:attr_set("background", color)
|
||||
end
|
||||
|
||||
-- limit value range
|
||||
function view:_limit(value, minval, maxval)
|
||||
return math.min(maxval, math.max(value, minval))
|
||||
end
|
||||
|
||||
-- need resize view
|
||||
function view:_mark_resize()
|
||||
|
||||
-- have been marked?
|
||||
if self:state("resize") then
|
||||
return
|
||||
end
|
||||
|
||||
-- trace
|
||||
log:print("%s: mark as resize", self)
|
||||
|
||||
-- need resize it
|
||||
self:state_set("resize", true)
|
||||
end
|
||||
|
||||
-- need redraw view
|
||||
function view:_mark_redraw()
|
||||
|
||||
-- have been marked?
|
||||
if self:state("redraw") then
|
||||
return
|
||||
end
|
||||
|
||||
-- trace
|
||||
log:print("%s: mark as redraw", self)
|
||||
|
||||
-- need redraw it
|
||||
self:state_set("redraw", true)
|
||||
|
||||
-- need redraw it's parent view if this view is invisible
|
||||
if not self:state("visible") and self:parent() then
|
||||
self:parent():_mark_redraw()
|
||||
end
|
||||
end
|
||||
|
||||
-- need refresh view
|
||||
function view:_mark_refresh()
|
||||
|
||||
-- have been marked?
|
||||
if self:state("refresh") then
|
||||
return
|
||||
end
|
||||
|
||||
-- need refresh it
|
||||
if self:state("visible") then
|
||||
self:state_set("refresh", true)
|
||||
end
|
||||
|
||||
-- need refresh it's parent view
|
||||
if self:parent() then
|
||||
self:parent():_mark_refresh()
|
||||
end
|
||||
end
|
||||
|
||||
-- tostring(view)
|
||||
function view:__tostring()
|
||||
return string.format("<view(%s) %s>", self:name(), tostring(self:bounds()))
|
||||
end
|
||||
|
||||
-- return module
|
||||
return view
|
131
src/ltui/window.lua
Normal file
131
src/ltui/window.lua
Normal file
@@ -0,0 +1,131 @@
|
||||
--!A cross-platform build utility based on Lua
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you 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 - 2018, TBOOX Open Source Group.
|
||||
--
|
||||
-- @author ruki
|
||||
-- @file window.lua
|
||||
--
|
||||
|
||||
-- load modules
|
||||
local log = require("ui/log")
|
||||
local rect = require("ui/rect")
|
||||
local view = require("ui/view")
|
||||
local label = require("ui/label")
|
||||
local panel = require("ui/panel")
|
||||
local event = require("ui/event")
|
||||
local border = require("ui/border")
|
||||
local curses = require("ui/curses")
|
||||
local action = require("ui/action")
|
||||
|
||||
-- define module
|
||||
local window = window or panel()
|
||||
|
||||
-- init window
|
||||
function window:init(name, bounds, title, shadow)
|
||||
|
||||
-- init panel
|
||||
panel.init(self, name, bounds)
|
||||
|
||||
-- check bounds
|
||||
assert(self:width() > 4 and self:height() > 3, string.format("%s: too small!", self))
|
||||
|
||||
-- insert shadow
|
||||
if shadow then
|
||||
self:insert(self:shadow())
|
||||
self:frame():bounds():movee(-2, -1)
|
||||
self:frame():invalidate(true)
|
||||
end
|
||||
|
||||
-- insert border
|
||||
self:frame():insert(self:border())
|
||||
|
||||
-- insert title
|
||||
if title then
|
||||
self._TITLE = label:new("window.title", rect{0, 0, #title, 1}, title)
|
||||
self:title():textattr_set("blue bold")
|
||||
self:title():action_set(action.ac_on_text_changed, function (v)
|
||||
if v:text() then
|
||||
local bounds = v:bounds()
|
||||
v:bounds():resize(#v:text(), v:height())
|
||||
bounds:move2(math.max(0, math.floor((self:frame():width() - v:width()) / 2)), bounds.sy)
|
||||
v:invalidate(true)
|
||||
end
|
||||
end)
|
||||
self:frame():insert(self:title(), {centerx = true})
|
||||
end
|
||||
|
||||
-- insert panel
|
||||
self:frame():insert(self:panel())
|
||||
|
||||
-- insert frame
|
||||
self:insert(self:frame())
|
||||
end
|
||||
|
||||
-- get frame
|
||||
function window:frame()
|
||||
if not self._FRAME then
|
||||
self._FRAME = panel:new("window.frame", rect{0, 0, self:width(), self:height()}):background_set("white")
|
||||
end
|
||||
return self._FRAME
|
||||
end
|
||||
|
||||
-- get panel
|
||||
function window:panel()
|
||||
if not self._PANEL then
|
||||
self._PANEL = panel:new("window.panel", self:frame():bounds())
|
||||
self._PANEL:bounds():grow(-1, -1)
|
||||
self._PANEL:invalidate(true)
|
||||
end
|
||||
return self._PANEL
|
||||
end
|
||||
|
||||
-- get title
|
||||
function window:title()
|
||||
return self._TITLE
|
||||
end
|
||||
|
||||
-- get shadow
|
||||
function window:shadow()
|
||||
if not self._SHADOW then
|
||||
self._SHADOW = view:new("window.shadow", rect{2, 1, self:width(), self:height()}):background_set("black")
|
||||
end
|
||||
return self._SHADOW
|
||||
end
|
||||
|
||||
-- get border
|
||||
function window:border()
|
||||
if not self._BORDER then
|
||||
self._BORDER = border:new("window.border", self:frame():bounds())
|
||||
end
|
||||
return self._BORDER
|
||||
end
|
||||
|
||||
-- on event
|
||||
function window:event_on(e)
|
||||
|
||||
-- select panel?
|
||||
if e.type == event.ev_keyboard then
|
||||
if e.key_name == "Tab" then
|
||||
return self:panel():select_next()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- return module
|
||||
return window
|
9
tests/load.lua
Executable file
9
tests/load.lua
Executable file
@@ -0,0 +1,9 @@
|
||||
-- init load directories
|
||||
package.path = package.path .. ';./src/?.lua'
|
||||
package.cpath = package.cpath .. ';./build/ltui.dll;./build/libltui.so;./build/libltui.dylib'
|
||||
|
||||
local os = require("ltui/base/os")
|
||||
local ltui = require("ltui.curses")
|
||||
print(ltui)
|
||||
|
||||
print(os.host())
|
Reference in New Issue
Block a user