mirror of https://github.com/koreader/koreader
rearranged source tree
parent
08edf7c259
commit
10d980ed87
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
require "math"
|
||||
require "ui/widget/container"
|
||||
|
||||
--[[
|
||||
BBoxWidget shows a bbox for page cropping
|
@ -1,4 +1,4 @@
|
||||
require "ui/widget"
|
||||
require "ui/widget/container"
|
||||
|
||||
--[[
|
||||
a button widget
|
@ -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
|
@ -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
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
require "ui/menu"
|
||||
require "ui/widget/menu"
|
||||
|
||||
FileChooser = Menu:new{
|
||||
height = Screen:getHeight(),
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
require "ui/ui"
|
||||
require "ui/widget"
|
||||
require "ui/widget/container"
|
||||
|
||||
--[[
|
||||
Widget that displays an informational message
|
@ -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"
|
||||
|
||||
--[[
|
@ -1,5 +1,4 @@
|
||||
require "ui/ui"
|
||||
require "ui/widget"
|
||||
require "ui/widget/container"
|
||||
|
||||
--[[
|
||||
Widget that displays a tiny notification on top of screen
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue