2
0
mirror of https://github.com/koreader/koreader synced 2024-11-10 01:10:34 +00:00
koreader/frontend/ui/geometry.lua

434 lines
9.0 KiB
Lua
Raw Normal View History

2016-12-04 23:13:49 +00:00
--[[--
2012-05-18 22:37:49 +00:00
2D Geometry utilities
2016-12-04 23:13:49 +00:00
All of these apply to full rectangles:
local Geom = require("ui/geometry")
Geom:new{ x = 1, y = 0, w = Screen:scaleBySize(100), h = Screen:scaleBySize(200), }
2016-12-04 23:13:49 +00:00
Some behaviour is defined for points:
Geom:new{ x = 0, y = 0, }
Some behaviour is defined for dimensions:
2012-05-18 22:37:49 +00:00
Geom:new{ w = Screen:scaleBySize(600), h = Screen:scaleBySize(800), }
2012-05-18 22:37:49 +00:00
2016-12-04 23:13:49 +00:00
Just use it on simple tables that have x, y and/or w, h
or define your own types using this as a metatable.
Where @{ffi.blitbuffer|BlitBuffer} is concerned, a point at (0, 0) means the top-left corner.
2016-12-04 23:13:49 +00:00
]]
local Math = require("optmath")
--[[--
Represents a full rectangle (all fields are set), a point (x & y are set), or a dimension (w & h are set).
2016-12-04 23:13:49 +00:00
@table Geom
]]
2013-10-18 20:38:07 +00:00
local Geom = {
x = 0, -- left origin
y = 0, -- top origin
w = 0, -- width
h = 0, -- height
2012-05-18 22:37:49 +00:00
}
function Geom:new(o)
2016-02-16 02:08:04 +00:00
if not o then o = {} end
2014-03-13 13:52:43 +00:00
setmetatable(o, self)
self.__index = self
return o
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Makes a deep copy of itself.
2016-12-04 23:13:49 +00:00
@treturn Geom
]]
function Geom:copy()
2014-03-13 13:52:43 +00:00
local n = Geom:new()
n.x = self.x
n.y = self.y
n.w = self.w
n.h = self.h
return n
2012-05-18 22:37:49 +00:00
end
function Geom:__tostring()
2014-03-13 13:52:43 +00:00
return self.w.."x"..self.h.."+"..self.x.."+"..self.y
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Offsets rectangle or point by relative values
2016-12-04 23:13:49 +00:00
@int dx x delta
@int dy y delta
]]
2012-05-18 22:37:49 +00:00
function Geom:offsetBy(dx, dy)
2014-03-13 13:52:43 +00:00
self.x = self.x + dx
self.y = self.y + dy
return self
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
Offsets rectangle or point to certain coordinates
@int x new x
@int y new y
]]
2012-05-18 22:37:49 +00:00
function Geom:offsetTo(x, y)
2014-03-13 13:52:43 +00:00
self.x = x
self.y = y
return self
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
Scales rectangle (grow to bottom and to the right) or dimension
2012-05-18 22:37:49 +00:00
2016-12-04 23:13:49 +00:00
If a single factor is given, it is applied to both width and height
@int zx scale for x axis
@int zy scale for y axis
]]
2012-05-18 22:37:49 +00:00
function Geom:scaleBy(zx, zy)
self.w = Math.round(self.w * zx)
self.h = Math.round(self.h * (zy or zx))
2014-03-13 13:52:43 +00:00
return self
end
2016-12-04 23:13:49 +00:00
--[[--
This method also takes care of x and y on top of @{Geom:scaleBy}
@int zx scale for x axis
@int zy scale for y axis
]]
2012-12-02 09:09:32 +00:00
function Geom:transformByScale(zx, zy)
self.x = Math.round(self.x * zx)
self.y = Math.round(self.y * (zx or zy))
2014-03-13 13:52:43 +00:00
self:scaleBy(zx, zy)
2012-12-02 09:09:32 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
Returns area of itself.
@treturn int
]]
function Geom:area()
2014-03-13 13:52:43 +00:00
if not self.w or not self.h then
return 0
else
return self.w * self.h
end
end
2016-12-04 23:13:49 +00:00
--[[--
Enlarges or shrinks dimensions or rectangles
2016-12-04 23:13:49 +00:00
Note that for rectangles the offset stays the same
@int dw width delta
@int dh height delta
]]
function Geom:changeSizeBy(dw, dh)
2014-03-13 13:52:43 +00:00
self.w = self.w + dw
self.h = self.h + dh
return self
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
Returns a new outer rectangle that contains both us and a given rectangle
2012-05-18 22:37:49 +00:00
2016-12-04 23:13:49 +00:00
Works for rectangles, dimensions and points
@tparam Geom rect_b
@treturn Geom
]]
2012-05-18 22:37:49 +00:00
function Geom:combine(rect_b)
2014-03-13 13:52:43 +00:00
local combined = self:copy()
2016-12-04 23:13:49 +00:00
if not rect_b or rect_b:area() == 0 then return combined end
2014-03-13 13:52:43 +00:00
if combined.x > rect_b.x then
combined.x = rect_b.x
end
if combined.y > rect_b.y then
combined.y = rect_b.y
end
if self.x + self.w > rect_b.x + rect_b.w then
combined.w = self.x + self.w - combined.x
else
combined.w = rect_b.x + rect_b.w - combined.x
end
if self.y + self.h > rect_b.y + rect_b.h then
combined.h = self.y + self.h - combined.y
else
combined.h = rect_b.y + rect_b.h - combined.y
end
return combined
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
Returns a new rectangle for the part that we and a given rectangle share
2012-05-18 22:37:49 +00:00
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
@treturn Geom
2012-05-18 22:37:49 +00:00
]]--
--- @todo what happens if there is no rectangle shared? currently behaviour is undefined.
2012-05-18 22:37:49 +00:00
function Geom:intersect(rect_b)
2014-03-13 13:52:43 +00:00
-- make a copy of self
local intersected = self:copy()
if self.x < rect_b.x then
intersected.x = rect_b.x
end
if self.y < rect_b.y then
intersected.y = rect_b.y
end
if self.x + self.w < rect_b.x + rect_b.w then
intersected.w = self.x + self.w - intersected.x
else
intersected.w = rect_b.x + rect_b.w - intersected.x
end
if self.y + self.h < rect_b.y + rect_b.h then
intersected.h = self.y + self.h - intersected.y
else
intersected.h = rect_b.y + rect_b.h - intersected.y
end
return intersected
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Returns true if self does not share any area with rect_b
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
]]
function Geom:notIntersectWith(rect_b)
2014-03-13 13:52:43 +00:00
if (self.x >= (rect_b.x + rect_b.w))
or (self.y >= (rect_b.y + rect_b.h))
or (rect_b.x >= (self.x + self.w))
or (rect_b.y >= (self.y + self.h)) then
return true
end
return false
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Returns true if self geom shares area with rect_b.
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
]]
function Geom:intersectWith(rect_b)
return not self:notIntersectWith(rect_b)
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Set size of dimension or rectangle to size of given dimension/rectangle.
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
]]
2012-05-18 22:37:49 +00:00
function Geom:setSizeTo(rect_b)
2014-03-13 13:52:43 +00:00
self.w = rect_b.w
self.h = rect_b.h
return self
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Checks whether rect_b is within current rectangle
2012-05-18 22:37:49 +00:00
2016-12-13 16:06:02 +00:00
Works for dimensions, too. For points, it is basically an equality check.
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
]]
2012-05-18 22:37:49 +00:00
function Geom:contains(rect_b)
2014-03-13 13:52:43 +00:00
if self.x <= rect_b.x
and self.y <= rect_b.y
and self.x + self.w >= rect_b.x + rect_b.w
and self.y + self.h >= rect_b.y + rect_b.h
then
return true
end
return false
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Checks for equality.
2012-05-18 22:37:49 +00:00
2016-12-13 16:06:02 +00:00
Works for rectangles, points, and dimensions.
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
]]
2012-05-18 22:37:49 +00:00
function Geom:__eq(rect_b)
2014-03-13 13:52:43 +00:00
if self.x == rect_b.x
and self.y == rect_b.y
and self:equalSize(rect_b)
then
return true
end
return false
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Checks the size of a dimension/rectangle for equality.
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
]]
2012-05-18 22:37:49 +00:00
function Geom:equalSize(rect_b)
if self.w == rect_b.w and self.h == rect_b.h then
2014-03-13 13:52:43 +00:00
return true
end
return false
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Checks if our size is smaller than the size of the given dimension/rectangle.
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
]]
2012-05-18 22:37:49 +00:00
function Geom:__lt(rect_b)
2014-03-13 13:52:43 +00:00
if self.w < rect_b.w and self.h < rect_b.h then
return true
end
return false
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Checks if our size is smaller or equal to the size of the given dimension/rectangle.
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
]]
2012-05-18 22:37:49 +00:00
function Geom:__le(rect_b)
2014-03-13 13:52:43 +00:00
if self.w <= rect_b.w and self.h <= rect_b.h then
return true
end
return false
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Offsets the current rectangle by dx, dy while fitting it into the space
of a given rectangle.
2012-05-18 22:37:49 +00:00
2016-12-04 23:13:49 +00:00
This can also be called with dx=0 and dy=0, which will fit the current
2016-12-13 16:06:02 +00:00
rectangle into the given rectangle.
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
@int dx
@int dy
]]
2012-05-18 22:37:49 +00:00
function Geom:offsetWithin(rect_b, dx, dy)
2014-03-13 13:52:43 +00:00
-- check size constraints and shrink us when we're too big
if self.w > rect_b.w then
self.w = rect_b.w
end
if self.h > rect_b.h then
self.h = rect_b.h
end
-- offset
self.x = self.x + dx
self.y = self.y + dy
-- check offsets
if self.x < rect_b.x then
self.x = rect_b.x
end
if self.y < rect_b.y then
self.y = rect_b.y
end
if self.x + self.w > rect_b.x + rect_b.w then
self.x = rect_b.x + rect_b.w - self.w
end
if self.y + self.h > rect_b.y + rect_b.h then
self.y = rect_b.y + rect_b.h - self.h
end
2012-05-18 22:37:49 +00:00
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Centers the current rectangle at position x and y of a given rectangle.
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
@int dx
@int dy
]]
function Geom:centerWithin(rect_b, x, y)
2014-03-13 13:52:43 +00:00
-- check size constraints and shrink us when we're too big
if self.w > rect_b.w then
self.w = rect_b.w
end
if self.h > rect_b.h then
self.h = rect_b.h
end
-- place to center
self.x = x - self.w/2
self.y = y - self.h/2
-- check boundary
if self.x < rect_b.x then
self.x = rect_b.x
end
if self.y < rect_b.y then
self.y = rect_b.y
end
if self.x + self.w > rect_b.x + rect_b.w then
self.x = rect_b.x + rect_b.w - self.w
end
if self.y + self.h > rect_b.y + rect_b.h then
self.y = rect_b.y + rect_b.h - self.h
end
end
function Geom:shrinkInside(rect_b, dx, dy)
2014-03-13 13:52:43 +00:00
self:offsetBy(dx, dy)
return self:intersect(rect_b)
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Returns the Euclidean distance between two geoms.
2016-12-04 23:13:49 +00:00
@tparam Geom rect_b
]]
function Geom:distance(geom)
2014-03-13 13:52:43 +00:00
return math.sqrt(math.pow(self.x - geom.x, 2) + math.pow(self.y - geom.y, 2))
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Returns the midpoint of two geoms.
2016-12-04 23:13:49 +00:00
@tparam Geom geom
@treturn Geom
]]
function Geom:midpoint(geom)
2014-03-13 13:52:43 +00:00
return Geom:new{
x = Math.round((self.x + geom.x) / 2),
y = Math.round((self.y + geom.y) / 2),
2014-03-13 13:52:43 +00:00
w = 0, h = 0,
}
end
2016-12-04 23:13:49 +00:00
--[[--
2016-12-13 16:06:02 +00:00
Returns the center point of this geom.
2016-12-04 23:13:49 +00:00
@treturn Geom
]]
function Geom:center()
2014-03-13 13:52:43 +00:00
return Geom:new{
x = self.x + Math.round(self.w / 2),
y = self.y + Math.round(self.h / 2),
2014-03-13 13:52:43 +00:00
w = 0, h = 0,
}
end
2013-10-18 20:38:07 +00:00
--[[--
Resets an existing Geom object to zero.
@treturn Geom
]]
function Geom:clear()
self.x = 0
self.y = 0
self.w = 0
self.h = 0
return self
end
--[[--
Checks if a dimension or rectangle is empty.
@treturn bool
]]
function Geom:isEmpty()
if self.w == 0 or self.h == 0 then
return true
end
return false
end
2013-10-18 20:38:07 +00:00
return Geom