From 10d980ed87ebc34a0ac2eb5e271d6ea51a69070e Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 13 Mar 2013 01:18:53 +0800 Subject: [PATCH] rearranged source tree --- frontend/dbg.lua | 36 ++ frontend/settings.lua | 12 - frontend/ui/gesturedetector.lua | 12 +- frontend/ui/inputevent.lua | 22 +- frontend/ui/reader/readerbookmark.lua | 8 +- frontend/ui/reader/readerconfig.lua | 2 +- frontend/ui/reader/readercropping.lua | 4 +- frontend/ui/reader/readerdogear.lua | 3 +- frontend/ui/reader/readerfooter.lua | 1 + frontend/ui/readerui.lua | 1 - frontend/ui/{ui.lua => uimanager.lua} | 17 +- frontend/ui/widget.lua | 748 ---------------------- frontend/ui/widget/base.lua | 68 ++ frontend/ui/{ => widget}/bbox.lua | 1 + frontend/ui/{ => widget}/button.lua | 2 +- frontend/ui/{ => widget}/config.lua | 65 +- frontend/ui/{ => widget}/confirmbox.lua | 6 +- frontend/ui/widget/container.lua | 287 +++++++++ frontend/ui/{ => widget}/filechooser.lua | 2 +- frontend/ui/{ => widget}/focusmanager.lua | 0 frontend/ui/widget/group.lua | 166 +++++ frontend/ui/widget/image.lua | 45 ++ frontend/ui/{ => widget}/infomessage.lua | 3 +- frontend/ui/{ => widget}/menu.lua | 9 +- frontend/ui/{ => widget}/notification.lua | 3 +- frontend/ui/widget/progress.lua | 39 ++ frontend/ui/widget/span.lua | 27 + frontend/ui/widget/text.lua | 180 ++++++ frontend/ui/{ => widget}/toggleswitch.lua | 0 reader.lua | 21 +- wtest.lua | 11 +- 31 files changed, 931 insertions(+), 870 deletions(-) create mode 100644 frontend/dbg.lua rename frontend/ui/{ui.lua => uimanager.lua} (98%) delete mode 100644 frontend/ui/widget.lua create mode 100644 frontend/ui/widget/base.lua rename frontend/ui/{ => widget}/bbox.lua (99%) rename frontend/ui/{ => widget}/button.lua (97%) rename frontend/ui/{ => widget}/config.lua (95%) rename frontend/ui/{ => widget}/confirmbox.lua (95%) create mode 100644 frontend/ui/widget/container.lua rename frontend/ui/{ => widget}/filechooser.lua (98%) rename frontend/ui/{ => widget}/focusmanager.lua (100%) create mode 100644 frontend/ui/widget/group.lua create mode 100644 frontend/ui/widget/image.lua rename frontend/ui/{ => widget}/infomessage.lua (97%) rename frontend/ui/{ => widget}/menu.lua (98%) rename frontend/ui/{ => widget}/notification.lua (96%) create mode 100644 frontend/ui/widget/progress.lua create mode 100644 frontend/ui/widget/span.lua create mode 100644 frontend/ui/widget/text.lua rename frontend/ui/{ => widget}/toggleswitch.lua (100%) diff --git a/frontend/dbg.lua b/frontend/dbg.lua new file mode 100644 index 000000000..f8e970bfa --- /dev/null +++ b/frontend/dbg.lua @@ -0,0 +1,36 @@ +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(...) + local line = "" + for i,v in ipairs({...}) do + if type(v) == "table" then + line = line .. " " .. dump(v) + else + line = line .. " " .. tostring(v) + end + end + print("#"..line) +end + + + diff --git a/frontend/settings.lua b/frontend/settings.lua index 7a691dd9c..4c6c50141 100644 --- a/frontend/settings.lua +++ b/frontend/settings.lua @@ -70,18 +70,6 @@ function dump(data) 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 - end - print("#"..line) -end - -- simple serialization function, won't do uservalues, functions, loops function DocSettings:_serialize(what, outt, indent) if type(what) == "table" then diff --git a/frontend/ui/gesturedetector.lua b/frontend/ui/gesturedetector.lua index e52304848..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 { @@ -411,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 diff --git a/frontend/ui/inputevent.lua b/frontend/ui/inputevent.lua index dd19d738b..b7dfd1a3c 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 @@ -334,7 +333,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 +442,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 +450,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 +470,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 +489,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 +519,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..0942fcd65 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 @@ -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 f40e60222..97f2ff369 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 = {} diff --git a/frontend/ui/reader/readercropping.lua b/frontend/ui/reader/readercropping.lua index 396380257..514baaaaf 100644 --- a/frontend/ui/reader/readercropping.lua +++ b/frontend/ui/reader/readercropping.lua @@ -1,5 +1,5 @@ -require "ui/widget" -require "ui/bbox" +require "ui/widget/group" +require "ui/widget/bbox" PageCropDialog = VerticalGroup:new{ ok_text = "OK", 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/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/readerui.lua b/frontend/ui/readerui.lua index 3a6dd54a2..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" 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 6b4b4d452..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 97% rename from frontend/ui/button.lua rename to frontend/ui/widget/button.lua index 4f2cc4db6..622c78c74 100644 --- a/frontend/ui/button.lua +++ b/frontend/ui/widget/button.lua @@ -1,4 +1,4 @@ -require "ui/widget" +require "ui/widget/container" --[[ a button widget diff --git a/frontend/ui/config.lua b/frontend/ui/widget/config.lua similarity index 95% rename from frontend/ui/config.lua rename to frontend/ui/widget/config.lua index 473d44b62..c2378e3ed 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() @@ -190,7 +169,7 @@ function ConfigOption:init() 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{ + local items_spacing = HorizontalSpan:new{ width = (self.options[c].spacing and self.options[c].spacing or default_items_spacing) * Screen:getDPI()/167 } local horizontal_group = HorizontalGroup:new{} @@ -205,7 +184,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 +197,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 +240,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 +277,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 +301,7 @@ function ConfigOption:init() end end end - + if self.options[c].toggle then local switch = ToggleSwitch:new{ name = self.options[c].name, @@ -338,7 +317,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 +331,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 +354,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 +394,7 @@ Widget that displays config menubar and config panel +----------------+ | Menu Bar | +----------------+ - + --]] ConfigDialog = InputContainer:new{ @@ -429,7 +408,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 +433,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 +449,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 +460,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 95% rename from frontend/ui/confirmbox.lua rename to frontend/ui/widget/confirmbox.lua index 704160843..d0091893f 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 diff --git a/frontend/ui/widget/container.lua b/frontend/ui/widget/container.lua new file mode 100644 index 000000000..a54bebe1a --- /dev/null +++ b/frontend/ui/widget/container.lua @@ -0,0 +1,287 @@ +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, +} + +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 + + +--[[ +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/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..8850fd6b8 --- /dev/null +++ b/frontend/ui/widget/group.lua @@ -0,0 +1,166 @@ +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/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/menu.lua b/frontend/ui/widget/menu.lua similarity index 98% rename from frontend/ui/menu.lua rename to frontend/ui/widget/menu.lua index becf9a51a..f8dc0e354 100644 --- a/frontend/ui/menu.lua +++ b/frontend/ui/widget/menu.lua @@ -1,6 +1,9 @@ -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" --[[ 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/reader.lua b/reader.lua index b030058c8..c0cf3fefe 100755 --- a/reader.lua +++ b/reader.lua @@ -1,13 +1,13 @@ #!./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{ @@ -79,7 +79,7 @@ function HomeMenu:onTapShowMenu() dimen = Screen:getSize(), home_menu, } - home_menu.close_callback = function () + home_menu.close_callback = function () UIManager:close(menu_container) end @@ -120,7 +120,7 @@ function showHomePage(path) end return true end, - file_filter = function(filename) + file_filter = function(filename) if DocumentRegistry:getProvider(filename) then return true end @@ -176,11 +176,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 diff --git a/wtest.lua b/wtest.lua index e1b6a9a87..29018e8cc 100644 --- a/wtest.lua +++ b/wtest.lua @@ -1,12 +1,11 @@ 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 "document/document" +require "ui/readerui" -----------------------------------------------------