diff --git a/frontend/ui/widget/inputdialog.lua b/frontend/ui/widget/inputdialog.lua new file mode 100644 index 000000000..90859e0a2 --- /dev/null +++ b/frontend/ui/widget/inputdialog.lua @@ -0,0 +1,109 @@ +require "ui/widget/container" +require "ui/widget/inputtext" + +InputDialog = InputContainer:new{ + title = "", + input = "", + input_hint = "", + buttons = nil, + input_type = nil, + + width = nil, + height = nil, + + title_face = Font:getFace("tfont", 22), + input_face = Font:getFace("cfont", 20), + + title_padding = scaleByDPI(5), + title_margin = scaleByDPI(2), + input_padding = scaleByDPI(10), + input_margin = scaleByDPI(10), + button_padding = scaleByDPI(14), +} + +function InputDialog:init() + self.title = FrameContainer:new{ + padding = self.title_padding, + margin = self.title_margin, + bordersize = 0, + TextWidget:new{ + text = self.title, + face = self.title_face, + width = self.width, + } + } + self.input = InputText:new{ + text = self.input, + hint = self.input_hint, + face = self.input_face, + width = self.width * 0.9, + input_type = self.input_type, + scroll = false, + parent = self, + } + local button_table = ButtonTable:new{ + width = self.width, + button_font_face = "cfont", + button_font_size = 20, + buttons = self.buttons, + zero_sep = true, + } + local title_bar = LineWidget:new{ + --background = 8, + dimen = Geom:new{ + w = button_table:getSize().w + self.button_padding, + h = scaleByDPI(2), + } + } + + self.dialog_frame = FrameContainer:new{ + radius = 8, + bordersize = 3, + padding = 0, + margin = 0, + background = 0, + VerticalGroup:new{ + align = "left", + self.title, + title_bar, + -- input + CenterContainer:new{ + dimen = Geom:new{ + w = title_bar:getSize().w, + h = self.input:getSize().h, + }, + self.input, + }, + -- buttons + CenterContainer:new{ + dimen = Geom:new{ + w = title_bar:getSize().w, + h = button_table:getSize().h, + }, + button_table, + } + } + } + + self[1] = CenterContainer:new{ + dimen = Geom:new{ + w = Screen:getWidth(), + h = Screen:getHeight() - self.input:getKeyboardDimen().h, + }, + self.dialog_frame, + } + UIManager.repaint_all = true + UIManager.full_refresh = true +end + +function InputDialog:onShowKeyboard() + self.input:onShowKeyboard() +end + +function InputDialog:getInputText() + return self.input:getText() +end + +function InputDialog:onClose() + self.input:onCloseKeyboard() +end diff --git a/frontend/ui/widget/inputtext.lua b/frontend/ui/widget/inputtext.lua new file mode 100644 index 000000000..17893e521 --- /dev/null +++ b/frontend/ui/widget/inputtext.lua @@ -0,0 +1,137 @@ +require "ui/graphics" +require "ui/widget/text" +require "ui/widget/keyboard" +require "ui/widget/container" + +InputText = InputContainer:new{ + text = "", + hint = "demo hint", + charlist = {}, -- table to store input string + charpos = 1, + input_type = nil, + + width = nil, + height = nil, + face = Font:getFace("cfont", 22), + + padding = 5, + margin = 5, + bordersize = 2, + + parent = nil, -- parent dialog that will be set dirty + scroll = false, +} + +function InputText:init() + self:StringToCharlist(self.text) + self:initTextBox() + self:initKeyboard() +end + +function InputText:initTextBox() + local bgcolor = nil + local fgcolor = nil + if self.text == "" then + self.text = self.hint + bgcolor = 0.0 + fgcolor = 0.5 + else + bgcolor = 0.0 + fgcolor = 1.0 + end + local text_widget = nil + if self.scroll then + text_widget = ScrollTextWidget:new{ + text = self.text, + face = self.face, + bgcolor = bgcolor, + fgcolor = fgcolor, + width = self.width, + height = self.height, + } + else + text_widget = TextBoxWidget:new{ + text = self.text, + face = self.face, + bgcolor = bgcolor, + fgcolor = fgcolor, + width = self.width, + height = self.height, + } + end + self[1] = FrameContainer:new{ + bordersize = self.bordersize, + padding = self.padding, + margin = self.margin, + text_widget, + } + self.dimen = self[1]:getSize() +end + +function InputText:initKeyboard() + local keyboard_layout = 2 + if self.input_type == "number" then + keyboard_layout = 3 + end + self.keyboard = VirtualKeyboard:new{ + layout = keyboard_layout, + inputbox = self, + } +end + +function InputText:onShowKeyboard() + UIManager:show(self.keyboard) +end + +function InputText:onCloseKeyboard() + UIManager:close(self.keyboard) +end + +function InputText:getKeyboardDimen() + return self.keyboard.dimen +end + +function InputText:addChar(char) + table.insert(self.charlist, self.charpos, char) + self.charpos = self.charpos + 1 + self.text = self:CharlistToString() + self:initTextBox() + UIManager:setDirty(self.parent, "partial") +end + +function InputText:delChar() + self.charpos = self.charpos - 1 + table.remove(self.charlist, self.charpos) + self.text = self:CharlistToString() + self:initTextBox() + UIManager:setDirty(self.parent, "partial") +end + +function InputText:getText() + return self.text +end + +function InputText:StringToCharlist(text) + if text == nil then return end + -- clear + self.charlist = {} + self.charpos = 1 + local prevcharcode, charcode = 0 + for uchar in string.gfind(text, "([%z\1-\127\194-\244][\128-\191]*)") do + charcode = util.utf8charcode(uchar) + if prevcharcode then -- utf8 + self.charlist[#self.charlist+1] = uchar + end + prevcharcode = charcode + end + self.text = self:CharlistToString() + self.charpos = #self.charlist+1 +end + +function InputText:CharlistToString() + local s, i = "" + for i=1, #self.charlist do + s = s .. self.charlist[i] + end + return s +end diff --git a/frontend/ui/widget/keyboard.lua b/frontend/ui/widget/keyboard.lua new file mode 100644 index 000000000..9e90da9fb --- /dev/null +++ b/frontend/ui/widget/keyboard.lua @@ -0,0 +1,298 @@ +require "ui/font" +require "ui/widget/text" +require "ui/widget/image" +require "ui/widget/group" +require "ui/widget/container" + +VirtualKey = InputContainer:new{ + key = nil, + icon = nil, + label = nil, + + keyboard = nil, + callback = nil, + + width = nil, + height = nil, + bordersize = 2, + face = Font:getFace("infont", 22), +} + +function VirtualKey:init() + if self.label == "Sym" or self.label == "ABC" then + self.callback = function () self.keyboard:setLayout(self.key or self.label) end + elseif self.label == "Shift" then + self.callback = function () self.keyboard:setLayout(self.key or self.label) end + elseif self.label == "IM" then + self.callback = function () self.keyboard:setLayout(self.key or self.label) end + elseif self.label == "Backspace" then + self.callback = function () self.keyboard:delChar() end + else + self.callback = function () self.keyboard:addChar(self.key) end + end + + local label_widget = nil + if self.icon then + label_widget = ImageWidget:new{ + file = self.icon, + } + else + label_widget = TextWidget:new{ + text = self.label, + face = self.face, + } + end + self[1] = FrameContainer:new{ + margin = 0, + bordersize = self.bordersize, + background = 0, + radius = 5, + padding = 0, + CenterContainer:new{ + dimen = Geom:new{ + w = self.width - 2*self.bordersize, + h = self.height - 2*self.bordersize, + }, + label_widget, + }, + } + self.dimen = Geom:new{ + w = self.width, + h = self.height, + } + --self.dimen = self[1]:getSize() + if Device:isTouchDevice() then + self.ges_events = { + TapSelect = { + GestureRange:new{ + ges = "tap", + range = self.dimen, + }, + }, + DoubleTapSelect = { + GestureRange:new{ + ges = "double_tap", + range = self.dimen, + }, + }, + } + end +end + +function VirtualKey:onTapSelect() + self[1].invert = true + if self.callback then + self.callback() + end + UIManager:scheduleIn(0.08, function() self:invert(false) end) + return true +end + +function VirtualKey:onDoubleTapSelect() + self[1].invert = true + if self.callback then + self.callback() -- once + self.callback() -- twice + end + UIManager:scheduleIn(0.08, function() self:invert(false) end) + return true +end + +function VirtualKey:invert(invert) + self[1].invert = invert + UIManager.update_region_func = function() + DEBUG("update key region", self[1].dimen) + return self[1].dimen + end + UIManager:setDirty(self.keyboard, "partial") +end + +VirtualKeyboard = InputContainer:new{ + is_always_active = true, + inputbox = nil, + KEYS = {}, -- table to store layouts + min_layout = 2, + max_layout = 9, + layout = 2, + shiftmode = false, + symbolmode = false, + utf8mode = false, + + width = 600, + height = 256, + bordersize = 2, + padding = 2, + key_padding = scaleByDPI(6), +} + +function VirtualKeyboard:init() + self.KEYS = { + -- first row + { + { "Q", "q", "1", "!", "Я", "я", "1", "!", }, + { "W", "w", "2", "?", "Ж", "ж", "2", "?", }, + { "E", "e", "3", "|", "Е", "е", "3", "«", }, + { "R", "r", "4", "#", "Р", "р", "4", "»", }, + { "T", "t", "5", "@", "Т", "т", "5", ":", }, + { "Y", "y", "6", "‰", "Ы", "ы", "6", ";", }, + { "U", "u", "7", "'", "У", "у", "7", "~", }, + { "I", "i", "8", "`", "И", "и", "8", "(", }, + { "O", "o", "9", ":", "О", "о", "9", ")", }, + { "P", "p", "0", ";", "П", "п", "0", "=", }, + }, + -- second raw + { + { "A", "a", "+", "…", "А", "а", "Ш", "ш", }, + { "S", "s", "-", "_", "С", "с", "Ѕ", "ѕ", }, + { "D", "d", "*", "=", "Д", "д", "Э", "э", }, + { "F", "f", "/", "\\", "Ф", "ф", "Ю", "ю", }, + { "G", "g", "%", "„", "Г", "г", "Ґ", "ґ", }, + { "H", "h", "^", "“", "Ч", "ч", "Ј", "ј", }, + { "J", "j", "<", "”", "Й", "й", "І", "і", }, + { "K", "k", "=", "\"", "К", "к", "Ќ", "ќ", }, + { "L", "l", ">", "~", "Л", "л", "Љ", "љ", }, + }, + -- third raw + { + { label = "Shift", + icon = "resources/icons/appbar.arrow.shift.png", + width = 1.5 + }, + { "Z", "z", "(", "$", "З", "з", "Щ", "щ", }, + { "X", "x", ")", "€", "Х", "х", "№", "@", }, + { "C", "c", "&", "¥", "Ц", "ц", "Џ", "џ", }, + { "V", "v", ":", "£", "В", "в", "Ў", "ў", }, + { "B", "b", "π", "‚", "Б", "б", "Ћ", "ћ", }, + { "N", "n", "е", "‘", "Н", "н", "Њ", "њ", }, + { "M", "m", "~", "’", "М", "м", "Ї", "ї", }, + { label = "Backspace", + icon = "resources/icons/appbar.clear.reflect.horizontal.png", + width = 1.5 + }, + }, + -- fourth raw + { + { "Sym", "Sym", "ABC", "ABC", "Sym", "Sym", "ABC", "ABC", + width = 1.5}, + { label = "IM", + icon = "resources/icons/appbar.globe.wire.png", + }, + { label = "space", + " ", " ", " ", " ", " ", " ", " ", " ", + width = 5.0}, + { ",", ".", ".", ",", ",", ".", "Є", "є", }, + { label = "Enter", + "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", + icon = "resources/icons/appbar.arrow.enter.png", + width = 1.5, + }, + + } + } + self:initLayout(self.layout) +end + +function VirtualKeyboard:initLayout(layout) + local function VKLayout(b1, b2, b3) + local function boolnum(bool) + return bool and 1 or 0 + end + return 2 - boolnum(b1) + 2 * boolnum(b2) + 4 * boolnum(b3) + end + + if layout then + -- to be sure layout is selected properly + layout = math.max(layout, self.min_layout) + layout = math.min(layout, self.max_layout) + self.layout = layout + -- fill the layout modes + layout = layout % 4 + self.shiftmode = (layout == 1 or layout == 3) + self.symbolmode = (layout == 3 or layout == 4) + self.utf8mode = (self.layout > 5) + else -- or, without input parameter, restore layout from current layout modes + self.layout = VKLayout(self.shiftmode, self.symbolmode, self.utf8mode) + end + self:addKeys() +end + +function VirtualKeyboard:addKeys() + local base_key_width = math.floor((self.width - 11*self.key_padding - 2*self.padding)/10) + local base_key_height = math.floor((self.height - 5*self.key_padding - 2*self.padding)/4) + local h_key_padding = HorizontalSpan:new{width = self.key_padding} + local v_key_padding = VerticalSpan:new{width = self.key_padding} + local vertical_group = VerticalGroup:new{} + for i = 1, #self.KEYS do + local horizontal_group = HorizontalGroup:new{} + for j = 1, #self.KEYS[i] do + local width_factor = self.KEYS[i][j].width or 1.0 + local key_width = math.floor((base_key_width + self.key_padding) * width_factor) + - self.key_padding + local key_height = base_key_height + local label = self.KEYS[i][j].label or self.KEYS[i][j][self.layout] + local key = VirtualKey:new{ + key = self.KEYS[i][j][self.layout], + icon = self.KEYS[i][j].icon, + label = label, + keyboard = self, + width = key_width, + height = key_height, + } + table.insert(horizontal_group, key) + if j ~= #self.KEYS[i] then + table.insert(horizontal_group, h_key_padding) + end + end + table.insert(vertical_group, horizontal_group) + if i ~= #self.KEYS then + table.insert(vertical_group, v_key_padding) + end + end + + local size = vertical_group:getSize() + local keyboard_frame = FrameContainer:new{ + margin = 0, + bordersize = self.bordersize, + background = 0, + radius = 0, + padding = self.padding, + CenterContainer:new{ + dimen = Geom:new{ + w = self.width - 2*self.bordersize -2*self.padding, + h = self.height - 2*self.bordersize -2*self.padding, + }, + vertical_group, + } + } + self[1] = BottomContainer:new{ + dimen = Screen:getSize(), + keyboard_frame, + } + self.dimen = keyboard_frame:getSize() +end + +function VirtualKeyboard:setLayout(key) + if key == "Shift" then + self.shiftmode = not self.shiftmode + elseif key == "Sym" or key == "ABC" then + self.symbolmode = not self.symbolmode + elseif key == "IM" then + self.utf8mode = not self.utf8mode + end + self:initLayout() + UIManager:setDirty(self, "partial") +end + +function VirtualKeyboard:addChar(key) + DEBUG("add char", key) + self.inputbox:addChar(key) + UIManager:setDirty(self, "partial") + UIManager:setDirty(self.inputbox, "partial") +end + +function VirtualKeyboard:delChar() + DEBUG("delete char") + self.inputbox:delChar() + UIManager:setDirty(self, "partial") + UIManager:setDirty(self.inputbox, "partial") +end diff --git a/resources/icons/appbar.arrow.enter.png b/resources/icons/appbar.arrow.enter.png new file mode 100644 index 000000000..ce3fd48d9 Binary files /dev/null and b/resources/icons/appbar.arrow.enter.png differ diff --git a/resources/icons/appbar.arrow.shift.png b/resources/icons/appbar.arrow.shift.png new file mode 100644 index 000000000..840f4f7db Binary files /dev/null and b/resources/icons/appbar.arrow.shift.png differ diff --git a/resources/icons/appbar.clear.reflect.horizontal.png b/resources/icons/appbar.clear.reflect.horizontal.png new file mode 100644 index 000000000..b800bf14c Binary files /dev/null and b/resources/icons/appbar.clear.reflect.horizontal.png differ diff --git a/resources/icons/appbar.globe.wire.png b/resources/icons/appbar.globe.wire.png new file mode 100644 index 000000000..13c25f6e1 Binary files /dev/null and b/resources/icons/appbar.globe.wire.png differ diff --git a/resources/icons/src/appbar.arrow.enter.svg b/resources/icons/src/appbar.arrow.enter.svg new file mode 100644 index 000000000..73fe8cefe --- /dev/null +++ b/resources/icons/src/appbar.arrow.enter.svg @@ -0,0 +1,54 @@ + +image/svg+xml + + \ No newline at end of file diff --git a/resources/icons/src/appbar.arrow.left.svg b/resources/icons/src/appbar.arrow.left.svg new file mode 100644 index 000000000..c436355dd --- /dev/null +++ b/resources/icons/src/appbar.arrow.left.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/icons/src/appbar.arrow.shift.svg b/resources/icons/src/appbar.arrow.shift.svg new file mode 100644 index 000000000..9376d4f82 --- /dev/null +++ b/resources/icons/src/appbar.arrow.shift.svg @@ -0,0 +1,57 @@ + +image/svg+xml + + \ No newline at end of file diff --git a/resources/icons/src/appbar.globe.wire.xaml b/resources/icons/src/appbar.globe.wire.xaml new file mode 100644 index 000000000..4443828ab --- /dev/null +++ b/resources/icons/src/appbar.globe.wire.xaml @@ -0,0 +1,4 @@ + + + +