diff --git a/.gitignore b/.gitignore index e91c25f9d..5c95bb8a7 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ git-rev kpdfview slider_watcher *.o +tags kindlepdfviewer-*.zip @@ -46,3 +47,4 @@ popen-noshell/popen_noshell_examples.c popen-noshell/popen_noshell_tests.c popen-noshell/popen_noshell_tests.cpp +kpvcrlib/libcrengine.so diff --git a/frontend/dbg.lua b/frontend/dbg.lua new file mode 100644 index 000000000..3d373685b --- /dev/null +++ b/frontend/dbg.lua @@ -0,0 +1,38 @@ +require "settings" -- for dump method + +Dbg = { + is_on = false, + ev_log = nil, +} + +function Dbg:turnOn() + self.is_on = true + + -- create or clear ev log file + os.execute("echo > ev.log") + self.ev_log = io.open("ev.log", "w") +end + +function Dbg:logEv(ev) + local log = ev.type.."|"..ev.code.."|" + ..ev.value.."|"..ev.time.sec.."|"..ev.time.usec.."\n" + self.ev_log:write(log) + self.ev_log:flush() +end + +function DEBUG(...) + LvDEBUG(math.huge, ...) +end + +function LvDEBUG(lv, ...) + local line = "" + for i,v in ipairs({...}) do + if type(v) == "table" then + line = line .. " " .. dump(v, lv) + else + line = line .. " " .. tostring(v) + end + end + print("#"..line) +end + diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index ba6a699af..0c72701a6 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -130,7 +130,7 @@ function CreDocument:engineInit() if not engine_initilized then -- initialize cache cre.initCache(1024*1024*64) - + -- we need to initialize the CRE font list local fonts = Font:getFontList() for _k, _v in ipairs(fonts) do diff --git a/frontend/document/djvudocument.lua b/frontend/document/djvudocument.lua index 3dbb1161e..3f0f34cc1 100644 --- a/frontend/document/djvudocument.lua +++ b/frontend/document/djvudocument.lua @@ -80,6 +80,14 @@ function DjvuDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mod end end +function DjvuDocument:hintPage(pageno, zoom, rotation, gamma, render_mode) + if self.configurable.text_wrap == 1 then + self.koptinterface:hintPage(self, pageno, zoom, rotation, gamma, render_mode) + else + Document.hintPage(self, pageno, zoom, rotation, gamma, render_mode) + end +end + function DjvuDocument:drawPage(target, x, y, rect, pageno, zoom, rotation, gamma, render_mode) if self.configurable.text_wrap == 1 then self.koptinterface:drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) diff --git a/frontend/document/koptinterface.lua b/frontend/document/koptinterface.lua index 570f29935..e868f441c 100644 --- a/frontend/document/koptinterface.lua +++ b/frontend/document/koptinterface.lua @@ -195,10 +195,38 @@ KoptOptions = { }, } -KoptInterface = {} +KoptInterface = { + bg_context = { + contex = nil, + pageno = nil, + hash = nil, + cached = false, + }, +} + +function KoptInterface:waitForContext(kc) + -- if koptcontext is being processed in background thread + -- the isPreCache will return 1. + while kc and kc:isPreCache() == 1 do + DEBUG("waiting for background rendering") + util.usleep(100000) + end +end + +function KoptInterface:consumeBgContext(doc) + -- clear up background context + self:waitForContext(self.bg_context.context) + if self.bg_context.context and not self.bg_context.cached then + self:makeCache(doc, self.bg_context.pageno, self.bg_context.hash) + self.bg_context.cached = true + end +end -- get reflow context function KoptInterface:getKOPTContext(doc, pageno, bbox) + -- since libk2pdfopt only has one bitmap buffer that holds reflowed page + -- we should consume background production before allocating new context. + self:consumeBgContext(doc) local kc = KOPTContext.new() local screen_size = Screen:getSize() kc:setTrim(doc.configurable.trim_page) @@ -222,6 +250,7 @@ function KoptInterface:getKOPTContext(doc, pageno, bbox) end function KoptInterface:setTrimPage(doc, pageno) + if doc.configurable.trim_page == 0 then return end local page_dimens = doc:getNativePageDimensions(pageno) --DEBUG("original page dimens", page_dimens) local orig_bbox = doc:getUsedBBox(pageno) @@ -240,6 +269,22 @@ function KoptInterface:getContextHash(doc, pageno, bbox) return doc.file.."|"..pageno.."|"..doc.configurable:hash("|").."|"..bbox_hash.."|"..screen_size_hash end +function KoptInterface:logReflowDuration(pageno, dur) + local file = io.open("reflowlog.txt", "a+") + if file then + if file:seek("end") == 0 then -- write the header only once + file:write("PAGE\tDUR\n") + end + file:write(string.format("%s\t%s\n", pageno, dur)) + file:close() + end +end + +function KoptInterface:getReflowedDim(kc) + self:waitForContext(kc) + return kc:getPageDim() +end + -- calculates page dimensions function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation) self:setTrimPage(doc, pageno) @@ -251,7 +296,12 @@ function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation) local kc = self:getKOPTContext(doc, pageno, bbox) local page = doc._document:openPage(pageno) -- reflow page + --local secs, usecs = util.gettime() page:reflow(kc, 0) + --local nsecs, nusecs = util.gettime() + --local dur = nsecs - secs + (nusecs - usecs) / 1000000 + --DEBUG("Reflow duration:", dur) + --self:logReflowDuration(pageno, dur) page:close() local fullwidth, fullheight = kc:getPageDim() DEBUG("page::reflowPage:", "fullwidth:", fullwidth, "fullheight:", fullheight) @@ -261,11 +311,37 @@ function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation) return page_size end --DEBUG("Found cached koptcontex on page", pageno, cached) - local fullwidth, fullheight = cached.kctx:getPageDim() + local fullwidth, fullheight = self:getReflowedDim(cached.kctx) local page_size = Geom:new{ w = fullwidth, h = fullheight } return page_size end +function KoptInterface:makeCache(doc, pageno, context_hash) + -- draw to blitbuffer + local kc_hash = "kctx|"..context_hash + local tile_hash = "renderpg|"..context_hash + local page = doc._document:openPage(pageno) + local cached = Cache:check(kc_hash) + if cached then + local fullwidth, fullheight = self:getReflowedDim(cached.kctx) + -- prepare cache item with contained blitbuffer + local tile = CacheItem:new{ + size = fullwidth * fullheight / 2 + 64, -- estimation + excerpt = Geom:new{ w = fullwidth, h = fullheight }, + pageno = pageno, + bb = Blitbuffer.new(fullwidth, fullheight) + } + page:rfdraw(cached.kctx, tile.bb) + page:close() + --DEBUG("cached hash", hash) + if not Cache:check(tile_hash) then + Cache:insert(tile_hash, tile) + end + return tile + end + DEBUG("Error: cannot render page before reflowing.") +end + function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode) self:setTrimPage(doc, pageno) doc.render_mode = render_mode @@ -291,30 +367,33 @@ function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode end local cached = Cache:check(hash) - if cached then return cached end - - -- prepare cache item with contained blitbuffer - local tile = CacheItem:new{ - size = size.w * size.h / 2 + 64, -- estimation - excerpt = size, - pageno = pageno, - bb = Blitbuffer.new(size.w, size.h) - } - - -- draw to blitbuffer - local kc_hash = "kctx|"..context_hash - local page = doc._document:openPage(pageno) - local cached = Cache:check(kc_hash) if cached then - page:rfdraw(cached.kctx, tile.bb) + return cached + else + return self:makeCache(doc, pageno, context_hash) + end +end + +function KoptInterface:hintPage(doc, pageno, zoom, rotation, gamma, render_mode) + self:setTrimPage(doc, pageno) + local bbox = doc:getPageBBox(pageno) + local context_hash = self:getContextHash(doc, pageno, bbox) + local hash = "kctx|"..context_hash + local cached = Cache:check(hash) + if not cached then + local kc = self:getKOPTContext(doc, pageno, bbox) + local page = doc._document:openPage(pageno) + kc:setPreCache() + self.bg_context.context = kc + self.bg_context.pageno = pageno + self.bg_context.hash = context_hash + self.bg_context.cached = false + DEBUG("hinting page", pageno, "in background") + -- will return immediately + page:reflow(kc, 0) page:close() - --DEBUG("cached hash", hash) - if not Cache:check(hash) then - Cache:insert(hash, tile) - end - return tile + Cache:insert(hash, CacheItem:new{ kctx = kc }) end - DEBUG("Error: cannot render page before reflowing.") end function KoptInterface:drawPage(doc, target, x, y, rect, pageno, zoom, rotation, render_mode) diff --git a/frontend/document/pdfdocument.lua b/frontend/document/pdfdocument.lua index ee8d38c7f..bda9c6c1c 100644 --- a/frontend/document/pdfdocument.lua +++ b/frontend/document/pdfdocument.lua @@ -82,6 +82,14 @@ function PdfDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mode end end +function PdfDocument:hintPage(pageno, zoom, rotation, gamma, render_mode) + if self.configurable.text_wrap == 1 then + self.koptinterface:hintPage(self, pageno, zoom, rotation, gamma, render_mode) + else + Document.hintPage(self, pageno, zoom, rotation, gamma, render_mode) + end +end + function PdfDocument:drawPage(target, x, y, rect, pageno, zoom, rotation, gamma, render_mode) if self.configurable.text_wrap == 1 then self.koptinterface:drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) diff --git a/frontend/settings.lua b/frontend/settings.lua index 638484488..2354e7658 100644 --- a/frontend/settings.lua +++ b/frontend/settings.lua @@ -15,6 +15,21 @@ function DocSettings:getHistoryPath(fullpath) return "./history/["..basename:gsub("/","#").."] "..filename..".lua" end +function DocSettings:getPathFromHistory(hist_name) + -- 1. select everything included in brackets + local s = string.match(hist_name,"%b[]") + -- 2. crop the bracket-sign from both sides + -- 3. and finally replace decorative signs '#' to dir-char '/' + return string.gsub(string.sub(s,2,-3),"#","/") +end + +function DocSettings:getNameFromHistory(hist_name) + -- at first, search for path length + local s = string.len(string.match(hist_name,"%b[]")) + -- and return the rest of string without 4 last characters (".lua") + return string.sub(hist_name, s+2, -5) +end + function DocSettings:open(docfile) local conf_path = nil if docfile == ".reader" then @@ -49,26 +64,22 @@ function DocSettings:delSetting(key) self.data[key] = nil end -function dump(data) +function dump(data, max_lv) local out = {} - DocSettings:_serialize(data, out, 0) + DocSettings:_serialize(data, out, 0, max_lv) return table.concat(out) end -function DEBUG(...) - local line = "" - for i,v in ipairs({...}) do - if type(v) == "table" then - line = line .. " " .. dump(v) - else - line = line .. " " .. tostring(v) - end +-- simple serialization function, won't do uservalues, functions, loops +function DocSettings:_serialize(what, outt, indent, max_lv) + if not max_lv then + max_lv = math.huge + end + + if indent > max_lv then + return end - print("#"..line) -end --- simple serialization function, won't do uservalues, functions, loops -function DocSettings:_serialize(what, outt, indent) if type(what) == "table" then local didrun = false table.insert(outt, "{") @@ -79,9 +90,9 @@ function DocSettings:_serialize(what, outt, indent) table.insert(outt, "\n") table.insert(outt, string.rep("\t", indent+1)) table.insert(outt, "[") - self:_serialize(k, outt, indent+1) + self:_serialize(k, outt, indent+1, max_lv) table.insert(outt, "] = ") - self:_serialize(v, outt, indent+1) + self:_serialize(v, outt, indent+1, max_lv) didrun = true end if didrun then diff --git a/frontend/ui/font.lua b/frontend/ui/font.lua index f3c7eaca2..8255fff24 100644 --- a/frontend/ui/font.lua +++ b/frontend/ui/font.lua @@ -43,8 +43,8 @@ function Font:getFace(font, size) -- default to content font font = self.cfont end - - local size = math.floor(size*Screen:getDPI()/167) + + local size = math.floor(scaleByDPI(size)) local face = self.faces[font..size] -- build face if not found diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index a256d5271..2c9a13a0b 100644 --- a/frontend/ui/gesturedetector.lua +++ b/frontend/ui/gesturedetector.lua @@ -78,7 +78,7 @@ GestureDetector = { DOUBLE_TAP_DISTANCE = 50, TWO_FINGER_TAP_REGION = 20, PAN_THRESHOLD = 50, - + -- states are stored in separated slots states = {}, track_ids = {}, @@ -272,7 +272,7 @@ function GestureDetector:tapState(tev) y = tev.y, timev = tev.timev, } - + if self.last_taps[slot] ~= nil and self:isDoubleTap(self.last_taps[slot], cur_tap) then -- it is a double tap @@ -282,10 +282,10 @@ function GestureDetector:tapState(tev) DEBUG("double tap detected in slot", slot) return ges_ev end - + -- set current tap to last tap self.last_taps[slot] = cur_tap - + DEBUG("set up tap timer") -- deadline should be calculated by adding current tap time and the interval local deadline = cur_tap.timev + TimeVal:new{ @@ -305,7 +305,7 @@ function GestureDetector:tapState(tev) -- we are already at the end of touch event -- so reset the state self:clearState(slot) - else + else -- last tev in this slot is cleared by last two finger tap self:clearState(slot) return { @@ -368,7 +368,19 @@ function GestureDetector:panState(tev) time = tev.timev, } end + DEBUG("pan release detected in slot", slot) + local release_pos = Geom:new{ + x = self.last_tevs[slot].x, + y = self.last_tevs[slot].y, + w = 0, h = 0, + } + local pan_release = { + ges = "pan_release", + pos = release_pos, + time = tev.timev, + } self:clearState(slot) + return pan_release else if self.states[slot] ~= self.panState then self.states[slot] = self.panState @@ -399,7 +411,7 @@ end function GestureDetector:holdState(tev, hold) DEBUG("in hold state...") - local slot = tev.slot + local slot = tev.slot -- when we switch to hold state, we pass additional param "hold" if tev.id ~= -1 and hold and self.last_tevs[slot].x and self.last_tevs[slot].y then self.states[slot] = self.holdState @@ -443,7 +455,7 @@ function GestureDetector:adjustGesCoordinate(ges) if ges.pos then ges.pos.x, ges.pos.y = (Screen.width - ges.pos.y), (ges.pos.x) end - if ges.ges == "swipe" then + if ges.ges == "swipe" or ges.ges == "pan" then if ges.direction == "down" then ges.direction = "left" elseif ges.direction == "up" then diff --git a/frontend/ui/inputevent.lua b/frontend/ui/inputevent.lua index dd19d738b..5daec6c61 100644 --- a/frontend/ui/inputevent.lua +++ b/frontend/ui/inputevent.lua @@ -2,7 +2,6 @@ require "ui/event" require "ui/device" require "ui/time" require "ui/gesturedetector" -require "settings" -- constants from EV_SYN = 0 @@ -112,7 +111,7 @@ function Key:match(sequence) return false end end - + return true end @@ -121,6 +120,7 @@ an interface to get input events ]] Input = { event_map = {}, + modifiers = {}, rotation_map = { [0] = {}, [1] = { Up = "Right", Right = "Down", Down = "Left", Left = "Up" }, @@ -287,6 +287,8 @@ function Input:init() elseif dev_mod == "KindleTouch" then input.open("/dev/input/event2") -- Home button input.open("/dev/input/event3") -- touchscreen + -- KT does have one key! + self.event_map[102] = "Home" -- update event hook function Input:eventAdjustHook(ev) if ev.type == EV_ABS then @@ -334,7 +336,7 @@ end function Input:setTimeout(cb, tv_out) local item = { - callback = cb, + callback = cb, deadline = tv_out, } for k,v in ipairs(self.timer_callbacks) do @@ -443,7 +445,7 @@ function Input:handleTouchEv(ev) local touch_ges = GestureDetector:feedEvent(self.MTSlots) self.MTSlots = {} if touch_ges then - return Event:new("Gesture", + return Event:new("Gesture", GestureDetector:adjustGesCoordinate(touch_ges) ) end @@ -451,7 +453,7 @@ function Input:handleTouchEv(ev) elseif ev.type == EV_ABS then if #self.MTSlots == 0 then table.insert(self.MTSlots, self:getMtSlot(self.cur_slot)) - end + end if ev.code == ABS_MT_SLOT then if self.cur_slot ~= ev.value then table.insert(self.MTSlots, self:getMtSlot(ev.value)) @@ -471,7 +473,7 @@ function Input:waitEvent(timeout_us, timeout_s) -- wrapper for input.waitForEvents that will retry for some cases local ok, ev local wait_deadline = TimeVal:now() + TimeVal:new{ - sec = timeout_s, + sec = timeout_s, usec = timeout_us } while true do @@ -490,7 +492,7 @@ function Input:waitEvent(timeout_us, timeout_s) -- Do we really need to clear all setTimeout after -- decided a gesture? FIXME Input.timer_callbacks = {} - return Event:new("Gesture", + return Event:new("Gesture", GestureDetector:adjustGesCoordinate(touch_ges) ) end -- EOF if touch_ges @@ -520,13 +522,10 @@ function Input:waitEvent(timeout_us, timeout_s) end if ok and ev then - ev = self:eventAdjustHook(ev) - if G_debug_mode then - local log = ev.type.."|"..ev.code.."|" - ..ev.value.."|"..ev.time.sec.."|"..ev.time.usec.."\n" - G_ev_log:write(log) - G_ev_log:flush() + if Dbg.is_on and ev then + Dbg:logEv(ev) end + ev = self:eventAdjustHook(ev) if ev.type == EV_KEY then return self:handleKeyBoardEv(ev) elseif ev.type == EV_ABS or ev.type == EV_SYN then diff --git a/frontend/ui/reader/readerbookmark.lua b/frontend/ui/reader/readerbookmark.lua index 503f83468..8dc05530e 100644 --- a/frontend/ui/reader/readerbookmark.lua +++ b/frontend/ui/reader/readerbookmark.lua @@ -1,4 +1,4 @@ -require "ui/notification" +require "ui/widget/notification" ReaderBookmark = InputContainer:new{ bm_menu_title = "Bookmarks", @@ -89,7 +89,7 @@ function ReaderBookmark:onShowBookmark() local bm_menu = Menu:new{ title = "Bookmarks", item_table = self.bookmarks, - width = Screen:getWidth()-20, + width = Screen:getWidth()-20, height = Screen:getHeight(), } -- buid up menu widget method as closure @@ -111,7 +111,7 @@ function ReaderBookmark:onShowBookmark() dimen = Screen:getSize(), bm_menu, } - bm_menu.close_callback = function() + bm_menu.close_callback = function() UIManager:close(menu_container) end @@ -119,9 +119,9 @@ function ReaderBookmark:onShowBookmark() return true end -function ReaderBookmark:addToMainMenu(item_table) +function ReaderBookmark:addToMainMenu(tab_item_table) -- insert table to main reader menu - table.insert(item_table, { + table.insert(tab_item_table.navi, { text = self.bm_menu_title, callback = function() self:onShowBookmark() @@ -154,7 +154,7 @@ function ReaderBookmark:addBookmark(pn_or_xp) return self:isBookmarkInSequence(a, b) end) return true -end +end function ReaderBookmark:isBookmarkInSequence(a, b) return a.page < b.page diff --git a/frontend/ui/reader/readerconfig.lua b/frontend/ui/reader/readerconfig.lua index 794d5da12..49faba8ab 100644 --- a/frontend/ui/reader/readerconfig.lua +++ b/frontend/ui/reader/readerconfig.lua @@ -1,4 +1,4 @@ -require "ui/config" +require "ui/widget/config" Configurable = {} @@ -47,7 +47,7 @@ end ReaderConfig = InputContainer:new{ dimen = Geom:new{ - x = 0, + x = 0, y = 7*Screen:getHeight()/8, w = Screen:getWidth(), h = Screen:getHeight()/8, @@ -82,8 +82,11 @@ function ReaderConfig:onShowConfigMenu() ui = self.ui, configurable = self.configurable, config_options = self.options, + close_callback = function() + self.ui:handleEvent(Event:new("RestoreHinting")) + end, } - + self.ui:handleEvent(Event:new("DisableHinting")) UIManager:show(self.config_dialog) return true diff --git a/frontend/ui/reader/readercropping.lua b/frontend/ui/reader/readercropping.lua index 0dd9fd571..5c4dcf793 100644 --- a/frontend/ui/reader/readercropping.lua +++ b/frontend/ui/reader/readercropping.lua @@ -1,12 +1,13 @@ -require "ui/widget" -require "ui/bbox" +require "ui/widget/group" +require "ui/widget/bbox" +require "ui/widget/button" PageCropDialog = VerticalGroup:new{ ok_text = "OK", cancel_text = "Cancel", ok_callback = function() end, cancel_callback = function() end, - button_width = math.floor(70*Screen:getDPI()/167), + button_width = math.floor(scaleByDPI(70)), } function PageCropDialog:init() @@ -63,6 +64,8 @@ function ReaderCropping:onPageCrop(mode) -- backup original page scroll self.orig_page_scroll = self.view.page_scroll self.view.page_scroll = false + -- backup and disable original hinting state + self.ui:handleEvent(Event:new("DisableHinting")) -- backup original reflow mode as cropping use non-reflow mode self.orig_reflow_mode = self.document.configurable.text_wrap if self.orig_reflow_mode == 1 then @@ -110,6 +113,8 @@ function ReaderCropping:onCancelPageCrop() end function ReaderCropping:exitPageCrop(confirmed) + -- restore hinting state + self.ui:handleEvent(Event:new("RestoreHinting")) -- restore page scroll self.view.page_scroll = self.orig_page_scroll -- restore view bgcolor diff --git a/frontend/ui/reader/readerdogear.lua b/frontend/ui/reader/readerdogear.lua index f8622f927..4ae3cf920 100644 --- a/frontend/ui/reader/readerdogear.lua +++ b/frontend/ui/reader/readerdogear.lua @@ -1,3 +1,4 @@ +require "ui/widget/image" ReaderDogear = RightContainer:new{} @@ -13,4 +14,4 @@ end function ReaderDogear:onSetDogearVisibility(visible) self.view.dogear_visible = visible return true -end \ No newline at end of file +end diff --git a/frontend/ui/reader/readerfont.lua b/frontend/ui/reader/readerfont.lua index 7443a2bcb..907b05d66 100644 --- a/frontend/ui/reader/readerfont.lua +++ b/frontend/ui/reader/readerfont.lua @@ -13,11 +13,11 @@ function ReaderFont:init() -- add shortcut for keyboard self.key_events = { ShowFontMenu = { {"F"}, doc = "show font menu" }, - IncreaseSize = { - { "Shift", Input.group.PgFwd }, - doc = "increase font size", + IncreaseSize = { + { "Shift", Input.group.PgFwd }, + doc = "increase font size", event = "ChangeSize", args = "increase" }, - DecreaseSize = { + DecreaseSize = { { "Shift", Input.group.PgBack }, doc = "decrease font size", event = "ChangeSize", args = "decrease" }, @@ -52,33 +52,33 @@ end function ReaderFont:onReadSettings(config) self.font_face = config:readSetting("font_face") - if not self.font_face then + if not self.font_face then self.font_face = self.ui.document.default_font end self.ui.document:setFontFace(self.font_face) self.header_font_face = config:readSetting("header_font_face") - if not self.header_font_face then + if not self.header_font_face then self.header_font_face = self.ui.document.header_font end self.ui.document:setHeaderFont(self.header_font_face) self.font_size = config:readSetting("font_size") - if not self.font_size then + if not self.font_size then --@TODO change this! 12.01 2013 (houqp) self.font_size = 29 end self.ui.document:setFontSize(self.font_size) self.line_space_percent = config:readSetting("line_space_percent") - if not self.line_space_percent then + if not self.line_space_percent then self.line_space_percent = 100 else self.ui.document:setInterlineSpacePercent(self.line_space_percent) end self.gamma_index = config:readSetting("gamma_index") - if not self.gamma_index then + if not self.gamma_index then self.gamma_index = 15 end self.ui.document:setGammaIndex(self.gamma_index) @@ -104,7 +104,7 @@ function ReaderFont:onShowFontMenu() main_menu, dimen = Screen:getSize(), } - main_menu.close_callback = function () + main_menu.close_callback = function () UIManager:close(menu_container) end -- show menu @@ -211,9 +211,9 @@ function ReaderFont:setFont(face) end end -function ReaderFont:addToMainMenu(item_table) +function ReaderFont:addToMainMenu(tab_item_table) -- insert table to main reader menu - table.insert(item_table, { + table.insert(tab_item_table.typeset, { text = self.font_menu_title, sub_item_table = self.face_table, }) diff --git a/frontend/ui/reader/readerfooter.lua b/frontend/ui/reader/readerfooter.lua index ddb72eeaf..019ccfbe0 100644 --- a/frontend/ui/reader/readerfooter.lua +++ b/frontend/ui/reader/readerfooter.lua @@ -1,3 +1,4 @@ +require "ui/widget/progress" ReaderFooter = InputContainer:new{ pageno = nil, diff --git a/frontend/ui/reader/readerhinting.lua b/frontend/ui/reader/readerhinting.lua new file mode 100644 index 000000000..1fad17ba1 --- /dev/null +++ b/frontend/ui/reader/readerhinting.lua @@ -0,0 +1,19 @@ + +ReaderHinting = EventListener:new{ + hinting_states = {} +} + +function ReaderHinting:onSetHinting(hinting) + self.view.hinting = hinting +end + +function ReaderHinting:onDisableHinting() + table.insert(self.hinting_states, self.view.hinting) + self.view.hinting = false + return true +end + +function ReaderHinting:onRestoreHinting() + self.view.hinting = table.remove(self.hinting_states) + return true +end \ No newline at end of file diff --git a/frontend/ui/reader/readermenu.lua b/frontend/ui/reader/readermenu.lua index 1f53b2b0b..a315e6100 100644 --- a/frontend/ui/reader/readermenu.lua +++ b/frontend/ui/reader/readermenu.lua @@ -1,11 +1,24 @@ +require "ui/widget/menu" +require "ui/widget/touchmenu" + ReaderMenu = InputContainer:new{ _name = "ReaderMenu", - item_table = {}, + tab_item_table = nil, registered_widgets = {}, } function ReaderMenu:init() - self.item_table = {} + self.tab_item_table = { + main = { + icon = "resources/icons/appbar.pokeball.png", + }, + navi = { + icon = "resources/icons/appbar.page.corner.bookmark.png", + }, + typeset = { + icon = "resources/icons/appbar.page.text.png", + }, + } self.registered_widgets = {} if Device:hasKeyboard() then @@ -32,34 +45,14 @@ function ReaderMenu:initGesListener() end function ReaderMenu:setUpdateItemTable() - table.insert(self.item_table, { - text = "Screen rotate", - sub_item_table = { - { - text = "landscape", - callback = function() - self.ui:handleEvent( - Event:new("SetScreenMode", "landscape")) - end - }, - { - text = "portrait", - callback = function() - self.ui:handleEvent( - Event:new("SetScreenMode", "portrait")) - end - }, - } - }) - for _, widget in pairs(self.registered_widgets) do - widget:addToMainMenu(self.item_table) + widget:addToMainMenu(self.tab_item_table) end - table.insert(self.item_table, { + table.insert(self.tab_item_table.main, { text = "Return to file manager", callback = function() - self.ui:handleEvent(Event:new("RestoreScreenMode", + self.ui:handleEvent(Event:new("RestoreScreenMode", G_reader_settings:readSetting("screen_mode") or "portrait")) UIManager:close(self.menu_container) self.ui:onClose() @@ -68,27 +61,48 @@ function ReaderMenu:setUpdateItemTable() end function ReaderMenu:onShowMenu() - if #self.item_table == 0 then + if #self.tab_item_table.main == 0 then self:setUpdateItemTable() end - local main_menu = Menu:new{ - title = "Document menu", - item_table = self.item_table, - width = Screen:getWidth() - 100, - } - local menu_container = CenterContainer:new{ + name = "haha", ignore = "height", dimen = Screen:getSize(), - main_menu, } - main_menu.close_callback = function () + + local main_menu = nil + if Device:isTouchDevice() then + main_menu = TouchMenu:new{ + name = "wocao", + tab_item_table = { + self.tab_item_table.navi, + self.tab_item_table.typeset, + self.tab_item_table.main, + }, + show_parent = menu_container, + } + else + main_menu = Menu:new{ + title = "Document menu", + item_table = {}, + width = Screen:getWidth() - 100, + } + + for _,item_table in pairs(self.tab_item_table) do + for k,v in ipairs(item_table) do + table.insert(main_menu.item_table, v) + end + end + end + + main_menu.close_callback = function () UIManager:close(menu_container) end + + menu_container[1] = main_menu -- maintain a reference to menu_container self.menu_container = menu_container - UIManager:show(menu_container) return true diff --git a/frontend/ui/reader/readerpaging.lua b/frontend/ui/reader/readerpaging.lua index 52cc2d0bc..e317ed241 100644 --- a/frontend/ui/reader/readerpaging.lua +++ b/frontend/ui/reader/readerpaging.lua @@ -6,38 +6,38 @@ ReaderPaging = InputContainer:new{ visible_area = nil, page_area = nil, show_overlap_enable = true, - overlap = 20 * Screen:getDPI()/167, + overlap = scaleByDPI(20), } function ReaderPaging:init() if Device:hasKeyboard() then self.key_events = { - GotoNextPage = { + GotoNextPage = { {Input.group.PgFwd}, doc = "go to next page", event = "GotoPageRel", args = 1 }, - GotoPrevPage = { + GotoPrevPage = { {Input.group.PgBack}, doc = "go to previous page", event = "GotoPageRel", args = -1 }, - GotoFirst = { + GotoFirst = { {"1"}, doc = "go to start", event = "GotoPercent", args = 0}, - Goto11 = { + Goto11 = { {"2"}, doc = "go to 11%", event = "GotoPercent", args = 11}, - Goto22 = { + Goto22 = { {"3"}, doc = "go to 22%", event = "GotoPercent", args = 22}, - Goto33 = { + Goto33 = { {"4"}, doc = "go to 33%", event = "GotoPercent", args = 33}, - Goto44 = { + Goto44 = { {"5"}, doc = "go to 44%", event = "GotoPercent", args = 44}, - Goto55 = { + Goto55 = { {"6"}, doc = "go to 55%", event = "GotoPercent", args = 55}, - Goto66 = { + Goto66 = { {"7"}, doc = "go to 66%", event = "GotoPercent", args = 66}, - Goto77 = { + Goto77 = { {"8"}, doc = "go to 77%", event = "GotoPercent", args = 77}, - Goto88 = { + Goto88 = { {"9"}, doc = "go to 88%", event = "GotoPercent", args = 88}, - GotoLast = { + GotoLast = { {"0"}, doc = "go to end", event = "GotoPercent", args = 100}, } end @@ -62,7 +62,7 @@ function ReaderPaging:initGesListener() GestureRange:new{ ges = "tap", range = Geom:new{ - x = 0, + x = 0, y = Screen:getHeight()/4, w = Screen:getWidth()/4, h = 5*Screen:getHeight()/8, @@ -100,6 +100,16 @@ function ReaderPaging:initGesListener() rate = 4.0, } }, + PanRelease = { + GestureRange:new{ + ges = "pan_release", + range = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight(), + }, + } + }, } end @@ -128,45 +138,69 @@ end function ReaderPaging:onToggleFlipping() self.view.flipping_visible = not self.view.flipping_visible - self.flipping_page = self.view.flipping_visible and self.current_page or nil + self.flipping_mode = self.view.flipping_visible + self.flipping_page = self.current_page + if self.flipping_mode then + self:updateOriginalPage(self.current_page) + end + self.ui:handleEvent(Event:new("SetHinting", not self.flipping_mode)) + UIManager:setDirty(self.view.dialog, "partial") +end + +function ReaderPaging:updateOriginalPage(page) + self.original_page = page +end + +function ReaderPaging:updateFlippingPage(page) + self.flipping_page = page +end + +function ReaderPaging:flipping(flipping_page, flipping_ges) + local read = flipping_page - 1 + local unread = self.number_of_pages - flipping_page + local whole = self.number_of_pages + local rel_proportion = flipping_ges.distance / Screen:getWidth() + local abs_proportion = flipping_ges.distance / Screen:getHeight() + if flipping_ges.direction == "right" then + self:gotoPage(flipping_page - math.floor(read*rel_proportion)) + elseif flipping_ges.direction == "left" then + self:gotoPage(flipping_page + math.floor(unread*rel_proportion)) + elseif flipping_ges.direction == "down" then + self:gotoPage(flipping_page - math.floor(whole*abs_proportion)) + elseif flipping_ges.direction == "up" then + self:gotoPage(flipping_page + math.floor(whole*abs_proportion)) + end UIManager:setDirty(self.view.dialog, "partial") end function ReaderPaging:onSwipe(arg, ges) - if self.flipping_page == nil then - if ges.direction == "left" or ges.direction == "up" then - self:onPagingRel(1) - elseif ges.direction == "right" or ges.direction == "down" then - self:onPagingRel(-1) - end - elseif self.flipping_page then - self:gotoPage(self.flipping_page) + if self.flipping_mode then + self:flipping(self.flipping_page, ges) + self:updateFlippingPage(self.current_page) + elseif self.original_page then + self:gotoPage(self.original_page) + self:updateOriginalPage(nil) + elseif ges.direction == "left" or ges.direction == "up" then + self:onPagingRel(1) + elseif ges.direction == "right" or ges.direction == "down" then + self:onPagingRel(-1) end return true end function ReaderPaging:onPan(arg, ges) - if self.flipping_page then - local read = self.flipping_page - 1 - local unread = self.number_of_pages - self.flipping_page - local whole = self.number_of_pages - local rel_proportion = ges.distance / Screen:getWidth() - local abs_proportion = ges.distance / Screen:getHeight() - if ges.direction == "right" then - self:gotoPage(self.flipping_page - math.floor(read*rel_proportion)) - elseif ges.direction == "left" then - self:gotoPage(self.flipping_page + math.floor(unread*rel_proportion)) - elseif ges.direction == "down" then - self:gotoPage(self.flipping_page - math.floor(whole*abs_proportion)) - elseif ges.direction == "up" then - self:gotoPage(self.flipping_page + math.floor(whole*abs_proportion)) - end - - UIManager:setDirty(self.view.dialog, "partial") + if self.flipping_mode then + self:flipping(self.flipping_page, ges) end return true end +function ReaderPaging:onPanRelease(arg, ges) + if self.flipping_mode then + self:updateFlippingPage(self.current_page) + end +end + function ReaderPaging:onZoomModeUpdate(new_mode) -- we need to remember zoom mode to handle page turn event self.zoom_mode = new_mode @@ -288,7 +322,11 @@ function ReaderPaging:updateLastPageState(state, blank_area, offset) local visible_area = Geom:new{x = 0, y = 0} visible_area.w, visible_area.h = blank_area.w, blank_area.h visible_area.x, visible_area.y = state.visible_area.x, state.visible_area.y - visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y) + if state.page == self.number_of_pages then + visible_area:offsetWithin(state.page_area, offset.x, offset.y) + else + visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y) + end -- shrink blank area by the height of visible area blank_area.h = blank_area.h - visible_area.h state.visible_area = visible_area @@ -300,7 +338,11 @@ function ReaderPaging:updateFirstPageState(state, blank_area, offset) visible_area.w, visible_area.h = blank_area.w, blank_area.h visible_area.x = state.page_area.x visible_area.y = state.visible_area.y + state.visible_area.h - visible_area.h - visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y) + if state.page == 1 then + visible_area:offsetWithin(state.page_area, offset.x, offset.y) + else + visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y) + end -- shrink blank area by the height of visible area blank_area.h = blank_area.h - visible_area.h state.visible_area = visible_area @@ -317,6 +359,12 @@ function ReaderPaging:onScrollPageRel(diff) x = 0, y = last_page_state.visible_area.h - self.overlap } + -- Scroll down offset should always be greater than 0 + -- otherwise if offset is less than 0 the height of blank area will be + -- larger than 0 even if page area is much larger than visible area, + -- which will trigger the drawing of next page leaving part of current + -- page undrawn. This should also be true for scroll up offset. + if offset.y < 0 then offset.y = 0 end local state = self:updateLastPageState(last_page_state, blank_area, offset) --DEBUG("updated state", state) self.view.page_states = {} @@ -327,7 +375,8 @@ function ReaderPaging:onScrollPageRel(diff) while blank_area.h > 0 do blank_area.h = blank_area.h - self.view.page_gap.height if blank_area.h > 0 then - self:gotoPage(state.page + 1, "scrolling") + if self.current_page == self.number_of_pages then break end + self:gotoPage(self.current_page + 1, "scrolling") local state = self:getNextPageState(blank_area, Geom:new{}) --DEBUG("new state", state) table.insert(self.view.page_states, state) @@ -340,6 +389,8 @@ function ReaderPaging:onScrollPageRel(diff) x = 0, y = -first_page_state.visible_area.h + self.overlap } + -- scroll up offset should always be less than 0 + if offset.y > 0 then offset.y = 0 end local state = self:updateFirstPageState(first_page_state, blank_area, offset) --DEBUG("updated state", state) self.view.page_states = {} @@ -350,7 +401,8 @@ function ReaderPaging:onScrollPageRel(diff) while blank_area.h > 0 do blank_area.h = blank_area.h - self.view.page_gap.height if blank_area.h > 0 then - self:gotoPage(state.page - 1, "scrolling") + if self.current_page == 1 then break end + self:gotoPage(self.current_page - 1, "scrolling") local state = self:getPrevPageState(blank_area, Geom:new{}) --DEBUG("new state", state) table.insert(self.view.page_states, 1, state) diff --git a/frontend/ui/reader/readerscreenshot.lua b/frontend/ui/reader/readerscreenshot.lua index 0c438abab..7fabd4437 100644 --- a/frontend/ui/reader/readerscreenshot.lua +++ b/frontend/ui/reader/readerscreenshot.lua @@ -3,14 +3,14 @@ ReaderScreenshot = InputContainer:new{} function ReaderScreenshot:init() local diagonal = math.sqrt( - math.pow(Screen:getWidth(), 2) + + math.pow(Screen:getWidth(), 2) + math.pow(Screen:getHeight(), 2) ) self.ges_events = { Screenshot = { GestureRange:new{ ges = "two_finger_tap", - scale = {diagonal - 80*Screen:getDPI()/167, diagonal}, + scale = {diagonal - scaleByDPI(80), diagonal}, rate = 1.0, } }, diff --git a/frontend/ui/reader/readertoc.lua b/frontend/ui/reader/readertoc.lua index 8e66dc342..8460e5ab3 100644 --- a/frontend/ui/reader/readertoc.lua +++ b/frontend/ui/reader/readertoc.lua @@ -81,7 +81,7 @@ function ReaderToc:onShowToc() title = "Table of Contents", item_table = self.toc, ui = self.ui, - width = Screen:getWidth()-20, + width = Screen:getWidth()-20, height = Screen:getHeight(), } function toc_menu:onMenuChoice(item) @@ -92,7 +92,7 @@ function ReaderToc:onShowToc() dimen = Screen:getSize(), toc_menu, } - toc_menu.close_callback = function() + toc_menu.close_callback = function() UIManager:close(menu_container) end @@ -100,9 +100,9 @@ function ReaderToc:onShowToc() return true end -function ReaderToc:addToMainMenu(item_table) +function ReaderToc:addToMainMenu(tab_item_table) -- insert table to main reader menu - table.insert(item_table, { + table.insert(tab_item_table.navi, { text = self.toc_menu_title, callback = function() self:onShowToc() diff --git a/frontend/ui/reader/readertypeset.lua b/frontend/ui/reader/readertypeset.lua index 7720ede53..7607e98af 100644 --- a/frontend/ui/reader/readertypeset.lua +++ b/frontend/ui/reader/readertypeset.lua @@ -10,7 +10,7 @@ end function ReaderTypeset:onReadSettings(config) self.css = config:readSetting("css") - if self.css and self.css ~= "" then + if self.css and self.css ~= "" then self.ui.document:setStyleSheet(self.css) else self.ui.document:setStyleSheet("") @@ -56,7 +56,7 @@ function ReaderTypeset:genStyleSheetMenu() if lfs.attributes("./data/"..f, "mode") == "file" and string.match(f, "%.css$") then table.insert(file_list, { text = f, - callback = function() + callback = function() self:setStyleSheet("./data/"..f) end }) @@ -98,9 +98,9 @@ function ReaderTypeset:toggleEmbeddedStyleSheet() self.ui:handleEvent(Event:new("UpdatePos")) end -function ReaderTypeset:addToMainMenu(item_table) +function ReaderTypeset:addToMainMenu(tab_item_table) -- insert table to main reader menu - table.insert(item_table, { + table.insert(tab_item_table.typeset, { text = self.css_menu_title, sub_item_table = self:genStyleSheetMenu(), }) diff --git a/frontend/ui/reader/readerview.lua b/frontend/ui/reader/readerview.lua index d21158f35..4fb73d4ff 100644 --- a/frontend/ui/reader/readerview.lua +++ b/frontend/ui/reader/readerview.lua @@ -5,7 +5,7 @@ require "ui/reader/readerdogear" ReaderView = OverlapGroup:new{ _name = "ReaderView", document = nil, - + -- single page state state = { page = 0, @@ -23,14 +23,15 @@ ReaderView = OverlapGroup:new{ page_states = {}, scroll_mode = "vertical", page_gap = { - width = 8 * Screen:getDPI()/167, - height = 8 * Screen:getDPI()/167, + width = scaleByDPI(8), + height = scaleByDPI(8), color = 8, }, -- DjVu page rendering mode (used in djvu.c:drawPage()) render_mode = 0, -- default to COLOR -- Crengine view mode view_mode = "page", -- default to page mode + hinting = true, -- visible area within current viewing page visible_area = Geom:new{x = 0, y = 0}, @@ -38,7 +39,7 @@ ReaderView = OverlapGroup:new{ page_area = Geom:new{}, -- dimen for area to dim dim_area = Geom:new{w = 0, h = 0}, - -- has footer + -- has footer footer_visible = false, -- has dogear dogear_visible = false, @@ -72,7 +73,7 @@ function ReaderView:paintTo(bb, x, y) else self:drawPageSurround(bb, x, y) end - + -- draw page content if self.ui.document.info.has_pages then if self.page_scroll then @@ -87,16 +88,16 @@ function ReaderView:paintTo(bb, x, y) self:drawScrollView(bb, x, y) end end - + -- dim last read area - if self.document.view_mode ~= "page" + if self.document.view_mode ~= "page" and self.dim_area.w ~= 0 and self.dim_area.h ~= 0 then bb:dimRect( self.dim_area.x, self.dim_area.y, self.dim_area.w, self.dim_area.h ) end - + -- paint dogear if self.dogear_visible then self.dogear:paintTo(bb, x, y) @@ -118,12 +119,12 @@ end function ReaderView:drawPageSurround(bb, x, y) if self.dimen.h > self.visible_area.h then bb:paintRect(x, y, self.dimen.w, self.state.offset.y, self.outer_page_color) - bb:paintRect(x, y + self.dimen.h - self.state.offset.y - 1, + bb:paintRect(x, y + self.dimen.h - self.state.offset.y - 1, self.dimen.w, self.state.offset.y + 1, self.outer_page_color) end if self.dimen.w > self.visible_area.w then bb:paintRect(x, y, self.state.offset.x, self.dimen.h, self.outer_page_color) - bb:paintRect(x + self.dimen.w - self.state.offset.x - 1, y, + bb:paintRect(x + self.dimen.w - self.state.offset.x - 1, y, self.state.offset.x + 1, self.dimen.h, self.outer_page_color) end end @@ -142,13 +143,15 @@ function ReaderView:drawScrollPages(bb, x, y) state.gamma, self.render_mode) pos.y = pos.y + state.visible_area.h - -- draw page gap if not the last part + -- draw page gap if not the last part if page ~= #self.page_states then self:drawPageGap(bb, pos.x, pos.y) pos.y = pos.y + self.page_gap.height end end - UIManager:scheduleIn(0, function() self.ui:handleEvent(Event:new("HintPage")) end) + UIManager:scheduleIn(0, function() + self.ui:handleEvent(Event:new("HintPage", self.hinting)) + end) end function ReaderView:drawPageGap(bb, x, y) @@ -170,7 +173,9 @@ function ReaderView:drawSinglePage(bb, x, y) self.state.rotation, self.state.gamma, self.render_mode) - UIManager:scheduleIn(0, function() self.ui:handleEvent(Event:new("HintPage")) end) + UIManager:scheduleIn(0, function() + self.ui:handleEvent(Event:new("HintPage", self.hinting)) + end) end function ReaderView:drawPageView(bb, x, y) @@ -294,7 +299,7 @@ end function ReaderView:onSetFullScreen(full_screen) self.footer_visible = not full_screen - self:onSetDimensions(Screen:getSize()) + self.ui:handleEvent(Event:new("SetDimensions", Screen:getSize())) end function ReaderView:onToggleScrollMode(page_scroll) diff --git a/frontend/ui/reader/readerzooming.lua b/frontend/ui/reader/readerzooming.lua index 3528c5ac2..55be19ddb 100644 --- a/frontend/ui/reader/readerzooming.lua +++ b/frontend/ui/reader/readerzooming.lua @@ -11,29 +11,29 @@ function ReaderZooming:init() if Device:hasKeyboard() then self.key_events = { ZoomIn = { - { "Shift", Input.group.PgFwd }, + { "Shift", Input.group.PgFwd }, doc = "zoom in", - event = "Zoom", args = "in" + event = "Zoom", args = "in" }, ZoomOut = { - { "Shift", Input.group.PgBack }, + { "Shift", Input.group.PgBack }, doc = "zoom out", - event = "Zoom", args = "out" + event = "Zoom", args = "out" }, ZoomToFitPage = { - { "A" }, + { "A" }, doc = "zoom to fit page", - event = "SetZoomMode", args = "page" + event = "SetZoomMode", args = "page" }, ZoomToFitContent = { - { "Shift", "A" }, + { "Shift", "A" }, doc = "zoom to fit content", - event = "SetZoomMode", args = "content" + event = "SetZoomMode", args = "content" }, ZoomToFitPageWidth = { - { "S" }, + { "S" }, doc = "zoom to fit page width", - event = "SetZoomMode", args = "pagewidth" + event = "SetZoomMode", args = "pagewidth" }, ZoomToFitContentWidth = { { "Shift", "S" }, @@ -41,9 +41,9 @@ function ReaderZooming:init() event = "SetZoomMode", args = "contentwidth" }, ZoomToFitPageHeight = { - { "D" }, + { "D" }, doc = "zoom to fit page height", - event = "SetZoomMode", args = "pageheight" + event = "SetZoomMode", args = "pageheight" }, ZoomToFitContentHeight = { { "Shift", "D" }, @@ -57,7 +57,7 @@ end function ReaderZooming:onReadSettings(config) -- @TODO config file from old code base uses globalzoom_mode - -- instead of zoom_mode, we need to handle this imcompatibility + -- instead of zoom_mode, we need to handle this imcompatibility -- 04.12 2012 (houqp) local zoom_mode = config:readSetting("zoom_mode") if not zoom_mode then @@ -110,12 +110,13 @@ function ReaderZooming:onPageUpdate(new_page_no) end function ReaderZooming:onHintPage() + if not self.view.hinting then return true end if self.current_page < self.ui.document.info.number_of_pages then self.ui.document:hintPage( - self.view.state.page + 1, + self.view.state.page + 1, self:getZoom(self.view.state.page + 1), - self.view.state.rotation, - self.view.state.gamma, + self.view.state.rotation, + self.view.state.gamma, self.view.render_mode) end return true @@ -125,7 +126,7 @@ function ReaderZooming:getZoom(pageno) -- check if we're in bbox mode and work on bbox if that's the case local zoom = nil local page_size = {} - if self.zoom_mode == "content" + if self.zoom_mode == "content" or self.zoom_mode == "contentwidth" or self.zoom_mode == "contentheight" then local ubbox_dimen = self.ui.document:getUsedBBoxDimensions(pageno, 1) @@ -178,9 +179,9 @@ function ReaderZooming:genSetZoomModeCallBack(mode) end end -function ReaderZooming:addToMainMenu(item_table) +function ReaderZooming:addToMainMenu(tab_item_table) if self.ui.document.info.has_pages then - table.insert(item_table, { + table.insert(tab_item_table.typeset, { text = "Switch zoom mode", sub_item_table = { { diff --git a/frontend/ui/readerui.lua b/frontend/ui/readerui.lua index ea69f128e..7a26fe6cd 100644 --- a/frontend/ui/readerui.lua +++ b/frontend/ui/readerui.lua @@ -1,4 +1,3 @@ -require "ui/ui" require "ui/reader/readerview" require "ui/reader/readerzooming" require "ui/reader/readerpanning" @@ -15,6 +14,7 @@ require "ui/reader/readercropping" require "ui/reader/readerkopt" require "ui/reader/readercopt" require "ui/reader/readerscreenshot" +require "ui/reader/readerhinting" --[[ This is an abstraction for a reader interface @@ -132,6 +132,14 @@ function ReaderUI:init() document = self.document, } table.insert(self, cropper) + -- hinting controller + local hinter = ReaderHinting:new{ + dialog = self.dialog, + view = self[1], + ui = self, + document = self.document, + } + table.insert(self, hinter) else -- make sure we load document first before calling any callback table.insert(self.postInitCallback, function() diff --git a/frontend/ui/screen.lua b/frontend/ui/screen.lua index d7f3cd252..6ea5dee38 100644 --- a/frontend/ui/screen.lua +++ b/frontend/ui/screen.lua @@ -18,7 +18,7 @@ --[[ Codes for rotation modes: -1 for no rotation, +1 for no rotation, 2 for landscape with bottom on the right side of screen, etc. 2 @@ -26,8 +26,8 @@ Codes for rotation modes: | +----------+ | | | | | | | Freedom! | | - | | | | - | | | | + | | | | + | | | | 3 | | | | 1 | | | | | | | | @@ -99,6 +99,15 @@ function Screen:getDPI() return Device:getModel() == "KindlePaperWhite" and 212 or 167 end +function Screen:scaleByDPI(px) + return (px * self:getDPI()/167) +end + +-- make a shortcut to Screen:scaleByDPI +function scaleByDPI(px) + return Screen:scaleByDPI(px) +end + function Screen:getPitch() return self.fb:getPitch() end diff --git a/frontend/ui/ui.lua b/frontend/ui/uimanager.lua similarity index 98% rename from frontend/ui/ui.lua rename to frontend/ui/uimanager.lua index 76b353967..fcdae4423 100644 --- a/frontend/ui/ui.lua +++ b/frontend/ui/uimanager.lua @@ -1,9 +1,8 @@ require "ui/geometry" require "ui/device" require "ui/inputevent" -require "ui/widget" require "ui/screen" -require "settings" -- for DEBUG(), TODO: put DEBUG() somewhere else +require "debug" -- initialize output module, this must be initialized before Input Screen:init() @@ -25,7 +24,7 @@ UIManager = { -- trigger a full refresh when counter reaches FULL_REFRESH_COUNT FULL_REFRESH_COUNT = 6, refresh_count = 0, - + _running = true, _window_stack = {}, _execution_stack = {}, @@ -156,7 +155,7 @@ function UIManager:run() while self._running do local now = { util.gettime() } local wait_until = self:checkTasks() - + --DEBUG("---------------------------------------------------") --DEBUG("exec stack", self._execution_stack) --DEBUG("window stack", self._window_stack) @@ -181,19 +180,19 @@ function UIManager:run() end if self._dirty[widget.widget] == "full" then force_full_refresh = true - end + end -- and remove from list after painting self._dirty[widget.widget] = nil -- trigger repaint dirty = true end end - + if self.full_refresh then dirty = true force_full_refresh = true end - + self.repaint_all = false self.full_refresh = false @@ -214,9 +213,9 @@ function UIManager:run() -- reset refresh_type self.refresh_type = 1 end - + self:checkTasks() - + -- wait for next event -- note that we will skip that if in the meantime we have tasks that are ready to run local input_event = nil diff --git a/frontend/ui/widget.lua b/frontend/ui/widget.lua deleted file mode 100644 index dbeae3f3a..000000000 --- a/frontend/ui/widget.lua +++ /dev/null @@ -1,748 +0,0 @@ -require "ui/screen" -require "ui/rendertext" -require "ui/graphics" -require "ui/image" -require "ui/event" -require "ui/inputevent" -require "ui/gesturedetector" -require "ui/font" - ---[[ -The EventListener is an interface that handles events - -EventListeners have a rudimentary event handler/dispatcher that -will call a method "onEventName" for an event with name -"EventName" - -]] -EventListener = {} - -function EventListener:new(o) - local o = o or {} - setmetatable(o, self) - self.__index = self - if o.init then o:init() end - return o -end - -function EventListener:handleEvent(event) - if self[event.handler] then - return self[event.handler](self, unpack(event.args)) - end -end - - ---[[ -This is a generic Widget interface - -widgets can be queried about their size and can be paint. -that's it for now. Probably we need something more elaborate -later. - -if the table that was given to us as parameter has an "init" -method, it will be called. use this to set _instance_ variables -rather than class variables. -]] -Widget = EventListener:new() - -function Widget:new(o) - local o = o or {} - setmetatable(o, self) - self.__index = self - -- Both o._init and o.init are called on object create. But o._init is used - -- for base widget initialization (basic component used to build other - -- widgets). While o.init is for higher level widgets, for example Menu - -- Widget - if o._init then o:_init() end - if o.init then o:init() end - return o -end - -function Widget:getSize() - return self.dimen -end - -function Widget:paintTo(bb, x, y) -end - ---[[ -WidgetContainer is a container for another Widget -]] -WidgetContainer = Widget:new() - -function WidgetContainer:getSize() - if self.dimen then - -- fixed size - return self.dimen - elseif self[1] then - -- return size of first child widget - return self[1]:getSize() - else - return Geom:new{ w = 0, h = 0 } - end -end - ---[[ -delete all child widgets -]]-- -function WidgetContainer:clear() - while table.remove(self) do end -end - -function WidgetContainer:paintTo(bb, x, y) - -- default to pass request to first child widget - if self[1] then - return self[1]:paintTo(bb, x, y) - end -end - -function WidgetContainer:propagateEvent(event) - -- propagate to children - for _, widget in ipairs(self) do - if widget:handleEvent(event) then - -- stop propagating when an event handler returns true - return true - end - end - return false -end - ---[[ -Containers will pass events to children or react on them themselves -]]-- -function WidgetContainer:handleEvent(event) - if not self:propagateEvent(event) then - -- call our own standard event handler - return Widget.handleEvent(self, event) - else - return true - end -end - -function WidgetContainer:free() - for _, widget in ipairs(self) do - if widget.free then widget:free() end - end -end - ---[[ -BottomContainer contains its content (1 widget) at the bottom of its own dimensions -]] -BottomContainer = WidgetContainer:new() - -function BottomContainer:paintTo(bb, x, y) - local contentSize = self[1]:getSize() - if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then - -- throw error? paint to scrap buffer and blit partially? - -- for now, we ignore this - end - self[1]:paintTo(bb, - x + (self.dimen.w - contentSize.w)/2, - y + (self.dimen.h - contentSize.h)) -end - ---[[ -CenterContainer centers its content (1 widget) within its own dimensions -]] -CenterContainer = WidgetContainer:new() - -function CenterContainer:paintTo(bb, x, y) - local contentSize = self[1]:getSize() - if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then - -- throw error? paint to scrap buffer and blit partially? - -- for now, we ignore this - end - local x_pos = x - local y_pos = y - if self.ignore ~= "height" then - y_pos = y + (self.dimen.h - contentSize.h)/2 - end - if self.ignore ~= "width" then - x_pos = x + (self.dimen.w - contentSize.w)/2 - end - self[1]:paintTo(bb, x_pos, y_pos) -end - ---[[ -LeftContainer aligns its content (1 widget) at the left of its own dimensions -]] -LeftContainer = WidgetContainer:new() - -function LeftContainer:paintTo(bb, x, y) - local contentSize = self[1]:getSize() - if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then - -- throw error? paint to scrap buffer and blit partially? - -- for now, we ignore this - end - self[1]:paintTo(bb, x , y + (self.dimen.h - contentSize.h)/2) -end - ---[[ -RightContainer aligns its content (1 widget) at the right of its own dimensions -]] -RightContainer = WidgetContainer:new() - -function RightContainer:paintTo(bb, x, y) - local contentSize = self[1]:getSize() - if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then - -- throw error? paint to scrap buffer and blit partially? - -- for now, we ignore this - end - self[1]:paintTo(bb, - x + (self.dimen.w - contentSize.w), - y + (self.dimen.h - contentSize.h)/2) -end - ---[[ -A FrameContainer is some graphics content (1 widget) that is surrounded by a frame -]] -FrameContainer = WidgetContainer:new{ - background = nil, - color = 15, - margin = 0, - radius = 0, - bordersize = 2, - padding = 5, -} - -function FrameContainer:getSize() - local content_size =self[1]:getSize() - return Geom:new{ - w = content_size.w + ( self.margin + self.bordersize + self.padding ) * 2, - h = content_size.h + ( self.margin + self.bordersize + self.padding ) * 2 - } -end - -function FrameContainer:paintTo(bb, x, y) - local my_size = self:getSize() - - if self.background then - bb:paintRoundedRect(x, y, my_size.w, my_size.h, self.background, self.radius) - end - if self.bordersize > 0 then - bb:paintBorder(x + self.margin, y + self.margin, - my_size.w - self.margin * 2, my_size.h - self.margin * 2, - self.bordersize, self.color, self.radius) - end - if self[1] then - self[1]:paintTo(bb, - x + self.margin + self.bordersize + self.padding, - y + self.margin + self.bordersize + self.padding) - end -end - ---[[ -A TextWidget puts a string on a single line -]] -TextWidget = Widget:new{ - text = nil, - face = nil, - color = 15, - _bb = nil, - _length = 0, - _height = 0, - _maxlength = 1200, -} - ---function TextWidget:_render() - --local h = self.face.size * 1.3 - --self._bb = Blitbuffer.new(self._maxlength, h) - --self._length = renderUtf8Text(self._bb, 0, h*0.8, self.face, self.text, self.color) ---end - -function TextWidget:getSize() - --if not self._bb then - --self:_render() - --end - --return { w = self._length, h = self._bb:getHeight() } - local tsize = sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true) - if not tsize then - return Geom:new{} - end - self._length = tsize.x - self._height = self.face.size * 1.5 - return Geom:new{ - w = self._length, - h = self._height, - } -end - -function TextWidget:paintTo(bb, x, y) - --if not self._bb then - --self:_render() - --end - --bb:blitFrom(self._bb, x, y, 0, 0, self._length, self._bb:getHeight()) - --@TODO Don't use kerning for monospaced fonts. (houqp) - renderUtf8Text(bb, x, y+self._height*0.7, self.face, self.text, true) -end - -function TextWidget:free() - if self._bb then - self._bb:free() - self._bb = nil - end -end - ---[[ -A TextWidget that handles long text wrapping -]] -TextBoxWidget = Widget:new{ - text = nil, - face = nil, - color = 15, - width = 400, -- in pixels - line_height = 0.3, -- in em - v_list = nil, - _bb = nil, - _length = 0, -} - -function TextBoxWidget:_wrapGreedyAlg(h_list) - local cur_line_width = 0 - local space_w = sizeUtf8Text(0, Screen:getWidth(), self.face, " ", true).x - local cur_line = {} - local v_list = {} - - for k,w in ipairs(h_list) do - cur_line_width = cur_line_width + w.width - if cur_line_width <= self.width then - cur_line_width = cur_line_width + space_w - table.insert(cur_line, w) - else - -- wrap to next line - table.insert(v_list, cur_line) - cur_line = {} - cur_line_width = w.width + space_w - table.insert(cur_line, w) - end - end - -- handle last line - table.insert(v_list, cur_line) - - return v_list -end - -function TextBoxWidget:_getVerticalList(alg) - -- build horizontal list - h_list = {} - for w in self.text:gmatch("%S+") do - word_box = {} - word_box.word = w - word_box.width = sizeUtf8Text(0, Screen:getWidth(), self.face, w, true).x - table.insert(h_list, word_box) - end - - -- @TODO check alg here 25.04 2012 (houqp) - -- @TODO replace greedy algorithm with K&P algorithm 25.04 2012 (houqp) - return self:_wrapGreedyAlg(h_list) -end - -function TextBoxWidget:_render() - self.v_list = self:_getVerticalList() - local v_list = self.v_list - local font_height = self.face.size - local line_height_px = self.line_height * font_height - local space_w = sizeUtf8Text(0, Screen:getWidth(), self.face, " ", true).x - local h = (font_height + line_height_px) * #v_list - line_height_px - self._bb = Blitbuffer.new(self.width, h) - local y = font_height - local pen_x = 0 - for _,l in ipairs(v_list) do - pen_x = 0 - for _,w in ipairs(l) do - --@TODO Don't use kerning for monospaced fonts. (houqp) - -- refert to cb25029dddc42693cc7aaefbe47e9bd3b7e1a750 in master tree - 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 - end - -- if text is shorter than one line, shrink to text's width - if #v_list == 1 then - self.width = pen_x - end -end - -function TextBoxWidget:getSize() - if not self._bb then - self:_render() - end - return { w = self.width, h = self._bb:getHeight() } -end - -function TextBoxWidget:paintTo(bb, x, y) - if not self._bb then - self:_render() - end - bb:blitFrom(self._bb, x, y, 0, 0, self.width, self._bb:getHeight()) -end - -function TextBoxWidget:free() - if self._bb then - self._bb:free() - self._bb = nil - end -end - ---[[ -ImageWidget shows an image from a file -]] -ImageWidget = Widget:new{ - invert = nil, - file = nil, - _bb = nil -} - -function ImageWidget:_render() - local itype = string.lower(string.match(self.file, ".+%.([^.]+)") or "") - if itype == "jpeg" or itype == "jpg" then - self._bb = Image.fromJPEG(self.file) - elseif itype == "png" then - self._bb = Image.fromPNG(self.file) - end -end - -function ImageWidget:getSize() - if not self._bb then - self:_render() - end - return Geom:new{ w = self._bb:getWidth(), h = self._bb:getHeight() } -end - -function ImageWidget:paintTo(bb, x, y) - local size = self:getSize() - bb:blitFrom(self._bb, x, y, 0, 0, size.w, size.h) - if self.invert then - bb:invertRect(x, y, size.w, size.h) - end -end - -function ImageWidget:free() - if self._bb then - self._bb:free() - self._bb = nil - end -end - ---[[ -ProgressWidget shows a progress bar -]] -ProgressWidget = Widget:new{ - width = nil, - height = nil, - margin_h = 3, - margin_v = 1, - radius = 2, - bordersize = 1, - bordercolor = 15, - bgcolor = 0, - rectcolor = 10, - percentage = nil, -} - -function ProgressWidget:getSize() - return { w = self.width, h = self.height } -end - -function ProgressWidget:paintTo(bb, x, y) - local my_size = self:getSize() - bb:paintRoundedRect(x, y, my_size.w, my_size.h, self.bgcolor, self.radius) - bb:paintBorder(x, y, my_size.w, my_size.h, self.bordersize, self.bordercolor, self.radius) - bb:paintRect(x+self.margin_h, y+self.margin_v+self.bordersize, - (my_size.w-2*self.margin_h)*self.percentage, (my_size.h-2*(self.margin_v+self.bordersize)), self.rectcolor) -end - -function ProgressWidget:setPercentage(percentage) - self.percentage = percentage -end - ---[[ -A Layout widget that puts objects besides each others -]] -HorizontalGroup = WidgetContainer:new{ - align = "center", - _size = nil, -} - -function HorizontalGroup:getSize() - if not self._size then - self._size = { w = 0, h = 0 } - self._offsets = { } - for i, widget in ipairs(self) do - local w_size = widget:getSize() - self._offsets[i] = { - x = self._size.w, - y = w_size.h - } - self._size.w = self._size.w + w_size.w - if w_size.h > self._size.h then - self._size.h = w_size.h - end - end - end - return self._size -end - -function HorizontalGroup:paintTo(bb, x, y) - local size = self:getSize() - - for i, widget in ipairs(self) do - if self.align == "center" then - widget:paintTo(bb, x + self._offsets[i].x, y + (size.h - self._offsets[i].y) / 2) - elseif self.align == "top" then - widget:paintTo(bb, x + self._offsets[i].x, y) - elseif self.align == "bottom" then - widget:paintTo(bb, x + self._offsets[i].x, y + size.h - self._offsets[i].y) - end - end -end - -function HorizontalGroup:clear() - self:free() - WidgetContainer.clear(self) -end - -function HorizontalGroup:resetLayout() - self._size = nil - self._offsets = {} -end - -function HorizontalGroup:free() - self:resetLayout() - WidgetContainer.free(self) -end - ---[[ -Dummy Widget that reserves horizontal space -]] -HorizontalSpan = Widget:new{ - width = 0, -} - -function HorizontalSpan:getSize() - return {w = self.width, h = 0} -end - ---[[ -A Layout widget that puts objects under each other -]] -VerticalGroup = WidgetContainer:new{ - align = "center", - _size = nil, - _offsets = {} -} - -function VerticalGroup:getSize() - if not self._size then - self._size = { w = 0, h = 0 } - self._offsets = { } - for i, widget in ipairs(self) do - local w_size = widget:getSize() - self._offsets[i] = { - x = w_size.w, - y = self._size.h, - } - self._size.h = self._size.h + w_size.h - if w_size.w > self._size.w then - self._size.w = w_size.w - end - end - end - return self._size -end - -function VerticalGroup:paintTo(bb, x, y) - local size = self:getSize() - - for i, widget in ipairs(self) do - if self.align == "center" then - widget:paintTo(bb, x + (size.w - self._offsets[i].x) / 2, y + self._offsets[i].y) - elseif self.align == "left" then - widget:paintTo(bb, x, y + self._offsets[i].y) - elseif self.align == "right" then - widget:paintTo(bb, x + size.w - self._offsets[i].x, y + self._offsets[i].y) - end - end -end - -function VerticalGroup:clear() - self:free() - WidgetContainer.clear(self) -end - -function VerticalGroup:resetLayout() - self._size = nil - self._offsets = {} -end - -function VerticalGroup:free() - self:resetLayout() - WidgetContainer.free(self) -end - - ---[[ -A Layout widget that puts objects above each other -]] -OverlapGroup = WidgetContainer:new{ - _size = nil, -} - -function OverlapGroup:getSize() - if not self._size then - self._size = {w = 0, h = 0} - self._offsets = { x = math.huge, y = math.huge } - for i, widget in ipairs(self) do - local w_size = widget:getSize() - if self._size.h < w_size.h then - self._size.h = w_size.h - end - if self._size.w < w_size.w then - self._size.w = w_size.w - end - end - end - - if self.dimen.w then - self._size.w = self.dimen.w - end - if self.dimen.h then - self._size.h = self.dimen.h - end - - return self._size -end - -function OverlapGroup:paintTo(bb, x, y) - local size = self:getSize() - - for i, wget in ipairs(self) do - local wget_size = wget:getSize() - if wget.align == "right" then - wget:paintTo(bb, x+size.w-wget_size.w, y) - elseif wget.align == "center" then - wget:paintTo(bb, x+(size.w-wget_size.w)/2, y) - else - -- default to left - wget:paintTo(bb, x, y) - end - end -end - - ---[[ -Dummy Widget that reserves vertical space -]] -VerticalSpan = Widget:new{ - width = 0, -} - -function VerticalSpan:getSize() - return {w = 0, h = self.width} -end - ---[[ -an UnderlineContainer is a WidgetContainer that is able to paint -a line under its child node -]] - -UnderlineContainer = WidgetContainer:new{ - linesize = 2, - padding = 1, - color = 0, -} - -function UnderlineContainer:getSize() - if self.dimen then - return { w = self.dimen.w, h = self.dimen.h } - else - local contentSize = self[1]:getSize() - return { - w = contentSize.w, - h = contentSize.h + self.linesize + self.padding - } - end -end - -function UnderlineContainer:paintTo(bb, x, y) - local content_size = self:getSize() - self[1]:paintTo(bb, x, y) - bb:paintRect(x, y + content_size.h - self.linesize, - content_size.w, self.linesize, self.color) -end - - ---[[ -an InputContainer is an WidgetContainer that handles input events - -an example for a key_event is this: - - PanBy20 = { { "Shift", Input.group.Cursor }, seqtext = "Shift+Cursor", doc = "pan by 20px", event = "Pan", args = 20, is_inactive = true }, - PanNormal = { { Input.group.Cursor }, seqtext = "Cursor", doc = "pan by 10 px", event = "Pan", args = 10 }, - Quit = { {"Home"} }, - -it is suggested to reference configurable sequences from another table -and store that table as configuration setting -]] -InputContainer = WidgetContainer:new{} - -function InputContainer:_init() - -- we need to do deep copy here - local new_key_events = {} - if self.key_events then - for k,v in pairs(self.key_events) do - new_key_events[k] = v - end - end - self.key_events = new_key_events - - local new_ges_events = {} - if self.ges_events then - for k,v in pairs(self.ges_events) do - new_ges_events[k] = v - end - end - self.ges_events = new_ges_events - - if not self.dimen then - self.dimen = Geom:new{} - end -end - -function InputContainer:paintTo(bb, x, y) - self.dimen.x = x - self.dimen.y = y - if self[1] then - return self[1]:paintTo(bb, x, y) - end -end - --- the following handler handles keypresses and checks --- if they lead to a command. --- if this is the case, we retransmit another event within --- ourselves -function InputContainer:onKeyPress(key) - for name, seq in pairs(self.key_events) do - if not seq.is_inactive then - for _, oneseq in ipairs(seq) do - if key:match(oneseq) then - local eventname = seq.event or name - return self:handleEvent(Event:new(eventname, seq.args, key)) - end - end - end - end -end - -function InputContainer:onGesture(ev) - for name, gsseq in pairs(self.ges_events) do - for _, gs_range in ipairs(gsseq) do - --DEBUG("gs_range", gs_range) - if gs_range:match(ev) then - local eventname = gsseq.event or name - return self:handleEvent(Event:new(eventname, gsseq.args, ev)) - end - end - end -end - diff --git a/frontend/ui/widget/base.lua b/frontend/ui/widget/base.lua new file mode 100644 index 000000000..a73687d98 --- /dev/null +++ b/frontend/ui/widget/base.lua @@ -0,0 +1,68 @@ +require "ui/screen" +require "ui/rendertext" +require "ui/graphics" +require "ui/image" +require "ui/event" +require "ui/inputevent" +require "ui/gesturedetector" +require "ui/font" + +--[[ +The EventListener is an interface that handles events + +EventListeners have a rudimentary event handler/dispatcher that +will call a method "onEventName" for an event with name +"EventName" +--]] +EventListener = {} + +function EventListener:new(o) + local o = o or {} + setmetatable(o, self) + self.__index = self + if o.init then o:init() end + return o +end + +function EventListener:handleEvent(event) + if self[event.handler] then + return self[event.handler](self, unpack(event.args)) + end +end + + +--[[ +This is a generic Widget interface + +widgets can be queried about their size and can be paint. +that's it for now. Probably we need something more elaborate +later. + +if the table that was given to us as parameter has an "init" +method, it will be called. use this to set _instance_ variables +rather than class variables. +--]] +Widget = EventListener:new() + +function Widget:new(o) + local o = o or {} + setmetatable(o, self) + self.__index = self + -- Both o._init and o.init are called on object create. But o._init is used + -- for base widget initialization (basic component used to build other + -- widgets). While o.init is for higher level widgets, for example Menu + -- Widget + if o._init then o:_init() end + if o.init then o:init() end + return o +end + +function Widget:getSize() + return self.dimen +end + +function Widget:paintTo(bb, x, y) +end + + + diff --git a/frontend/ui/bbox.lua b/frontend/ui/widget/bbox.lua similarity index 99% rename from frontend/ui/bbox.lua rename to frontend/ui/widget/bbox.lua index 561a49121..af3ee01fc 100644 --- a/frontend/ui/bbox.lua +++ b/frontend/ui/widget/bbox.lua @@ -1,4 +1,5 @@ require "math" +require "ui/widget/container" --[[ BBoxWidget shows a bbox for page cropping diff --git a/frontend/ui/button.lua b/frontend/ui/widget/button.lua similarity index 93% rename from frontend/ui/button.lua rename to frontend/ui/widget/button.lua index 034dff3a5..767e50f79 100644 --- a/frontend/ui/button.lua +++ b/frontend/ui/widget/button.lua @@ -1,8 +1,8 @@ -require "ui/widget" +require "ui/widget/container" --[[ a button widget -]] +--]] Button = InputContainer:new{ text = nil, -- mandatory preselect = false, @@ -23,6 +23,9 @@ function Button:init() face = Font:getFace(self.text_font_face, self.text_font_size) } local text_size = text_widget:getSize() + if self.width == nil then + self.width = text_size.w + end -- set FrameContainer content self[1] = FrameContainer:new{ margin = self.margin, diff --git a/frontend/ui/config.lua b/frontend/ui/widget/config.lua similarity index 93% rename from frontend/ui/config.lua rename to frontend/ui/widget/config.lua index 473d44b62..a04934d9a 100644 --- a/frontend/ui/config.lua +++ b/frontend/ui/widget/config.lua @@ -1,26 +1,5 @@ -require "ui/widget" -require "ui/focusmanager" -require "ui/infomessage" -require "ui/font" -require "ui/toggleswitch" - -FixedTextWidget = TextWidget:new{} -function FixedTextWidget:getSize() - local tsize = sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true) - if not tsize then - return Geom:new{} - end - self._length = tsize.x - self._height = self.face.size - return Geom:new{ - w = self._length, - h = self._height, - } -end - -function FixedTextWidget:paintTo(bb, x, y) - renderUtf8Text(bb, x, y+self._height, self.face, self.text, true) -end +require "ui/widget/container" +require "ui/widget/toggleswitch" MenuBarItem = InputContainer:new{} function MenuBarItem:init() @@ -180,7 +159,9 @@ function ConfigOption:init() local default_option_height = 50 local default_option_padding = 15 local vertical_group = VerticalGroup:new{} - table.insert(vertical_group, VerticalSpan:new{ width = default_option_padding * Screen:getDPI()/167 }) + table.insert(vertical_group, VerticalSpan:new{ + width = scaleByDPI(default_option_padding), + }) for c = 1, #self.options do if self.options[c].show ~= false then local name_align = self.options[c].name_align_right and self.options[c].name_align_right or 0.33 @@ -189,9 +170,9 @@ function ConfigOption:init() local name_font_size = self.options[c].name_font_size and self.options[c].name_font_size or default_name_font_size local item_font_face = self.options[c].item_font_face and self.options[c].item_font_face or "cfont" local item_font_size = self.options[c].item_font_size and self.options[c].item_font_size or default_item_font_size - local option_height = (self.options[c].height and self.options[c].height or default_option_height) * Screen:getDPI()/167 - local items_spacing = HorizontalSpan:new{ - width = (self.options[c].spacing and self.options[c].spacing or default_items_spacing) * Screen:getDPI()/167 + local option_height = scaleByDPI(self.options[c].height and self.options[c].height or default_option_height) + local items_spacing = HorizontalSpan:new{ + width = scaleByDPI(self.options[c].spacing and self.options[c].spacing or default_items_spacing) } local horizontal_group = HorizontalGroup:new{} if self.options[c].name_text then @@ -205,7 +186,7 @@ function ConfigOption:init() table.insert(option_name_container, option_name) table.insert(horizontal_group, option_name_container) end - + if self.options[c].widget == "ProgressWidget" then local widget_container = CenterContainer:new{ dimen = Geom:new{w = Screen:getWidth()*self.options[c].widget_align_center, h = option_height} @@ -218,7 +199,7 @@ function ConfigOption:init() table.insert(widget_container, widget) table.insert(horizontal_group, widget_container) end - + local option_items_container = CenterContainer:new{ dimen = Geom:new{w = Screen:getWidth()*item_align, h = option_height} } @@ -261,7 +242,7 @@ function ConfigOption:init() end end end - + if self.options[c].item_text then for d = 1, #self.options[c].item_text do local option_item = nil @@ -298,7 +279,7 @@ function ConfigOption:init() end end end - + if self.options[c].item_icons then for d = 1, #self.options[c].item_icons do local option_item = OptionIconItem:new{ @@ -322,7 +303,7 @@ function ConfigOption:init() end end end - + if self.options[c].toggle then local switch = ToggleSwitch:new{ name = self.options[c].name, @@ -338,7 +319,7 @@ function ConfigOption:init() switch:setPosition(position) table.insert(option_items_group, switch) end - + table.insert(option_items_container, option_items_group) table.insert(horizontal_group, option_items_container) table.insert(vertical_group, horizontal_group) @@ -352,7 +333,7 @@ end ConfigPanel = FrameContainer:new{ background = 0, bordersize = 0, } function ConfigPanel:init() local config_options = self.config_dialog.config_options - local default_option = config_options.default_options and config_options.default_options + local default_option = config_options.default_options and config_options.default_options or config_options[1].options local panel = ConfigOption:new{ options = self.index and config_options[self.index].options or default_option, @@ -375,26 +356,26 @@ function MenuBar:init() local icon_dimen = menu_icon:getSize() icons_width = icons_width + icon_dimen.w icons_height = icon_dimen.h > icons_height and icon_dimen.h or icons_height - + menu_items[c] = MenuBarItem:new{ menu_icon, index = c, config = self.config_dialog, } end - + local spacing = HorizontalSpan:new{ width = (Screen:getWidth() - icons_width) / (#menu_items+1) } - + local menu_bar = HorizontalGroup:new{} - + for c = 1, #menu_items do table.insert(menu_bar, spacing) table.insert(menu_bar, menu_items[c]) end table.insert(menu_bar, spacing) - + self.dimen = Geom:new{ w = Screen:getWidth(), h = icons_height} table.insert(self, menu_bar) end @@ -415,7 +396,7 @@ Widget that displays config menubar and config panel +----------------+ | Menu Bar | +----------------+ - + --]] ConfigDialog = InputContainer:new{ @@ -429,7 +410,7 @@ function ConfigDialog:init() self.config_panel = ConfigPanel:new{ config_dialog = self, } - self.config_menubar = MenuBar:new{ + self.config_menubar = MenuBar:new{ config_dialog = self, } self:makeDialog() @@ -454,7 +435,7 @@ function ConfigDialog:init() self.key_events.FocusRight = nil end self.key_events.Select = { {"Press"}, doc = "select current menu item"} - + UIManager:setDirty(self, "partial") end @@ -470,9 +451,9 @@ function ConfigDialog:makeDialog() self.config_panel, self.config_menubar, } - + local dialog_size = dialog:getSize() - + self[1] = BottomContainer:new{ dimen = Screen:getSize(), FrameContainer:new{ @@ -481,13 +462,13 @@ function ConfigDialog:makeDialog() dialog, } } - + self.dialog_dimen = Geom:new{ x = (Screen:getWidth() - dialog_size.w)/2, y = Screen:getHeight() - dialog_size.h, w = dialog_size.w, h = dialog_size.h, - } + } end function ConfigDialog:onShowConfigPanel(index) diff --git a/frontend/ui/confirmbox.lua b/frontend/ui/widget/confirmbox.lua similarity index 83% rename from frontend/ui/confirmbox.lua rename to frontend/ui/widget/confirmbox.lua index 5bc40e29c..c7bee880e 100644 --- a/frontend/ui/confirmbox.lua +++ b/frontend/ui/widget/confirmbox.lua @@ -1,6 +1,6 @@ -require "ui/widget" -require "ui/focusmanager" -require "ui/button" +require "ui/widget/container" +require "ui/widget/focusmanager" +require "ui/widget/button" --[[ Widget that shows a message and OK/Cancel buttons @@ -25,14 +25,22 @@ function ConfirmBox:init() local ok_button = Button:new{ text = self.ok_text, + callback = function() + self.ok_callback() + UIManager:close(self) + end, } local cancel_button = Button:new{ text = self.cancel_text, - preselect = true + preselect = true, + callback = function() + self.cancel_callback() + UIManager:close(self) + end, } self.layout = { { ok_button, cancel_button } } - self.selected.x = 2 -- Cancel is default + self.selected.x = 2 -- Cancel is default self[1] = CenterContainer:new{ dimen = Screen:getSize(), diff --git a/frontend/ui/widget/container.lua b/frontend/ui/widget/container.lua new file mode 100644 index 000000000..636acda73 --- /dev/null +++ b/frontend/ui/widget/container.lua @@ -0,0 +1,317 @@ +require "ui/widget/base" + +--[[ +WidgetContainer is a container for another Widget +--]] +WidgetContainer = Widget:new() + +function WidgetContainer:getSize() + if self.dimen then + -- fixed size + return self.dimen + elseif self[1] then + -- return size of first child widget + return self[1]:getSize() + else + return Geom:new{ w = 0, h = 0 } + end +end + +--[[ +delete all child widgets +--]] +function WidgetContainer:clear() + while table.remove(self) do end +end + +function WidgetContainer:paintTo(bb, x, y) + -- default to pass request to first child widget + if self[1] then + return self[1]:paintTo(bb, x, y) + end +end + +function WidgetContainer:propagateEvent(event) + -- propagate to children + for _, widget in ipairs(self) do + if widget:handleEvent(event) then + -- stop propagating when an event handler returns true + return true + end + end + return false +end + +--[[ +Containers will pass events to children or react on them themselves +--]] +function WidgetContainer:handleEvent(event) + if not self:propagateEvent(event) then + -- call our own standard event handler + return Widget.handleEvent(self, event) + else + return true + end +end + +function WidgetContainer:free() + for _, widget in ipairs(self) do + if widget.free then widget:free() end + end +end + +--[[ +BottomContainer contains its content (1 widget) at the bottom of its own +dimensions +--]] +BottomContainer = WidgetContainer:new() + +function BottomContainer:paintTo(bb, x, y) + local contentSize = self[1]:getSize() + if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then + -- throw error? paint to scrap buffer and blit partially? + -- for now, we ignore this + end + self[1]:paintTo(bb, + x + (self.dimen.w - contentSize.w)/2, + y + (self.dimen.h - contentSize.h)) +end + +--[[ +CenterContainer centers its content (1 widget) within its own dimensions +--]] +CenterContainer = WidgetContainer:new() + +function CenterContainer:paintTo(bb, x, y) + local contentSize = self[1]:getSize() + if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then + -- throw error? paint to scrap buffer and blit partially? + -- for now, we ignore this + end + local x_pos = x + local y_pos = y + if self.ignore ~= "height" then + y_pos = y + (self.dimen.h - contentSize.h)/2 + end + if self.ignore ~= "width" then + x_pos = x + (self.dimen.w - contentSize.w)/2 + end + self[1]:paintTo(bb, x_pos, y_pos) +end + +--[[ +LeftContainer aligns its content (1 widget) at the left of its own dimensions +--]] +LeftContainer = WidgetContainer:new() + +function LeftContainer:paintTo(bb, x, y) + local contentSize = self[1]:getSize() + if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then + -- throw error? paint to scrap buffer and blit partially? + -- for now, we ignore this + end + self[1]:paintTo(bb, x , y + (self.dimen.h - contentSize.h)/2) +end + +--[[ +RightContainer aligns its content (1 widget) at the right of its own dimensions +--]] +RightContainer = WidgetContainer:new() + +function RightContainer:paintTo(bb, x, y) + local contentSize = self[1]:getSize() + if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then + -- throw error? paint to scrap buffer and blit partially? + -- for now, we ignore this + end + self[1]:paintTo(bb, + x + (self.dimen.w - contentSize.w), + y + (self.dimen.h - contentSize.h)/2) +end + +--[[ +A FrameContainer is some graphics content (1 widget) that is surrounded by a +frame +--]] +FrameContainer = WidgetContainer:new{ + background = nil, + color = 15, + margin = 0, + radius = 0, + bordersize = 2, + padding = 5, + width = nil, + height = nil, + invert = false, +} + +function FrameContainer:getSize() + local content_size = self[1]:getSize() + return Geom:new{ + w = content_size.w + ( self.margin + self.bordersize + self.padding ) * 2, + h = content_size.h + ( self.margin + self.bordersize + self.padding ) * 2 + } +end + +function FrameContainer:paintTo(bb, x, y) + local my_size = self:getSize() + local container_width = self.width or my_size.w + local container_height = self.height or my_size.h + + --@TODO get rid of margin here? 13.03 2013 (houqp) + if self.background then + bb:paintRoundedRect(x, y, container_width, container_height, + self.background, self.radius) + end + if self.bordersize > 0 then + bb:paintBorder(x + self.margin, y + self.margin, + container_width - self.margin * 2, + container_height - self.margin * 2, + self.bordersize, self.color, self.radius) + end + if self[1] then + self[1]:paintTo(bb, + x + self.margin + self.bordersize + self.padding, + y + self.margin + self.bordersize + self.padding) + end + if self.invert then + bb:invertRect(x, y, container_width, container_height) + end +end + + +--[[ +an UnderlineContainer is a WidgetContainer that is able to paint +a line under its child node +--]] + +UnderlineContainer = WidgetContainer:new{ + linesize = 2, + padding = 1, + color = 0, + vertical_align = "top", +} + +function UnderlineContainer:getSize() + if self.dimen then + return { w = self.dimen.w, h = self.dimen.h } + else + return self:getContentSize() + end +end + +function UnderlineContainer:getContentSize() + local contentSize = self[1]:getSize() + return { + w = contentSize.w, + h = contentSize.h + self.linesize + self.padding + } +end + +function UnderlineContainer:paintTo(bb, x, y) + local container_size = self:getSize() + local content_size = self:getContentSize() + local p_y = y + if self.vertical_align == "center" then + p_y = (container_size.h - content_size.h) / 2 + y + elseif self.vertical_align == "bottom" then + p_y = (container_size.h - content_size.h) + y + end + self[1]:paintTo(bb, x, p_y) + bb:paintRect(x, y + container_size.h - self.linesize, + container_size.w, self.linesize, self.color) +end + + + +--[[ +an InputContainer is an WidgetContainer that handles input events + +an example for a key_event is this: + + PanBy20 = { + { "Shift", Input.group.Cursor }, + seqtext = "Shift+Cursor", + doc = "pan by 20px", + event = "Pan", args = 20, is_inactive = true, + }, + PanNormal = { + { Input.group.Cursor }, + seqtext = "Cursor", + doc = "pan by 10 px", event = "Pan", args = 10, + }, + Quit = { {"Home"} }, + +it is suggested to reference configurable sequences from another table +and store that table as configuration setting +--]] +InputContainer = WidgetContainer:new{ + vertical_align = "top", +} + +function InputContainer:_init() + -- we need to do deep copy here + local new_key_events = {} + if self.key_events then + for k,v in pairs(self.key_events) do + new_key_events[k] = v + end + end + self.key_events = new_key_events + + local new_ges_events = {} + if self.ges_events then + for k,v in pairs(self.ges_events) do + new_ges_events[k] = v + end + end + self.ges_events = new_ges_events + + if not self.dimen then + self.dimen = Geom:new{} + end +end + +function InputContainer:paintTo(bb, x, y) + self.dimen.x = x + self.dimen.y = y + if self[1] then + if self.vertical_align == "center" then + local content_size = self[1]:getSize() + self[1]:paintTo(bb, x, y + (self.dimen.h - content_size.h)/2) + else + self[1]:paintTo(bb, x, y) + end + end +end + +--[[ +the following handler handles keypresses and checks if they lead to a command. +if this is the case, we retransmit another event within ourselves +--]] +function InputContainer:onKeyPress(key) + for name, seq in pairs(self.key_events) do + if not seq.is_inactive then + for _, oneseq in ipairs(seq) do + if key:match(oneseq) then + local eventname = seq.event or name + return self:handleEvent(Event:new(eventname, seq.args, key)) + end + end + end + end +end + +function InputContainer:onGesture(ev) + for name, gsseq in pairs(self.ges_events) do + for _, gs_range in ipairs(gsseq) do + --DEBUG("gs_range", gs_range) + if gs_range:match(ev) then + local eventname = gsseq.event or name + return self:handleEvent(Event:new(eventname, gsseq.args, ev)) + end + end + end +end + + diff --git a/frontend/ui/filechooser.lua b/frontend/ui/widget/filechooser.lua similarity index 98% rename from frontend/ui/filechooser.lua rename to frontend/ui/widget/filechooser.lua index 2dbd5ca99..589d69b92 100644 --- a/frontend/ui/filechooser.lua +++ b/frontend/ui/widget/filechooser.lua @@ -1,4 +1,4 @@ -require "ui/menu" +require "ui/widget/menu" FileChooser = Menu:new{ height = Screen:getHeight(), diff --git a/frontend/ui/focusmanager.lua b/frontend/ui/widget/focusmanager.lua similarity index 100% rename from frontend/ui/focusmanager.lua rename to frontend/ui/widget/focusmanager.lua diff --git a/frontend/ui/widget/group.lua b/frontend/ui/widget/group.lua new file mode 100644 index 000000000..27d06ca19 --- /dev/null +++ b/frontend/ui/widget/group.lua @@ -0,0 +1,168 @@ +require "ui/widget/container" + + +--[[ +A Layout widget that puts objects besides each others +--]] +HorizontalGroup = WidgetContainer:new{ + align = "center", + _size = nil, +} + +function HorizontalGroup:getSize() + if not self._size then + self._size = { w = 0, h = 0 } + self._offsets = { } + for i, widget in ipairs(self) do + local w_size = widget:getSize() + self._offsets[i] = { + x = self._size.w, + y = w_size.h + } + self._size.w = self._size.w + w_size.w + if w_size.h > self._size.h then + self._size.h = w_size.h + end + end + end + return self._size +end + +function HorizontalGroup:paintTo(bb, x, y) + local size = self:getSize() + + for i, widget in ipairs(self) do + if self.align == "center" then + widget:paintTo(bb, + x + self._offsets[i].x, + y + (size.h - self._offsets[i].y) / 2) + elseif self.align == "top" then + widget:paintTo(bb, x + self._offsets[i].x, y) + elseif self.align == "bottom" then + widget:paintTo(bb, x + self._offsets[i].x, y + size.h - self._offsets[i].y) + end + end +end + +function HorizontalGroup:clear() + self:free() + WidgetContainer.clear(self) +end + +function HorizontalGroup:resetLayout() + self._size = nil + self._offsets = {} +end + +function HorizontalGroup:free() + self:resetLayout() + WidgetContainer.free(self) +end + + +--[[ +A Layout widget that puts objects under each other +--]] +VerticalGroup = WidgetContainer:new{ + align = "center", + _size = nil, + _offsets = {} +} + +function VerticalGroup:getSize() + if not self._size then + self._size = { w = 0, h = 0 } + self._offsets = { } + for i, widget in ipairs(self) do + local w_size = widget:getSize() + self._offsets[i] = { + x = w_size.w, + y = self._size.h, + } + self._size.h = self._size.h + w_size.h + if w_size.w > self._size.w then + self._size.w = w_size.w + end + end + end + return self._size +end + +function VerticalGroup:paintTo(bb, x, y) + local size = self:getSize() + + for i, widget in ipairs(self) do + if self.align == "center" then + widget:paintTo(bb, x + (size.w - self._offsets[i].x) / 2, y + self._offsets[i].y) + elseif self.align == "left" then + widget:paintTo(bb, x, y + self._offsets[i].y) + elseif self.align == "right" then + widget:paintTo(bb, x + size.w - self._offsets[i].x, y + self._offsets[i].y) + end + end +end + +function VerticalGroup:clear() + self:free() + WidgetContainer.clear(self) +end + +function VerticalGroup:resetLayout() + self._size = nil + self._offsets = {} +end + +function VerticalGroup:free() + self:resetLayout() + WidgetContainer.free(self) +end + + +--[[ +A Layout widget that puts objects above each other +--]] +OverlapGroup = WidgetContainer:new{ + _size = nil, +} + +function OverlapGroup:getSize() + if not self._size then + self._size = {w = 0, h = 0} + self._offsets = { x = math.huge, y = math.huge } + for i, widget in ipairs(self) do + local w_size = widget:getSize() + if self._size.h < w_size.h then + self._size.h = w_size.h + end + if self._size.w < w_size.w then + self._size.w = w_size.w + end + end + end + + if self.dimen.w then + self._size.w = self.dimen.w + end + if self.dimen.h then + self._size.h = self.dimen.h + end + + return self._size +end + +function OverlapGroup:paintTo(bb, x, y) + local size = self:getSize() + + for i, wget in ipairs(self) do + local wget_size = wget:getSize() + if wget.align == "right" then + wget:paintTo(bb, x+size.w-wget_size.w, y) + elseif wget.align == "center" then + wget:paintTo(bb, x+(size.w-wget_size.w)/2, y) + else + -- default to left + wget:paintTo(bb, x, y) + end + end +end + diff --git a/frontend/ui/widget/iconbutton.lua b/frontend/ui/widget/iconbutton.lua new file mode 100644 index 000000000..a80b1ab00 --- /dev/null +++ b/frontend/ui/widget/iconbutton.lua @@ -0,0 +1,55 @@ +require "ui/widget/container" +require "ui/widget/image" + + +--[[ +Button with a big icon image! Designed for touch device +--]] +IconButton = InputContainer:new{ + icon_file = "resources/info-confirm.png", + dimen = nil, + -- show_parent is used for UIManager:setDirty, so we can trigger repaint + show_parent = nil, + callback = function() end, +} + +function IconButton:init() + self.image = ImageWidget:new{ + file = self.icon_file + } + + self.show_parent = self.show_parent or self + self.dimen = self.image:getSize() + + self:initGesListener() + + self[1] = self.image +end + +function IconButton:initGesListener() + self.ges_events = { + TapClickButton = { + GestureRange:new{ + ges = "tap", + range = self.dimen, + } + }, + } +end + +function IconButton:onTapClickButton() + self.image.invert = true + UIManager:setDirty(self.show_parent, "partial") + -- make sure button reacts before doing callback + UIManager:scheduleIn(0.1, function() + self.callback() + self.image.invert = false + UIManager:setDirty(self.show_parent, "partial") + end) + return true +end + +function IconButton:onSetDimensions(new_dimen) + self.dimen = new_dimen +end + diff --git a/frontend/ui/widget/image.lua b/frontend/ui/widget/image.lua new file mode 100644 index 000000000..bf008cab8 --- /dev/null +++ b/frontend/ui/widget/image.lua @@ -0,0 +1,45 @@ +require "ui/widget/base" +require "ui/image" + + +--[[ +ImageWidget shows an image from a file +--]] +ImageWidget = Widget:new{ + invert = nil, + file = nil, + _bb = nil +} + +function ImageWidget:_render() + local itype = string.lower(string.match(self.file, ".+%.([^.]+)") or "") + if itype == "jpeg" or itype == "jpg" then + self._bb = Image.fromJPEG(self.file) + elseif itype == "png" then + self._bb = Image.fromPNG(self.file) + end +end + +function ImageWidget:getSize() + if not self._bb then + self:_render() + end + return Geom:new{ w = self._bb:getWidth(), h = self._bb:getHeight() } +end + +function ImageWidget:paintTo(bb, x, y) + local size = self:getSize() + bb:blitFrom(self._bb, x, y, 0, 0, size.w, size.h) + if self.invert then + bb:invertRect(x, y, size.w, size.h) + end +end + +function ImageWidget:free() + if self._bb then + self._bb:free() + self._bb = nil + end +end + + diff --git a/frontend/ui/infomessage.lua b/frontend/ui/widget/infomessage.lua similarity index 97% rename from frontend/ui/infomessage.lua rename to frontend/ui/widget/infomessage.lua index 7c9eb408c..9cdbc1c57 100644 --- a/frontend/ui/infomessage.lua +++ b/frontend/ui/widget/infomessage.lua @@ -1,5 +1,4 @@ -require "ui/ui" -require "ui/widget" +require "ui/widget/container" --[[ Widget that displays an informational message diff --git a/frontend/ui/widget/line.lua b/frontend/ui/widget/line.lua new file mode 100644 index 000000000..eba16976f --- /dev/null +++ b/frontend/ui/widget/line.lua @@ -0,0 +1,31 @@ +require "ui/widget/base" + +LineWidget = Widget:new{ + style = "solid", + background = 15, + dimen = nil, + --@TODO replay dirty hack here 13.03 2013 (houqp) + empty_segments = nil, +} + +function LineWidget:paintTo(bb, x, y) + if self.style == "dashed" then + for i = 0, self.dimen.w - 20, 20 do + bb:paintRect(x + i, y, + 16, self.dimen.h, self.background) + end + else + if self.empty_segments then + bb:paintRect(x, y, + self.empty_segments[1].s, + self.dimen.h, + self.background) + bb:paintRect(x + self.empty_segments[1].e, y, + self.dimen.w - x - self.empty_segments[1].e, + self.dimen.h, + self.background) + else + bb:paintRect(x, y, self.dimen.w, self.dimen.h, self.background) + end + end +end diff --git a/frontend/ui/menu.lua b/frontend/ui/widget/menu.lua similarity index 96% rename from frontend/ui/menu.lua rename to frontend/ui/widget/menu.lua index becf9a51a..c37691607 100644 --- a/frontend/ui/menu.lua +++ b/frontend/ui/widget/menu.lua @@ -1,11 +1,14 @@ -require "ui/widget" -require "ui/focusmanager" -require "ui/infomessage" +require "ui/widget/container" +require "ui/widget/focusmanager" +require "ui/widget/infomessage" +require "ui/widget/text" +require "ui/widget/group" +require "ui/widget/span" require "ui/font" --[[ Widget that displays a shortcut icon for menu item -]] +--]] ItemShortCutIcon = WidgetContainer:new{ dimen = Geom:new{ w = 22, h = 22 }, key = nil, @@ -100,7 +103,7 @@ function MenuItem:init() local shortcut_icon_dimen = Geom:new() if self.shortcut then shortcut_icon_dimen.w = math.floor(self.dimen.h*4/5) - shortcut_icon_dimen.h = shortcut_icon_dimen.w + shortcut_icon_dimen.h = shortcut_icon_dimen.w end self.detail = self.text @@ -129,7 +132,7 @@ function MenuItem:init() if Device:isTouchDevice() then else self.active_key_events.ShowItemDetail = { - {"Right"}, doc = "show item detail" + {"Right"}, doc = "show item detail" } end indicator = " >>" @@ -346,7 +349,7 @@ function Menu:init() if self.is_enable_shortcut then self.key_events.SelectByShortCut = { {self.item_shortcuts} } end - self.key_events.Select = { + self.key_events.Select = { {"Press"}, doc = "select current menu item" } end @@ -368,7 +371,7 @@ function Menu:updateItems(select_number) for c = 1, self.perpage do -- calculate index in item_table - local i = (self.page - 1) * self.perpage + c + local i = (self.page - 1) * self.perpage + c if i <= #self.item_table then local item_shortcut = nil local shortcut_style = "square" @@ -398,7 +401,7 @@ function Menu:updateItems(select_number) table.insert(self.layout, {item_tmp}) else -- item not enough to fill the whole page, break out of loop - table.insert(self.item_group, + table.insert(self.item_group, VerticalSpan:new{ width = (self.item_dimen.h * (self.perpage - c + 1)) }) @@ -438,7 +441,7 @@ function Menu:onSelectByShortCut(_, keyevent) if self.item_table[(self.page-1)*self.perpage + k] then self:onMenuSelect(self.item_table[(self.page-1)*self.perpage + k]) end - break + break end end return true @@ -468,7 +471,7 @@ override this function to process the item selected in a different manner ]]-- function Menu:onMenuSelect(item) if item.sub_item_table == nil then - self.close_callback() + self.close_callback() self:onMenuChoice(item) else -- save menu title for later resume diff --git a/frontend/ui/notification.lua b/frontend/ui/widget/notification.lua similarity index 96% rename from frontend/ui/notification.lua rename to frontend/ui/widget/notification.lua index 7ae326bbc..2930fe304 100644 --- a/frontend/ui/notification.lua +++ b/frontend/ui/widget/notification.lua @@ -1,5 +1,4 @@ -require "ui/ui" -require "ui/widget" +require "ui/widget/container" --[[ Widget that displays a tiny notification on top of screen diff --git a/frontend/ui/widget/progress.lua b/frontend/ui/widget/progress.lua new file mode 100644 index 000000000..3952e223e --- /dev/null +++ b/frontend/ui/widget/progress.lua @@ -0,0 +1,39 @@ +require "ui/widget/base" + + +--[[ +ProgressWidget shows a progress bar +--]] +ProgressWidget = Widget:new{ + width = nil, + height = nil, + margin_h = 3, + margin_v = 1, + radius = 2, + bordersize = 1, + bordercolor = 15, + bgcolor = 0, + rectcolor = 10, + percentage = nil, +} + +function ProgressWidget:getSize() + return { w = self.width, h = self.height } +end + +function ProgressWidget:paintTo(bb, x, y) + local my_size = self:getSize() + bb:paintRoundedRect(x, y, my_size.w, my_size.h, self.bgcolor, self.radius) + bb:paintBorder(x, y, my_size.w, my_size.h, + self.bordersize, self.bordercolor, self.radius) + bb:paintRect(x+self.margin_h, y+self.margin_v+self.bordersize, + (my_size.w-2*self.margin_h)*self.percentage, + (my_size.h-2*(self.margin_v+self.bordersize)), self.rectcolor) +end + +function ProgressWidget:setPercentage(percentage) + self.percentage = percentage +end + + + diff --git a/frontend/ui/widget/span.lua b/frontend/ui/widget/span.lua new file mode 100644 index 000000000..9782ce6c1 --- /dev/null +++ b/frontend/ui/widget/span.lua @@ -0,0 +1,27 @@ +require "ui/widget/base" + + +--[[ +Dummy Widget that reserves horizontal space +--]] +HorizontalSpan = Widget:new{ + width = 0, +} + +function HorizontalSpan:getSize() + return {w = self.width, h = 0} +end + + +--[[ +Dummy Widget that reserves vertical space +--]] +VerticalSpan = Widget:new{ + width = 0, +} + +function VerticalSpan:getSize() + return {w = 0, h = self.width} +end + + diff --git a/frontend/ui/widget/text.lua b/frontend/ui/widget/text.lua new file mode 100644 index 000000000..a0d4b3512 --- /dev/null +++ b/frontend/ui/widget/text.lua @@ -0,0 +1,180 @@ +require "ui/widget/base" +require "ui/rendertext" + + +--[[ +A TextWidget puts a string on a single line +--]] +TextWidget = Widget:new{ + text = nil, + face = nil, + color = 15, + _bb = nil, + _length = 0, + _height = 0, + _maxlength = 1200, +} + +--function TextWidget:_render() + --local h = self.face.size * 1.3 + --self._bb = Blitbuffer.new(self._maxlength, h) + --self._length = renderUtf8Text(self._bb, 0, h*0.8, self.face, self.text, self.color) +--end + +function TextWidget:getSize() + --if not self._bb then + --self:_render() + --end + --return { w = self._length, h = self._bb:getHeight() } + local tsize = sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true) + if not tsize then + return Geom:new{} + end + self._length = tsize.x + self._height = self.face.size * 1.5 + return Geom:new{ + w = self._length, + h = self._height, + } +end + +function TextWidget:paintTo(bb, x, y) + --if not self._bb then + --self:_render() + --end + --bb:blitFrom(self._bb, x, y, 0, 0, self._length, self._bb:getHeight()) + --@TODO Don't use kerning for monospaced fonts. (houqp) + renderUtf8Text(bb, x, y+self._height*0.7, self.face, self.text, true) +end + +function TextWidget:free() + if self._bb then + self._bb:free() + self._bb = nil + end +end + +--[[ +A TextWidget that handles long text wrapping +--]] +TextBoxWidget = Widget:new{ + text = nil, + face = nil, + color = 15, + width = 400, -- in pixels + line_height = 0.3, -- in em + v_list = nil, + _bb = nil, + _length = 0, +} + +function TextBoxWidget:_wrapGreedyAlg(h_list) + local cur_line_width = 0 + local space_w = sizeUtf8Text(0, Screen:getWidth(), self.face, " ", true).x + local cur_line = {} + local v_list = {} + + for k,w in ipairs(h_list) do + cur_line_width = cur_line_width + w.width + if cur_line_width <= self.width then + cur_line_width = cur_line_width + space_w + table.insert(cur_line, w) + else + -- wrap to next line + table.insert(v_list, cur_line) + cur_line = {} + cur_line_width = w.width + space_w + table.insert(cur_line, w) + end + end + -- handle last line + table.insert(v_list, cur_line) + + return v_list +end + +function TextBoxWidget:_getVerticalList(alg) + -- build horizontal list + h_list = {} + for w in self.text:gmatch("%S+") do + word_box = {} + word_box.word = w + word_box.width = sizeUtf8Text(0, Screen:getWidth(), self.face, w, true).x + table.insert(h_list, word_box) + end + + -- @TODO check alg here 25.04 2012 (houqp) + -- @TODO replace greedy algorithm with K&P algorithm 25.04 2012 (houqp) + return self:_wrapGreedyAlg(h_list) +end + +function TextBoxWidget:_render() + self.v_list = self:_getVerticalList() + local v_list = self.v_list + local font_height = self.face.size + local line_height_px = self.line_height * font_height + local space_w = sizeUtf8Text(0, Screen:getWidth(), self.face, " ", true).x + local h = (font_height + line_height_px) * #v_list - line_height_px + self._bb = Blitbuffer.new(self.width, h) + local y = font_height + local pen_x = 0 + for _,l in ipairs(v_list) do + pen_x = 0 + for _,w in ipairs(l) do + --@TODO Don't use kerning for monospaced fonts. (houqp) + -- refert to cb25029dddc42693cc7aaefbe47e9bd3b7e1a750 in master tree + 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 + end + -- if text is shorter than one line, shrink to text's width + if #v_list == 1 then + self.width = pen_x + end +end + +function TextBoxWidget:getSize() + if not self._bb then + self:_render() + end + return { w = self.width, h = self._bb:getHeight() } +end + +function TextBoxWidget:paintTo(bb, x, y) + if not self._bb then + self:_render() + end + bb:blitFrom(self._bb, x, y, 0, 0, self.width, self._bb:getHeight()) +end + +function TextBoxWidget:free() + if self._bb then + self._bb:free() + self._bb = nil + end +end + + +--[[ +FixedTextWidget +--]] +FixedTextWidget = TextWidget:new{} +function FixedTextWidget:getSize() + local tsize = sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true) + if not tsize then + return Geom:new{} + end + self._length = tsize.x + self._height = self.face.size + return Geom:new{ + w = self._length, + h = self._height, + } +end + +function FixedTextWidget:paintTo(bb, x, y) + renderUtf8Text(bb, x, y+self._height, self.face, self.text, true) +end + + diff --git a/frontend/ui/toggleswitch.lua b/frontend/ui/widget/toggleswitch.lua similarity index 100% rename from frontend/ui/toggleswitch.lua rename to frontend/ui/widget/toggleswitch.lua diff --git a/frontend/ui/widget/touchmenu.lua b/frontend/ui/widget/touchmenu.lua new file mode 100644 index 000000000..292488969 --- /dev/null +++ b/frontend/ui/widget/touchmenu.lua @@ -0,0 +1,389 @@ +require "ui/widget/container" +require "ui/widget/group" +require "ui/widget/line" +require "ui/widget/iconbutton" + + +--[[ +TouchMenuItem widget +--]] +TouchMenuItem = InputContainer:new{ + menu = nil, + vertical_align = "center", + item = nil, + dimen = nil, + face = Font:getFace("cfont", 22), + show_parent = nil, +} + +function TouchMenuItem:init() + self.ges_events = { + TapSelect = { + GestureRange:new{ + ges = "tap", + range = self.dimen, + }, + doc = "Select Menu Item", + }, + } + + self.item_frame = FrameContainer:new{ + width = self.dimen.w, + bordersize = 0, + color = 15, + HorizontalGroup:new { + align = "center", + HorizontalSpan:new{ width = 10 }, + TextWidget:new{ + text = self.item.text, + face = self.face, + }, + }, + } + self[1] = self.item_frame +end + +function TouchMenuItem:onTapSelect(arg, ges) + self.item_frame.invert = true + UIManager:setDirty(self.show_parent, "partial") + UIManager:scheduleIn(0.5, function() + self.item_frame.invert = false + UIManager:setDirty(self.show_parent, "partial") + end) + self.menu:onMenuSelect(self.item) + return true +end + + +--[[ +TouchMenuBar widget +--]] +TouchMenuBar = InputContainer:new{ + height = scaleByDPI(70), + width = Screen:getWidth(), + icons = {}, + -- touch menu that holds the bar, used for trigger repaint on icons + show_parent = nil, + menu = nil, +} + +function TouchMenuBar:init() + self.show_parent = self.show_parent or self + + self.dimen = Geom:new{ + w = self.width, + h = self.height, + } + + self.bar_icon_group = HorizontalGroup:new{} + + local icon_sep = LineWidget:new{ + dimen = Geom:new{ + w = scaleByDPI(2), + h = self.height, + } + } + + local icon_span = HorizontalSpan:new{ width = scaleByDPI(20) } + + -- build up image widget for menu icon bar + self.icon_widgets = {} + -- the start_seg for first icon_widget should be 0 + -- we asign negative here to offset it in the loop + start_seg = -icon_sep:getSize().w + end_seg = start_seg + for k, v in ipairs(self.icons) do + local ib = IconButton:new{ + show_parent = self.show_parent, + icon_file = v, + callback = nil, + } + + table.insert(self.icon_widgets, HorizontalGroup:new{ + icon_span, + ib, + icon_span, + }) + + -- we have to use local variable here for closure callback + local _start_seg = end_seg + icon_sep:getSize().w + local _end_seg = _start_seg + self.icon_widgets[k]:getSize().w + + if k == 1 then + self.bar_sep = LineWidget:new{ + dimen = Geom:new{ + w = self.width, + h = scaleByDPI(2), + }, + empty_segments = { + { + s = _start_seg, e = _end_seg + } + }, + } + end + + ib.callback = function() + self.bar_sep.empty_segments = { + { + s = _start_seg, e = _end_seg + } + } + self.menu:switchMenuTab(k) + end + + table.insert(self.bar_icon_group, self.icon_widgets[k]) + table.insert(self.bar_icon_group, icon_sep) + + start_seg = _start_seg + end_seg = _end_seg + end + + self[1] = FrameContainer:new{ + bordersize = 0, + padding = 0, + VerticalGroup:new{ + align = "left", + -- bar icons + self.bar_icon_group, + -- separate line + self.bar_sep + }, + } +end + + +--[[ +TouchMenu widget +--]] +TouchMenu = InputContainer:new{ + tab_item_table = {}, + -- for returnning in multi-level menus + item_table_stack = nil, + item_table = nil, + item_height = scaleByDPI(50), + bordersize = scaleByDPI(2), + padding = scaleByDPI(5), + width = Screen:getWidth(), + height = nil, + page = 1, + max_per_page = 10, + -- for UIManager:setDirty + show_parent = nil, + cur_tab = -1, + close_callback = nil, +} + +function TouchMenu:init() + self.show_parent = self.show_parent or self + if not self.close_callback then + self.close_callback = function() + UIManager:close(self.show_parent) + end + end + + self.ges_events.TapCloseAllMenus = { + GestureRange:new{ + ges = "tap", + range = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight(), + } + } + } + self.ges_events.Swipe = { + GestureRange:new{ + ges = "swipe", + range = self.dimen, + } + } + + local icons = {} + for _,v in ipairs(self.tab_item_table) do + table.insert(icons, v.icon) + end + self.bar = TouchMenuBar:new{ + width = self.width - self.padding * 2 - self.bordersize * 2, + icons = icons, + show_parent = self.show_parent, + menu = self, + } + + self.item_group = VerticalGroup:new{ + align = "left", + } + + self.footer_page = TextWidget:new{ + face = Font:getFace("ffont", 20), + text = "", + } + self.footer = HorizontalGroup:new{ + IconButton:new{ + invert = true, + icon_file = "resources/icons/appbar.chevron.up.png", + show_parent = self.show_parent, + callback = function() + self:backToUpperMenu() + end, + }, + self.footer_page, + } + + self[1] = FrameContainer:new{ + padding = self.padding, + bordersize = self.bordersize, + background = 0, + -- menubar and footer will be inserted in + -- item_group in updateItems + self.item_group, + } + + self:switchMenuTab(1) + self:updateItems() +end + +function TouchMenu:_recalculateDimen() + self.dimen.w = self.width + -- if height not given, dynamically calculate it + if not self.height then + self.dimen.h = (#self.item_table + 2) * self.item_height + + self.bar:getSize().h + else + self.dimen.h = self.height + end + if self.dimen.h > Screen:getHeight() then + self.dimen.h = Screen:getHeight() + end + self.perpage = math.floor(self.dimen.h / self.item_height) - 2 + if self.perpage > self.max_per_page then + self.perpage = self.max_per_page + end + self.page_num = math.ceil(#self.item_table / self.perpage) +end + +function TouchMenu:updateItems() + self:_recalculateDimen() + self.item_group:clear() + table.insert(self.item_group, self.bar) + + local item_width = self.dimen.w - self.padding*2 - self.bordersize*2 + + for c = 1, self.perpage do + -- calculate index in item_table + local i = (self.page - 1) * self.perpage + c + if i <= #self.item_table then + local item_tmp = TouchMenuItem:new{ + item = self.item_table[i], + menu = self, + dimen = Geom:new{ + w = item_width, + h = self.item_height, + }, + show_parent = self.show_parent, + } + table.insert(self.item_group, item_tmp) + -- insert split line + if c ~= self.perpage then + table.insert(self.item_group, HorizontalGroup:new{ + -- pad with spacing + HorizontalSpan:new{width = scaleByDPI(10)}, + LineWidget:new{ + style = "dashed", + dimen = Geom:new{ + w = item_width - 20, + h = 1, + } + } + }) + end + else + -- item not enough to fill the whole page, break out of loop + --table.insert(self.item_group, + --VerticalSpan:new{ + --width = self.item_height + --}) + --break + end -- if i <= self.items + end -- for c=1, self.perpage + + table.insert(self.item_group, VerticalSpan:new{width = scaleByDPI(2)}) + table.insert(self.item_group, self.footer) + self.footer_page.text = "Page "..self.page.."/"..self.page_num + -- FIXME: this is a dirty hack to clear previous menus + -- refert to issue #664 + UIManager.repaint_all = true +end + +function TouchMenu:switchMenuTab(tab_num) + if self.cur_tab ~= tab_num then + -- it's like getting a new menu everytime we switch tab! + self.page = 1 + -- clear item table stack + self.item_table_stack = {} + self.cur_tab = tab_num + self.item_table = self.tab_item_table[tab_num] + self:updateItems() + end +end + +function TouchMenu:backToUpperMenu() + if #self.item_table_stack ~= 0 then + self.item_table = table.remove(self.item_table_stack) + self:updateItems() + end +end + +function TouchMenu:closeMenu() + self.close_callback() +end + +function TouchMenu:onNextPage() + if self.page < self.page_num then + self.page = self.page + 1 + self:updateItems() + end + return true +end + +function TouchMenu:onPrevPage() + if self.page > 1 then + self.page = self.page - 1 + self:updateItems() + end + return true +end + +function TouchMenu:onSwipe(arg, ges_ev) + if ges_ev.direction == "left" then + self:onNextPage() + elseif ges_ev.direction == "right" then + self:onPrevPage() + end +end + +function TouchMenu:onMenuSelect(item) + if item.sub_item_table == nil then + if item.callback then + -- put stuff in scheduler so we can See + -- the effect of inverted menu item + UIManager:scheduleIn(0.1, function() + self:closeMenu() + item.callback() + end) + end + else + table.insert(self.item_table_stack, self.item_table) + self.item_table = item.sub_item_table + self:updateItems() + end + return true +end + +function TouchMenu:onTapCloseAllMenus(arg, ges_ev) + if ges_ev.pos:notIntersectWith(self.dimen) then + self:closeMenu() + return true + end +end + diff --git a/kpdf.sh b/kpdf.sh index 6dbea2786..e4ecfc075 100755 --- a/kpdf.sh +++ b/kpdf.sh @@ -20,6 +20,9 @@ if test "$1" == "--framework_stop"; then /etc/init.d/framework stop fi +# dismiss chrome bar +lipc-set-prop com.lab126.pillow disableEnablePillow disable + # stop cvm #killall -stop cvm @@ -33,3 +36,7 @@ fi # always try to continue cvm killall -cont cvm || /etc/init.d/framework start + +# display chrome bar +lipc-set-prop com.lab126.pillow disableEnablePillow enable + diff --git a/reader.lua b/reader.lua index e93839a4a..8d622db74 100755 --- a/reader.lua +++ b/reader.lua @@ -1,17 +1,20 @@ #!./kpdfview package.path = "./frontend/?.lua" -require "ui/ui" +require "ui/uimanager" +require "ui/widget/filechooser" +require "ui/widget/infomessage" require "ui/readerui" -require "ui/filechooser" -require "ui/infomessage" -require "ui/button" require "document/document" - +require "settings" +require "dbg" HomeMenu = InputContainer:new{ item_table = {}, + key_events = { + TapShowMenu = { {"Home"}, doc = "Show Home Menu"}, + }, ges_events = { TapShowMenu = { GestureRange:new{ @@ -26,19 +29,57 @@ HomeMenu = InputContainer:new{ }, } +function exitReader() + G_reader_settings:close() + + input.closeAll() + + if util.isEmulated() ==0 then + if Device:isKindle3() or (Device:getModel() == "KindleDXG") then + -- send double menu key press events to trigger screen refresh + os.execute("echo 'send 139' > /proc/keypad;echo 'send 139' > /proc/keypad") + end + end +end + function HomeMenu:setUpdateItemTable() + function readHistDir(order_arg, re) + local pipe_out = io.popen("ls "..order_arg.." -1 ./history") + for f in pipe_out:lines() do + table.insert(re, { + dir = DocSettings:getPathFromHistory(f), + name = DocSettings:getNameFromHistory(f), + }) + end + end + + local hist_sub_item_table = {} + local last_files = {} + readHistDir("-c", last_files) + for _,v in pairs(last_files) do + table.insert(hist_sub_item_table, { + text = v.name, + callback = function() + showReader(v.dir .. "/" .. v.name) + end + }) + end + table.insert(self.item_table, { + text = "Last documents", + sub_item_table = hist_sub_item_table, + }) + table.insert(self.item_table, { text = "Exit", callback = function() - os.exit(0) + exitReader() end }) end function HomeMenu:onTapShowMenu() - if #self.item_table == 0 then - self:setUpdateItemTable() - end + self.item_table = {} + self:setUpdateItemTable() local home_menu = Menu:new{ title = "Home menu", @@ -51,7 +92,7 @@ function HomeMenu:onTapShowMenu() dimen = Screen:getSize(), home_menu, } - home_menu.close_callback = function () + home_menu.close_callback = function () UIManager:close(menu_container) end @@ -68,6 +109,7 @@ function showReader(file, pass) return end + G_reader_settings:saveSetting("lastfile", file) local reader = ReaderUI:new{ dialog = readerwindow, dimen = Screen:getSize(), @@ -92,7 +134,7 @@ function showHomePage(path) end return true end, - file_filter = function(filename) + file_filter = function(filename) if DocumentRegistry:getProvider(filename) then return true end @@ -148,11 +190,8 @@ end local argidx = 1 if ARGV[1] == "-d" then - argidx = argidx + 1 - G_debug_mode = true - os.execute("echo > ev.log") - -- create ev log file - G_ev_log = io.open("ev.log", "w") + Dbg:turnOn() + argidx = argidx + 1 else DEBUG = function() end end @@ -175,7 +214,7 @@ local last_file = G_reader_settings:readSetting("lastfile") --87712cf0e43fed624f8a9f610be42b1fe174b9fe -if ARGV[argidx] then +if ARGV[argidx] and ARGV[argidx] ~= "" then if lfs.attributes(ARGV[argidx], "mode") == "directory" then showHomePage(ARGV[argidx]) elseif lfs.attributes(ARGV[argidx], "mode") == "file" then @@ -189,11 +228,4 @@ else return showusage() end -input.closeAll() - -if util.isEmulated()==0 then - if Device:isKindle3() or (Device:getModel() == "KindleDXG") then - -- send double menu key press events to trigger screen refresh - os.execute("echo 'send 139' > /proc/keypad;echo 'send 139' > /proc/keypad") - end -end +exitReader() diff --git a/resources/icons/appbar.chevron.left.png b/resources/icons/appbar.chevron.left.png index 7c96681b0..d479e024e 100644 Binary files a/resources/icons/appbar.chevron.left.png and b/resources/icons/appbar.chevron.left.png differ diff --git a/resources/icons/appbar.chevron.up.png b/resources/icons/appbar.chevron.up.png new file mode 100644 index 000000000..75d0ff262 Binary files /dev/null and b/resources/icons/appbar.chevron.up.png differ diff --git a/resources/icons/appbar.page.corner.bookmark.png b/resources/icons/appbar.page.corner.bookmark.png new file mode 100644 index 000000000..d14a1220b Binary files /dev/null and b/resources/icons/appbar.page.corner.bookmark.png differ diff --git a/resources/icons/appbar.page.text.png b/resources/icons/appbar.page.text.png new file mode 100644 index 000000000..f59925713 Binary files /dev/null and b/resources/icons/appbar.page.text.png differ diff --git a/resources/icons/appbar.pokeball.png b/resources/icons/appbar.pokeball.png new file mode 100644 index 000000000..afe39f562 Binary files /dev/null and b/resources/icons/appbar.pokeball.png differ diff --git a/resources/icons/src/appbar.chevron.left.svg b/resources/icons/src/appbar.chevron.left.svg new file mode 100644 index 000000000..fd888a110 --- /dev/null +++ b/resources/icons/src/appbar.chevron.left.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/icons/src/appbar.chevron.up.svg b/resources/icons/src/appbar.chevron.up.svg new file mode 100644 index 000000000..ddfbf8418 --- /dev/null +++ b/resources/icons/src/appbar.chevron.up.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/icons/src/appbar.page.corner.bookmark.svg b/resources/icons/src/appbar.page.corner.bookmark.svg new file mode 100644 index 000000000..9df9fdc66 --- /dev/null +++ b/resources/icons/src/appbar.page.corner.bookmark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/icons/src/appbar.page.text.svg b/resources/icons/src/appbar.page.text.svg new file mode 100644 index 000000000..088a9eb05 --- /dev/null +++ b/resources/icons/src/appbar.page.text.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/icons/src/appbar.pokeball.svg b/resources/icons/src/appbar.pokeball.svg new file mode 100644 index 000000000..758588b08 --- /dev/null +++ b/resources/icons/src/appbar.pokeball.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/wtest.lua b/wtest.lua index caeefaf2a..534bb66b6 100644 --- a/wtest.lua +++ b/wtest.lua @@ -1,13 +1,18 @@ print(package.path) package.path = "./frontend/?.lua" -require "ui/widget" -require "ui/ui" -require "ui/readerui" -require "ui/menu" -require "ui/infomessage" -require "ui/confirmbox" +require "ui/uimanager" +require "ui/widget/menu" +require "ui/widget/infomessage" +require "ui/widget/confirmbox" +require "ui/widget/touchmenu" require "document/document" +require "ui/readerui" +require "dbg" + +----------------------------------------------------- +-- widget that paints the grid on the background +----------------------------------------------------- TestGrid = Widget:new{} function TestGrid:paintTo(bb) @@ -25,7 +30,9 @@ function TestGrid:paintTo(bb) end end +----------------------------------------------------- -- we create a widget that paints a background: +----------------------------------------------------- Background = InputContainer:new{ is_always_active = true, -- receive events when other dialogs are active key_events = { @@ -37,7 +44,13 @@ Background = InputContainer:new{ FrameContainer:new{ background = 3, bordersize = 0, - dimen = Screen:getSize() + dimen = Screen:getSize(), + Widget:new{ + dimen = { + w = Screen:getWidth(), + h = Screen:getHeight(), + } + }, } } @@ -64,7 +77,9 @@ end +----------------------------------------------------- -- example widget: a clock +----------------------------------------------------- Clock = FrameContainer:new{ background = 0, bordersize = 1, @@ -96,6 +111,9 @@ function Clock:getTextWidget() } end +----------------------------------------------------- +-- a confirmbox box widget +----------------------------------------------------- Quiz = ConfirmBox:new{ text = "Tell me the truth, isn't it COOL?!", width = 300, @@ -108,6 +126,9 @@ Quiz = ConfirmBox:new{ end, } +----------------------------------------------------- +-- a menu widget +----------------------------------------------------- menu_items = { {text = "item1"}, {text = "item2"}, @@ -136,6 +157,9 @@ M = Menu:new{ } +----------------------------------------------------- +-- a reader view widget +----------------------------------------------------- readerwindow = CenterContainer:new{ dimen = Screen:getSize(), FrameContainer:new{ @@ -151,12 +175,86 @@ reader = ReaderUI:new{ } readerwindow[1][1] = reader + +touch_menu = TouchMenu:new{ + title = "Document menu", + tab_item_table = { + { + icon = "resources/icons/appbar.pokeball.png", + { + text = "item1", + callback = function() + end, + }, + { + text = "item2", + callback = function() + end, + }, + { + text = "item3", + callback = function() + end, + }, + { + text = "item4", + callback = function() + end, + }, + { + text = "item5", + callback = function() + end, + }, + { + text = "item6", + callback = function() + end, + }, + { + text = "item7", + callback = function() + end, + }, + { + text = "item8", + callback = function() + end, + }, + { + text = "item9", + callback = function() + end, + }, + }, + { + icon = "resources/icons/appbar.page.corner.bookmark.png", + { + text = "item10", + callback = function() + end, + }, + { + text = "item11", + callback = function() + end, + }, + } + }, +} + + + +----------------------------------------------------------------------- +-- you may want to uncomment following show calls to see the changes +----------------------------------------------------------------------- UIManager:show(Background:new()) UIManager:show(TestGrid) -UIManager:show(Clock:new()) -UIManager:show(M) -UIManager:show(Quiz) -UIManager:show(readerwindow) +--UIManager:show(Clock:new()) +--UIManager:show(M) +--UIManager:show(Quiz) +--UIManager:show(readerwindow) +UIManager:show(touch_menu) UIManager:run()