mirror of https://github.com/koreader/koreader
New ImageViewer widget + hold on image show it fullscreen
Simple image viewer with Best Fit/Original size and Rotate (for landscape images) buttons. readerhighlight: check if hold is on an image to show it fullscrrenpull/2498/head
parent
0577fde59e
commit
ee06171c83
@ -0,0 +1,310 @@
|
|||||||
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
||||||
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
||||||
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
||||||
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
||||||
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
||||||
|
local OverlapGroup = require("ui/widget/overlapgroup")
|
||||||
|
local CloseButton = require("ui/widget/closebutton")
|
||||||
|
local ButtonTable = require("ui/widget/buttontable")
|
||||||
|
local TextWidget = require("ui/widget/textwidget")
|
||||||
|
local LineWidget = require("ui/widget/linewidget")
|
||||||
|
local ImageWidget = require("ui/widget/imagewidget")
|
||||||
|
local GestureRange = require("ui/gesturerange")
|
||||||
|
local UIManager = require("ui/uimanager")
|
||||||
|
local Screen = require("device").screen
|
||||||
|
local Device = require("device")
|
||||||
|
local Geom = require("ui/geometry")
|
||||||
|
local Font = require("ui/font")
|
||||||
|
local logger = require("logger")
|
||||||
|
local _ = require("gettext")
|
||||||
|
local Blitbuffer = require("ffi/blitbuffer")
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Display image with some simple manipulation options
|
||||||
|
]]
|
||||||
|
local ImageViewer = InputContainer:new{
|
||||||
|
-- Allow for providing same different input types as ImageWidget :
|
||||||
|
-- a path to a file
|
||||||
|
file = nil,
|
||||||
|
-- or an already made BlitBuffer (ie: made by Mupdf.renderImageFile())
|
||||||
|
image = nil,
|
||||||
|
-- whether provided BlitBuffer should be free(), normally true
|
||||||
|
-- unless our caller wants to reuse it's provided image
|
||||||
|
image_disposable = true,
|
||||||
|
|
||||||
|
fullscreen = false, -- false will add some padding around widget (so footer can be visible)
|
||||||
|
with_title_bar = true,
|
||||||
|
title_text = _("Viewing image"), -- default title text
|
||||||
|
|
||||||
|
width = nil,
|
||||||
|
height = nil,
|
||||||
|
stretched = true, -- start with image stretched (Best fit)
|
||||||
|
rotated = false,
|
||||||
|
-- we use this global setting for rotation angle to have the same angle as reader
|
||||||
|
rotation_angle = DLANDSCAPE_CLOCKWISE_ROTATION and 90 or 270,
|
||||||
|
|
||||||
|
title_face = Font:getFace("tfont", 22),
|
||||||
|
title_padding = Screen:scaleBySize(5),
|
||||||
|
title_margin = Screen:scaleBySize(2),
|
||||||
|
image_padding = Screen:scaleBySize(2),
|
||||||
|
button_padding = Screen:scaleBySize(14),
|
||||||
|
|
||||||
|
-- Reference to current ImageWidget instance, for cleaning
|
||||||
|
_image_wg = nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
function ImageViewer:init()
|
||||||
|
if Device:hasKeys() then
|
||||||
|
self.key_events = {
|
||||||
|
Close = { {"Back"}, doc = "close viewer" }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if Device:isTouchDevice() then
|
||||||
|
self.ges_events = {
|
||||||
|
Tap = {
|
||||||
|
GestureRange:new{
|
||||||
|
ges = "tap",
|
||||||
|
range = Geom:new{
|
||||||
|
x = 0, y = 0,
|
||||||
|
w = Screen:getWidth(),
|
||||||
|
h = Screen:getHeight(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Swipe = {
|
||||||
|
GestureRange:new{
|
||||||
|
ges = "swipe",
|
||||||
|
range = Geom:new{
|
||||||
|
x = 0, y = 0,
|
||||||
|
w = Screen:getWidth(),
|
||||||
|
h = Screen:getHeight(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
self:update()
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageViewer:_clean_image_wg()
|
||||||
|
-- To be called before re-using / not needing self._image_wg
|
||||||
|
-- otherwise ressources used by its blitbuffer won't be freed
|
||||||
|
if self._image_wg then
|
||||||
|
logger.dbg("ImageViewer:_clean_image_wg()")
|
||||||
|
self._image_wg:free()
|
||||||
|
self._image_wg = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageViewer:update()
|
||||||
|
self:_clean_image_wg() -- clean previous if any
|
||||||
|
local orig_dimen = self.main_frame and self.main_frame.dimen or Geom:new{}
|
||||||
|
self.align = "center"
|
||||||
|
self.region = Geom:new{
|
||||||
|
x = 0, y = 0,
|
||||||
|
w = Screen:getWidth(),
|
||||||
|
h = Screen:getHeight(),
|
||||||
|
}
|
||||||
|
if self.fullscreen then
|
||||||
|
self.height = Screen:getHeight()
|
||||||
|
self.width = Screen:getWidth()
|
||||||
|
else
|
||||||
|
self.height = Screen:getHeight() - Screen:scaleBySize(40)
|
||||||
|
self.width = Screen:getWidth() - Screen:scaleBySize(40)
|
||||||
|
end
|
||||||
|
|
||||||
|
local buttons = {
|
||||||
|
{
|
||||||
|
{
|
||||||
|
text = self.stretched and _("Original size") or _("Best fit"),
|
||||||
|
callback = function()
|
||||||
|
self.stretched = not self.stretched and true or false
|
||||||
|
self:update()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = self.rotated and _("No rotation") or _("Rotate"),
|
||||||
|
callback = function()
|
||||||
|
self.rotated = not self.rotated and true or false
|
||||||
|
self:update()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Close"),
|
||||||
|
callback = function()
|
||||||
|
UIManager:close(self)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
local button_table = ButtonTable:new{
|
||||||
|
width = self.width,
|
||||||
|
button_font_face = "cfont",
|
||||||
|
button_font_size = 20,
|
||||||
|
buttons = buttons,
|
||||||
|
zero_sep = true,
|
||||||
|
show_parent = self,
|
||||||
|
}
|
||||||
|
local button_container = CenterContainer:new{
|
||||||
|
dimen = Geom:new{
|
||||||
|
w = self.width,
|
||||||
|
h = button_table:getSize().h,
|
||||||
|
},
|
||||||
|
button_table,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- height available to our image
|
||||||
|
local img_container_h = self.height - button_table:getSize().h
|
||||||
|
|
||||||
|
local title_bar, title_sep
|
||||||
|
if self.with_title_bar then
|
||||||
|
local title_text = FrameContainer:new{
|
||||||
|
padding = self.title_padding,
|
||||||
|
margin = self.title_margin,
|
||||||
|
bordersize = 0,
|
||||||
|
TextWidget:new{
|
||||||
|
text = self.title_text,
|
||||||
|
face = self.title_face,
|
||||||
|
bold = true,
|
||||||
|
width = self.width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title_bar = OverlapGroup:new{
|
||||||
|
dimen = {
|
||||||
|
w = self.width,
|
||||||
|
h = title_text:getSize().h
|
||||||
|
},
|
||||||
|
title_text,
|
||||||
|
CloseButton:new{ window = self, },
|
||||||
|
}
|
||||||
|
title_sep = LineWidget:new{
|
||||||
|
dimen = Geom:new{
|
||||||
|
w = self.width,
|
||||||
|
h = Screen:scaleBySize(2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-- adjust height available to our image
|
||||||
|
img_container_h = img_container_h - title_bar:getSize().h - title_sep:getSize().h
|
||||||
|
end
|
||||||
|
|
||||||
|
local max_image_h = img_container_h - self.image_padding*2
|
||||||
|
local max_image_w = self.width - self.image_padding*2
|
||||||
|
|
||||||
|
-- Do a first rendering without our h/w to get native image size and see if it needs to be reduced
|
||||||
|
self._image_wg = ImageWidget:new{
|
||||||
|
file = self.file,
|
||||||
|
image = self.image,
|
||||||
|
image_disposable = false, -- we may re-use self.image
|
||||||
|
alpha = true,
|
||||||
|
pre_rotate = self.rotated and self.rotation_angle or 0,
|
||||||
|
}
|
||||||
|
local imwg_size = self._image_wg:getSize()
|
||||||
|
if self.stretched or imwg_size.w > max_image_w or imwg_size.h > max_image_h then
|
||||||
|
-- 2nd rendering if it needs to be stretched to fit our size
|
||||||
|
self:_clean_image_wg() -- clean previous ImageWidget._bb
|
||||||
|
self._image_wg = ImageWidget:new{
|
||||||
|
file = self.file,
|
||||||
|
image = self.image,
|
||||||
|
image_disposable = false, -- we may re-use self.image
|
||||||
|
alpha = true,
|
||||||
|
width = max_image_w,
|
||||||
|
height = max_image_h,
|
||||||
|
autostretch = true,
|
||||||
|
pre_rotate = self.rotated and self.rotation_angle or 0,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local image_container = CenterContainer:new{
|
||||||
|
dimen = Geom:new{
|
||||||
|
w = self.width,
|
||||||
|
h = img_container_h,
|
||||||
|
},
|
||||||
|
self._image_wg,
|
||||||
|
}
|
||||||
|
|
||||||
|
local frame_elements
|
||||||
|
if self.with_title_bar then
|
||||||
|
frame_elements = VerticalGroup:new{
|
||||||
|
align = "left",
|
||||||
|
title_bar,
|
||||||
|
title_sep,
|
||||||
|
image_container,
|
||||||
|
button_container,
|
||||||
|
}
|
||||||
|
else
|
||||||
|
frame_elements = VerticalGroup:new{
|
||||||
|
align = "left",
|
||||||
|
image_container,
|
||||||
|
button_container,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
self.main_frame = FrameContainer:new{
|
||||||
|
radius = not self.fullscreen and 8 or nil,
|
||||||
|
bordersize = 3,
|
||||||
|
padding = 0,
|
||||||
|
margin = 0,
|
||||||
|
background = Blitbuffer.COLOR_WHITE,
|
||||||
|
frame_elements,
|
||||||
|
}
|
||||||
|
self[1] = WidgetContainer:new{
|
||||||
|
align = self.align,
|
||||||
|
dimen = self.region,
|
||||||
|
FrameContainer:new{
|
||||||
|
bordersize = 0,
|
||||||
|
padding = Screen:scaleBySize(5),
|
||||||
|
self.main_frame,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UIManager:setDirty("all", function()
|
||||||
|
local update_region = self.main_frame.dimen:combine(orig_dimen)
|
||||||
|
logger.dbg("update image region", update_region)
|
||||||
|
return "partial", update_region
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageViewer:onShow()
|
||||||
|
UIManager:setDirty(self, function()
|
||||||
|
return "ui", self.main_frame.dimen
|
||||||
|
end)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageViewer:onTap(arg, ges)
|
||||||
|
if ges.pos:notIntersectWith(self.main_frame.dimen) then
|
||||||
|
self:onClose()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageViewer:onSwipe(arg, ges)
|
||||||
|
-- trigger full refresh
|
||||||
|
UIManager:setDirty(nil, "full")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageViewer:onClose()
|
||||||
|
UIManager:close(self)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageViewer:onAnyKeyPressed()
|
||||||
|
self:onClose()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageViewer:onCloseWidget()
|
||||||
|
-- clean all our BlitBuffer objects when UIManager:close() was called
|
||||||
|
self:_clean_image_wg()
|
||||||
|
if self.image and self.image_disposable and self.image.free then
|
||||||
|
logger.dbg("ImageViewer:free(self.image)")
|
||||||
|
self.image:free()
|
||||||
|
self.image = nil
|
||||||
|
end
|
||||||
|
UIManager:setDirty(nil, function()
|
||||||
|
return "partial", self.main_frame.dimen
|
||||||
|
end)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return ImageViewer
|
Loading…
Reference in New Issue