From f685f68852bcc3493c902edc6d3615295529504e Mon Sep 17 00:00:00 2001 From: HW Date: Sat, 19 May 2012 00:50:26 +0200 Subject: [PATCH] added reader widget implementation zooming, rotating, panning works already --- readerpaging.lua | 61 ++++++++++++++++++++++++++++++ readerpanning.lua | 31 ++++++++++++++++ readerrotation.lua | 16 ++++++++ readerui.lua | 80 ++++++++++++++++++++++++++++++++++++++++ readerview.lua | 92 ++++++++++++++++++++++++++++++++++++++++++++++ readerzooming.lua | 90 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 370 insertions(+) create mode 100644 readerpaging.lua create mode 100644 readerpanning.lua create mode 100644 readerrotation.lua create mode 100644 readerui.lua create mode 100644 readerview.lua create mode 100644 readerzooming.lua diff --git a/readerpaging.lua b/readerpaging.lua new file mode 100644 index 000000000..86523a2d2 --- /dev/null +++ b/readerpaging.lua @@ -0,0 +1,61 @@ +ReaderPaging = InputContainer:new{ + key_events = { + GotoNextPage = { {Input.group.PgFwd}, doc = "go to next page", event = "GotoPageRel", args = 1 }, + GotoPrevPage = { {Input.group.PgBack}, doc = "go to previous page", event = "GotoPageRel", args = -1 }, + + GotoFirst = { {"1"}, doc = "go to start", event = "GotoPercent", args = 0}, + Goto11 = { {"2"}, doc = "go to 11%", event = "GotoPercent", args = 11}, + Goto22 = { {"3"}, doc = "go to 22%", event = "GotoPercent", args = 22}, + Goto33 = { {"4"}, doc = "go to 33%", event = "GotoPercent", args = 33}, + Goto44 = { {"5"}, doc = "go to 44%", event = "GotoPercent", args = 44}, + Goto55 = { {"6"}, doc = "go to 55%", event = "GotoPercent", args = 55}, + Goto66 = { {"7"}, doc = "go to 66%", event = "GotoPercent", args = 66}, + Goto77 = { {"8"}, doc = "go to 77%", event = "GotoPercent", args = 77}, + Goto88 = { {"9"}, doc = "go to 88%", event = "GotoPercent", args = 88}, + GotoLast = { {"0"}, doc = "go to end", event = "GotoPercent", args = 100}, + }, + current_page = 0, + number_of_pages = 0 +} + +function ReaderPaging:init() + self.number_of_pages = self.ui.document.info.number_of_pages +end + +-- wrapper for bounds checking +function ReaderPaging:gotoPage(number) + if number == self.current_page then + return true + end + if number > self.number_of_pages + or number < 1 then + return false + end + debug("going to page number", number) + + -- this is an event to allow other controllers to be aware of this change + self.ui:handleEvent(Event:new("PageUpdate", number)) + + return true +end + +function ReaderPaging:onPageUpdate(new_page_no) + self.current_page = new_page_no +end + +function ReaderPaging:onGotoPercent(percent) + debug("goto document offset in percent:", percent) + local dest = math.floor(self.number_of_pages * percent / 100) + if dest < 1 then dest = 1 end + if dest > self.number_of_pages then + dest = self.number_of_pages + end + self:gotoPage(dest) + return true +end + +function ReaderPaging:onGotoPageRel(diff) + debug("goto relative page:", diff) + self:gotoPage(self.current_page + diff) + return true +end diff --git a/readerpanning.lua b/readerpanning.lua new file mode 100644 index 000000000..3bc09fc87 --- /dev/null +++ b/readerpanning.lua @@ -0,0 +1,31 @@ +ReaderPanning = InputContainer:new{ + key_events = { + -- these will all generate the same event, just with different arguments + MoveUp = { {"Up"}, doc = "move focus up", event = "Panning", args = {0, -1} }, + MoveDown = { {"Down"}, doc = "move focus down", event = "Panning", args = {0, 1} }, + MoveLeft = { {"Left"}, doc = "move focus left", event = "Panning", args = {-1, 0} }, + MoveRight = { {"Right"}, doc = "move focus right", event = "Panning", args = {1, 0} }, + }, + + -- defaults + panning_steps = { + normal = 50, + alt = 25, + shift = 10, + altshift = 5 + }, +} + +function ReaderPanning:onSetDimensions(dimensions) + self.dimen = dimensions +end + +function ReaderPanning:onPanning(args, key) + local dx, dy = unpack(args) + debug("key =", key) + -- for now, bounds checking/calculation is done in the view + self.view:PanningUpdate( + dx * self.panning_steps.normal * self.dimen.w / 100, + dy * self.panning_steps.normal * self.dimen.h / 100) + return true +end diff --git a/readerrotation.lua b/readerrotation.lua new file mode 100644 index 000000000..07660e0eb --- /dev/null +++ b/readerrotation.lua @@ -0,0 +1,16 @@ +ReaderRotation = InputContainer:new{ + key_events = { + -- these will all generate the same event, just with different arguments + RotateLeft = { {"J"}, doc = "rotate left by 90 degrees", event = "Rotate", args = -90 }, + RotateRight = { {"K"}, doc = "rotate right by 90 degrees", event = "Rotate", args = 90 }, + }, + current_rotation = 0 +} + +-- TODO: reset rotation on new document, maybe on new page? + +function ReaderRotation:onRotate(rotate_by) + self.current_rotation = (self.current_rotation + rotate_by) % 360 + self.ui:handleEvent(Event:new("RotationUpdate", self.current_rotation)) + return true +end diff --git a/readerui.lua b/readerui.lua new file mode 100644 index 000000000..85bb53336 --- /dev/null +++ b/readerui.lua @@ -0,0 +1,80 @@ +require "ui" +require "readerview" +require "readerzooming" +require "readerpanning" +require "readerrotation" +require "readerpaging" + +--[[ +This is an abstraction for a reader interface + +it works using data gathered from a document interface +]]-- + +ReaderUI = InputContainer:new{ + key_events = { + Close = { {"Home"}, doc = "close document", event = "Close" }, + Back = { {"Back"}, doc = "close document", event = "Close" }, + }, + + -- our own size + dimen = Geom:new{ w = 400, h = 600 }, + -- if we have a parent container, it must be referenced for now + dialog = nil, + + -- the document interface + document = nil, +} + +function ReaderUI:init() + -- if we are not the top level dialog ourselves, it must be given in the table + if not self.dialog then + self.dialog = self + end + -- a view container (so it must be child #1!) + self[1] = ReaderView:new{ + dialog = self.dialog, + ui = self + } + -- zooming controller + self[2] = ReaderZooming:new{ + dialog = self.dialog, + view = self[1], + ui = self + } + -- panning controller + self[3] = ReaderPanning:new{ + dialog = self.dialog, + view = self[1], + ui = self + } + -- rotation controller + self[4] = ReaderRotation:new{ + dialog = self.dialog, + view = self[1], + ui = self + } + -- if needed, insert a paging container + if self.document.info.has_pages then + local pager = ReaderPaging:new{ + dialog = self.dialog, + view = self[1], + ui = self + } + table.insert(self, pager) + pager:gotoPage(1) + end + -- notify childs of dimensions + self:handleEvent(Event:new("SetDimensions", self.dimen)) +end + +function ReaderUI:onClose() + debug("closing reader") + if self.document then + self.document:close() + self.document = false + end + UIManager:close(self.dialog) + return true +end + diff --git a/readerview.lua b/readerview.lua new file mode 100644 index 000000000..1ef5cde0a --- /dev/null +++ b/readerview.lua @@ -0,0 +1,92 @@ +require "ui" + +ReaderView = WidgetContainer:new{ + document = nil, + + state = { + page = 0, + zoom = 1.0, + rotation = 0, + offset = {}, + bbox = nil, + }, + + outer_page_color = 7, + + visible_area = Geom:new{x = 0, y = 0}, + page_area = Geom:new{}, +} + +function ReaderView:paintTo(bb, x, y) + debug("painting", self.visible_area, "to", x, y) + local inner_offset = Geom:new{x = 0, y = 0} + + -- draw surrounding space, if any + if self.ui.dimen.h > self.visible_area.h then + inner_offset.y = (self.ui.dimen.h - self.visible_area.h) / 2 + bb:paintRect(x, y, self.ui.dimen.w, inner_offset.y, self.outer_page_color) + bb:paintRect(x, y + self.ui.dimen.h - inner_offset.y - 1, self.ui.dimen.w, inner_offset.y + 1, self.outer_page_color) + end + if self.ui.dimen.w > self.visible_area.w then + inner_offset.x = (self.ui.dimen.w - self.visible_area.w) / 2 + bb:paintRect(x, y, inner_offset.x, self.ui.dimen.h, self.outer_page_color) + bb:paintRect(x + self.ui.dimen.w - inner_offset.x - 1, y, inner_offset.x + 1, self.ui.dimen.h, self.outer_page_color) + end + + -- draw content + self.ui.document:drawPage( + bb, + x + inner_offset.x, + y + inner_offset.y, + self.visible_area, + self.state.page, + self.state.zoom, + self.state.rotation) +end + +function ReaderView:recalculate() + local page_size = self.ui.document:getPageDimensions(self.state.page, self.state.zoom, self.state.rotation) + -- TODO: bbox + self.page_area = page_size + + -- reset our size + self.visible_area:setSizeTo(self.ui.dimen) + -- and recalculate it according to page size + self.visible_area:offsetWithin(self.page_area, 0, 0) + -- flag a repaint + UIManager:setDirty(self.dialog) +end + +function ReaderView:onSetDimensions(dimensions) + -- recalculate view + self:recalculate() +end + +function ReaderView:PanningUpdate(dx, dy) + debug("pan by", dx, dy) + local old = Geom:copy(self.visible_area) + self.visible_area:offsetWithin(self.page_area, dx, dy) + if self.visible_area ~= old then + -- flag a repaint + UIManager:setDirty(self.dialog) + debug(self.page_area) + debug(self.visible_area) + end + return true +end + +function ReaderView:onPageUpdate(new_page_no) + self.state.page = new_page_no + self:recalculate() +end + +function ReaderView:ZoomUpdate(zoom) + self.state.zoom = zoom + self:recalculate() +end + +function ReaderView:onRotationUpdate(rotation) + self.state.rotation = rotation + self:recalculate() +end + diff --git a/readerzooming.lua b/readerzooming.lua new file mode 100644 index 000000000..5297a0b9d --- /dev/null +++ b/readerzooming.lua @@ -0,0 +1,90 @@ +ReaderZooming = InputContainer:new{ + key_events = { + ZoomIn = { { "Shift", Input.group.PgFwd }, doc = "zoom in", event = "Zoom", args = "in" }, + ZoomOut = { { "Shift", Input.group.PgBack }, doc = "zoom out", event = "Zoom", args = "out" }, + ZoomToFitPage = { {"A"}, doc = "zoom to fit page", event = "SetZoomMode", args = "page" }, + ZoomToFitContent = { {"Shift", "A"}, doc = "zoom to fit content", event = "SetZoomMode", args = "content" }, + ZoomToFitPageWidth = { {"S"}, doc = "zoom to fit page width", event = "SetZoomMode", args = "pagewidth" }, + ZoomToFitContentWidth = { {"Shift", "S"}, doc = "zoom to fit content width", event = "SetZoomMode", args = "contentwidth" }, + ZoomToFitPageHeight = { {"D"}, doc = "zoom to fit page height", event = "SetZoomMode", args = "pageheight" }, + ZoomToFitContentHeight = { {"Shift", "D"}, doc = "zoom to fit content height", event = "SetZoomMode", args = "contentheight" }, + }, + zoom = 1.0, + zoom_mode = "free", + current_page = 1, + rotation = 0 +} + +function ReaderZooming:onSetDimensions(dimensions) + -- we were resized + self.dimen = dimensions +end + +function ReaderZooming:onRotationUpdate(rotation) + self.rotation = rotation + self:setZoom() +end + +function ReaderZooming:setZoom() + -- nothing to do in free zoom mode + if self.zoom_mode == "free" then + return + end + -- check if we're in bbox mode and work on bbox if that's the case + local page_size = {} + if self.zoom_mode == "content" or self.zoom_mode == "contentwidth" or self.zoom_mode == "content_height" then + -- TODO: enable this, still incomplete + page_size = self.ui.document:getUsedBBox(self.current_page) + self.view:handleEvent(Event:new("BBoxUpdate", page_size)) + else + -- otherwise, operate on full page + page_size = self.ui.document:getNativePageDimensions(self.current_page) + end + -- calculate zoom value: + local zoom_w = self.dimen.w / page_size.w + local zoom_h = self.dimen.h / page_size.h + if self.rotation % 180 ~= 0 then + -- rotated by 90 or 270 degrees + zoom_w = self.dimen.w / page_size.h + zoom_h = self.dimen.h / page_size.w + end + if self.zoom_mode == "content" or self.zoom_mode == "page" then + if zoom_w < zoom_h then + self.zoom = zoom_w + else + self.zoom = zoom_h + end + elseif self.zoom_mode == "contentwidth" or self.zoom_mode == "pagewidth" then + self.zoom = zoom_w + elseif self.zoom_mode == "contentheight" or self.zoom_mode == "pageheight" then + self.zoom = zoom_h + end + self.view:ZoomUpdate(self.zoom) +end + +function ReaderZooming:onZoom(direction) + debug("zoom", direction) + if direction == "in" then + self.zoom = self.zoom * 1.333333 + elseif direction == "out" then + self.zoom = self.zoom * 0.75 + end + debug("zoom is now at", self.zoom) + self:onSetZoomMode("free") + self.view:ZoomUpdate(self.zoom) + return true +end + +function ReaderZooming:onSetZoomMode(what) + if self.zoom_mode ~= what then + debug("setting zoom mode to", what) + self.zoom_mode = what + self:setZoom() + end + return true +end + +function ReaderZooming:onPageUpdate(new_page_no) + self.current_page = new_page_no + self:setZoom() +end