diff --git a/dialog.lua b/dialog.lua index 1b9ab9b0c..19f7b0043 100644 --- a/dialog.lua +++ b/dialog.lua @@ -52,18 +52,23 @@ function FocusManager:onFocusMove(args) local current_item = self.layout[self.selected.y][self.selected.x] while true do - if self.selected.y + dy > #self.layout - or self.selected.y + dy < 1 - or self.selected.x + dx > #self.layout[self.selected.y] + if self.selected.x + dx > #self.layout[self.selected.y] or self.selected.x + dx < 1 then - break -- abort when we run into borders + break -- abort when we run into horizontal borders end + -- move cyclic in vertical direction + if self.selected.y + dy > #self.layout then + self.selected.y = 1 + elseif self.selected.y + dy < 1 then + self.selected.y = #self.layout + else + self.selected.y = self.selected.y + dy + end self.selected.x = self.selected.x + dx - self.selected.y = self.selected.y + dy if self.layout[self.selected.y][self.selected.x] ~= current_item - and not self.layout[self.selected.y][self.selected.x].is_inactive then + or not self.layout[self.selected.y][self.selected.x].is_inactive then -- we found a different object to focus current_item:handleEvent(Event:new("Unfocus")) self.layout[self.selected.y][self.selected.x]:handleEvent(Event:new("Focus")) @@ -171,7 +176,7 @@ function ConfirmBox:init() face = Font:getFace("cfont", 30), width = self.width, }, - VerticalSpan:new{ width = 20 }, + VerticalSpan:new{ width = 10 }, HorizontalGroup:new{ ok_button, HorizontalSpan:new{ width = 10 }, @@ -184,7 +189,6 @@ function ConfirmBox:init() end function ConfirmBox:onClose() - self:cancel_callback() UIManager:close(self) return true end @@ -229,7 +233,7 @@ function InfoMessage:init() file = "resources/info-i.png" }, HorizontalSpan:new{ width = 10 }, - TextWidget:new{ + TextBoxWidget:new{ text = self.text, face = Font:getFace("cfont", 30) } @@ -251,3 +255,276 @@ function InfoMessage:onAnyKeyPressed() UIManager:close(self) return true end + + +--[[ +Widget that displays a shortcut icon for menu item +]] +ItemShortCutIcon = WidgetContainer:new{ + width = 22, + height = 22, + key = nil, + bordersize = 2, +} + +function ItemShortCutIcon:init() + if not self.key then + return + end + self[1] = HorizontalGroup:new{ + HorizontalSpan:new{ width = 5 }, + FrameContainer:new{ + padding = 0, + bordersize = self.bordersize, + dimen = { + w = self.width, + h = self.height, + }, + CenterContainer:new{ + dimen = { + w = self.width, + h = self.height, + }, + TextWidget:new{ + text = self.key, + face = Font:getFace("scfont", 22) + }, + }, + }, + HorizontalSpan:new{ width = 5 }, + } +end + + +--[[ +Widget that displays an item for menu + +]] +MenuItem = WidgetContainer:new{ + text = nil, + detail = nil, + face = Font:getFace("cfont", 22), + width = nil, + height = nil, + shortcut = nil, +} + +function MenuItem:init() + local shortcut_icon_w = 0 + local shortcut_icon_h = 0 + if self.shortcut then + shortcut_icon_w = math.floor(self.height*4/5) + shortcut_icon_h = shortcut_icon_w + end + + self.detail = self.text + w = sizeUtf8Text(0, self.width, self.face, self.text, true).x + if w >= self.width - shortcut_icon_w then + indicator = " >>" + indicator_w = sizeUtf8Text(0, self.width, self.face, indicator, true).x + self.text = getSubTextByWidth(self.text, self.face, + self.width - shortcut_icon_w - indicator_w - 4, true) .. indicator + end + + self[1] = HorizontalGroup:new{ + ItemShortCutIcon:new{ + width = shortcut_icon_w, + height = shortcut_icon_h, + key = self.shortcut, + }, + HorizontalSpan:new{ width = 5 }, + UnderlineContainer:new{ + dimen = { + w = self.width - 5 - shortcut_icon_w, + h = self.height + }, + HorizontalGroup:new { + align = "center", + TextWidget:new{ + text = self.text, + face = self.face, + }, + }, + }, + } +end + +function MenuItem:onFocus() + self[1][3].color = 10 + return true +end + +function MenuItem:onUnfocus() + self[1][3].color = 0 + return true +end + +function MenuItem:onShowDetail() + UIManager:show(InfoMessage:new{ + text=self.detail, + }) + return true +end + + +--[[ +Widget that displays menu +]] +Menu = FocusManager:new{ + -- face for displaying item contents + cface = Font:getFace("cfont", 22), + -- face for menu title + tface = Font:getFace("tfont", 25), + -- face for paging info display + fface = Font:getFace("ffont", 16), + -- font for item shortcut + sface = Font:getFace("scfont", 20), + + title = "No Title", + height = 500, + width = 500, + item_table = {}, + items = 0, + item_shortcuts = { + "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", + "A", "S", "D", "F", "G", "H", "J", "K", "L", "Del", + "Z", "X", "C", "V", "B", "N", "M", ".", "Sym", "Enter", + }, + is_enable_shortcut = true, + + item_height = 36, + page = 1, + current = 1, + oldcurrent = 0, + selected_item = nil, +} + +function Menu:init() + self.items = #self.item_table + self.perpage = math.floor(self.height / self.item_height) + self.page = 1 + self.page_num = math.ceil(self.items / self.perpage) + + self.key_events.Close = { {"Back"}, doc = "close menu" } + self.key_events.Select = { {"Press"}, doc = "chose selected item" } + self.key_events.NextPage = { + {Input.group.PgFwd}, doc = "goto next page of the menu" + } + self.key_events.PrevPage = { + {Input.group.PgBack}, doc = "goto previous page of the menu" + } + self.key_events.FocusRight = nil + self.key_events.ShowItemDetail = { {"Right"}, doc = "show item detail" } + if self.is_enable_shortcut then + self.key_events.SelectByShortCut = { {self.item_shortcuts} } + end + + self[1] = CenterContainer:new{ + dimen = {w = G_width, h = G_height}, + FrameContainer:new{ + background = 0, + radius = math.floor(self.width/20), + VerticalGroup:new{ + TextWidget:new{ + text = self.title, + face = self.tface, + }, + -- group for items + VerticalGroup:new{ + }, + TextWidget:new{ + text = "page "..self.page.."/"..self.page_num, + face = self.fface, + }, + VerticalSpan:new{ width = 5 }, + }, -- VerticalGroup + }, -- FrameContainer + } -- CenterContainer + + self:_updateItems() +end + +function Menu:_updateItems() + self.layout = {} + self[1][1][1][2] = VerticalGroup:new{} + local item_group = self[1][1][1][2] + + for c = 1, self.perpage do + local i = (self.page - 1) * self.perpage + c + if i <= self.items then + local item_shortcut = nil + if self.is_enable_shortcut then + item_shortcut = self.item_shortcuts[c] + if item_shortcut == "Enter" then + item_shortcut = "Ent" + end + end + item_tmp = MenuItem:new{ + text = self.item_table[i].text, + face = self.cface, + width = self.width - 14, + height = self.item_height, + shortcut = item_shortcut + } + table.insert(item_group, item_tmp) + table.insert(self.layout, {item_tmp}) + --self.last_shortcut = c + end -- if i <= self.items + end -- for c=1, self.perpage + -- set focus to first menu item + item_group[1]:onFocus() +end + +function Menu:onSelectByShortCut(_, keyevent) + for k,v in ipairs(self.item_shortcuts) do + if v == keyevent.key then + local item = self.item_table[k] + self.item_table = nil + UIManager:close(self) + debug(item) + -- send events + break + end + end +end + +function Menu:onNextPage() + if self.page < self.page_num then + local page_info = self[1][1][1][3] + self.page = self.page + 1 + self:_updateItems() + self.selected = { x = 1, y = 1 } + self[1][1][1][3] = TextWidget:new{ + text = "page "..self.page.."/"..self.page_num, + face = self.fface, + }, + UIManager:setDirty(self) + end + return true +end + +function Menu:onPrevPage() + if self.page > 1 then + local page_info = self[1][1][1][3] + self.page = self.page - 1 + self:_updateItems() + self.selected = { x = 1, y = 1 } + self[1][1][1][3] = TextWidget:new{ + text = "page "..self.page.."/"..self.page_num, + face = self.fface, + }, + UIManager:setDirty(self) + end + return true +end + +function Menu:onShowItemDetail() + return self.layout[self.selected.y][self.selected.x]:handleEvent( + Event:new("ShowDetail") + ) +end + +function Menu:onClose() + UIManager:close(self) + return true +end diff --git a/rendertext.lua b/rendertext.lua index 68e4aa8ce..2e70c3bf4 100644 --- a/rendertext.lua +++ b/rendertext.lua @@ -44,6 +44,30 @@ function clearGlyphCache() glyphcache = {} end +function getSubTextByWidth(text, face, width, kerning) + local pen_x = 0 + local prevcharcode = 0 + local char_list = {} + for uchar in string.gfind(text, "([%z\1-\127\194-\244][\128-\191]*)") do + if pen_x < width then + local charcode = util.utf8charcode(uchar) + local glyph = getGlyph(face, charcode) + if kerning and prevcharcode then + local kern = face.ftface:getKerning(prevcharcode, charcode) + pen_x = pen_x + kern + end + pen_x = pen_x + glyph.ax + if pen_x <= width then + prevcharcode = charcode + table.insert(char_list, uchar) + else + break + end + end + end + return table.concat(char_list) +end + function sizeUtf8Text(x, width, face, text, kerning) if text == nil then debug("sizeUtf8Text called without text"); diff --git a/widget.lua b/widget.lua index 874e13b4a..a624d7bca 100644 --- a/widget.lua +++ b/widget.lua @@ -85,8 +85,8 @@ end Containers will pass events to children or react on them themselves ]] function WidgetContainer:handleEvent(event) - -- call our own standard event handler if not self:propagateEvent(event) then + -- call our own standard event handler return Widget.handleEvent(self, event) else return true @@ -168,9 +168,9 @@ TextWidget = Widget:new{ } function TextWidget:_render() - local h = self.face.size * 1.5 + local h = self.face.size * 1.3 self._bb = Blitbuffer.new(self._maxlength, h) - self._length = renderUtf8Text(self._bb, 0, h*.7, self.face, self.text, self.color) + self._length = renderUtf8Text(self._bb, 0, h*0.8, self.face, self.text, self.color) end function TextWidget:getSize() @@ -261,7 +261,7 @@ function TextBoxWidget:_render() for _,l in ipairs(v_list) do pen_x = 0 for _,w in ipairs(l) do - renderUtf8Text(self._bb, pen_x, y, self.face, w.word, true) + renderUtf8Text(self._bb, pen_x, y*0.8, self.face, w.word, true) pen_x = pen_x + w.width + space_w end y = y + line_height_px + font_height @@ -459,13 +459,17 @@ UnderlineContainer = WidgetContainer:new{ function UnderlineContainer:getSize() local contentSize = self[1]:getSize() + if self.dimen then + if contentSize.w < self.dimen.w then contentSize.w = self.dimen.w end + if contentSize.h < self.dimen.h then contentSize.h = self.dimen.h end + end return { w = contentSize.w, h = contentSize.h + self.linesize + self.padding } end function UnderlineContainer:paintTo(bb, x, y) - local contentSize = self[1]:getSize() + local contentSize = self:getSize() self[1]:paintTo(bb, x, y) - bb:paintRect(x, y + contentSize.h + self.padding, + bb:paintRect(x, y + contentSize.h - self.linesize, contentSize.w, self.linesize, self.color) end diff --git a/wtest.lua b/wtest.lua index 36ea9269c..8c6e861b6 100644 --- a/wtest.lua +++ b/wtest.lua @@ -71,15 +71,42 @@ function Clock:getTextWidget() } end -quiz = ConfirmBox:new{ +Quiz = ConfirmBox:new{ text = "Tell me the truth, isn't it COOL?!", width = 300, ok_text = "Yes, of course.", cancel_text = "No, it's ugly.", + cancel_callback = function() + UIManager:show(InfoMessage:new{ + text="You liar!", + }) + end, +} + +menu_items = { + {text = "item1"}, + {text = "item2"}, + {text = "This is a very very log item whose length should exceed the width of the menu."}, + {text = "item3"}, + {text = "item4"}, + {text = "item5"}, + {text = "item6"}, + {text = "item7"}, + {text = "item8"}, + {text = "item9"}, + {text = "item10"}, + {text = "item11"}, + {text = "item12"}, +} +M = Menu:new{ + title = "Test Menu", + item_table = menu_items, + width = 500, + height = 400, } -quiz:init() UIManager:show(Background:new()) UIManager:show(Clock:new()) -UIManager:show(quiz) +UIManager:show(M) +UIManager:show(Quiz) UIManager:run()