mirror of https://github.com/koreader/koreader
Adds ScrollableContainer, to be used with tall widgets (#8299)
And use it with KeyboardLayoutDialog.reviewable/pr8323/r1
parent
8c29b71e45
commit
ade89cb9b6
@ -0,0 +1,449 @@
|
||||
--[[--
|
||||
ScrollableContainer allows scrolling its content (1 widget) within its own dimensions
|
||||
|
||||
This scrollable container needs to be known as widget.cropping_widget in
|
||||
the widget using it that is passed to UIManager:show() for UIManager to
|
||||
ensure proper interception of inner widget self-repainting/invert (mostly
|
||||
used when flashing for UI feedback that we want to limit to the cropped
|
||||
area).
|
||||
If we notice some inner element flashing leaking outside the scrollable
|
||||
area, it's probably some 'show_parent' forwarding missing from the main
|
||||
widget to some of the inner widgets: chase the missing ones and add them.
|
||||
--]]
|
||||
|
||||
local BD = require("ui/bidi")
|
||||
local Blitbuffer = require("ffi/blitbuffer")
|
||||
local Device = require("device")
|
||||
local Geom = require("ui/geometry")
|
||||
local GestureRange = require("ui/gesturerange")
|
||||
local HorizontalScrollBar = require("ui/widget/horizontalscrollbar")
|
||||
local InputContainer = require("ui/widget/container/inputcontainer")
|
||||
local Math = require("optmath")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local VerticalScrollBar = require("ui/widget/verticalscrollbar")
|
||||
local Screen = Device.screen
|
||||
local logger = require("logger")
|
||||
|
||||
local ScrollableContainer = InputContainer:new{
|
||||
-- Events to ignore (ie: ignore_events={"hold", "hold_release"})
|
||||
ignore_events = nil,
|
||||
scroll_bar_width = Screen:scaleBySize(6),
|
||||
|
||||
-- Set to true if child widget is larger, false otherwise
|
||||
_is_scrollable = nil,
|
||||
-- Current scroll offset (use getScrolledOffset()/setScrolledOffset() to access them)
|
||||
_scroll_offset_x = 0,
|
||||
_scroll_offset_y = 0,
|
||||
_max_scroll_offset_x = 0,
|
||||
_max_scroll_offset_y = 0,
|
||||
-- Internal state between events
|
||||
_touch_pre_pan_was_inside = false,
|
||||
_scrolling = false,
|
||||
_scroll_relative_x = nil,
|
||||
_scroll_relative_y = nil,
|
||||
-- Scrollbar widgets, created as needed
|
||||
_v_scroll_bar = nil,
|
||||
_h_scroll_bar = nil,
|
||||
-- Scratch buffer
|
||||
_bb = nil,
|
||||
_crop_w = nil,
|
||||
_crop_h = nil,
|
||||
_crop_dx = 0,
|
||||
_mirroredUI = BD.mirroredUILayout(),
|
||||
}
|
||||
|
||||
function ScrollableContainer:getScrollbarWidth(scroll_bar_width)
|
||||
-- Return the width taken by the (default) scroll bar and its paddings
|
||||
if not scroll_bar_width then
|
||||
scroll_bar_width = self.scroll_bar_width
|
||||
end
|
||||
return 3 * scroll_bar_width
|
||||
end
|
||||
|
||||
function ScrollableContainer:init()
|
||||
if Device:isTouchDevice() then
|
||||
local range = Geom:new{
|
||||
x = 0, y = 0,
|
||||
w = Screen:getWidth(),
|
||||
h = Screen:getHeight(),
|
||||
}
|
||||
-- Unflatten self.ignore_events to table keys for cleaner code below
|
||||
local ignore = {}
|
||||
if self.ignore_events then
|
||||
for _, evname in pairs(self.ignore_events) do
|
||||
ignore[evname] = true
|
||||
end
|
||||
end
|
||||
-- The following gestures need to be supported, depending on the
|
||||
-- ways a user can move/scroll things:
|
||||
-- Hold happens if he holds at start
|
||||
-- Pan happens if he doesn't hold at start, but holds at end
|
||||
-- Swipe happens if he doesn't hold at any moment
|
||||
-- (Touch is needed for accurate pan)
|
||||
self.ges_events = {}
|
||||
self.ges_events.ScrollableTouch = not ignore.touch and { GestureRange:new{ ges = "touch", range = range } } or nil
|
||||
self.ges_events.ScrollableSwipe = not ignore.swipe and { GestureRange:new{ ges = "swipe", range = range } } or nil
|
||||
self.ges_events.ScrollableHold = not ignore.hold and { GestureRange:new{ ges = "hold", range = range } } or nil
|
||||
self.ges_events.ScrollableHoldPan = not ignore.hold_pan and { GestureRange:new{ ges = "hold_pan", range = range } } or nil
|
||||
self.ges_events.ScrollableHoldRelease = not ignore.hold_release and { GestureRange:new{ ges = "hold_release", range = range } } or nil
|
||||
self.ges_events.ScrollablePan = not ignore.pan and { GestureRange:new{ ges = "pan", range = range } } or nil
|
||||
self.ges_events.ScrollablePanRelease = not ignore.pan_release and { GestureRange:new{ ges = "pan_release", range = range } } or nil
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollableContainer:initState()
|
||||
local content_size = self[1]:getSize()
|
||||
self._max_scroll_offset_x = math.max(0, content_size.w - self.dimen.w)
|
||||
self._max_scroll_offset_y = math.max(0, content_size.h - self.dimen.h)
|
||||
if self._max_scroll_offset_x == 0 and self._max_scroll_offset_y == 0 then
|
||||
-- Inner widget fits entirely: no need for anything scrollable
|
||||
self._is_scrollable = false
|
||||
else
|
||||
self._is_scrollable = true
|
||||
self._crop_w = self.dimen.w
|
||||
self._crop_h = self.dimen.h
|
||||
if self._max_scroll_offset_y > 0 then
|
||||
-- Adding a vertical scrollbar reduces the available width: recompute
|
||||
self._max_scroll_offset_x = math.max(0, content_size.w - (self.dimen.w - 3*self.scroll_bar_width))
|
||||
end
|
||||
if self._max_scroll_offset_x > 0 then
|
||||
-- Adding a horizontal scrollbar reduces the available height: recompute
|
||||
self._max_scroll_offset_y = math.max(0, content_size.h - (self.dimen.h - 3*self.scroll_bar_width))
|
||||
if self._max_scroll_offset_y > 0 then
|
||||
-- And re-compute again if we have to now add a vertical scrollbar
|
||||
self._max_scroll_offset_x = math.max(0, content_size.w - (self.dimen.w - 3*self.scroll_bar_width))
|
||||
end
|
||||
end
|
||||
-- Scrollbars won't be classic sub-widgets, we'll handle their painting ourselves
|
||||
if self._max_scroll_offset_y > 0 then
|
||||
self._v_scroll_bar = VerticalScrollBar:new{
|
||||
width = self.scroll_bar_width,
|
||||
height = self.dimen.h,
|
||||
scroll_callback = function(ratio)
|
||||
self:scrollToRatio(nil, ratio)
|
||||
end
|
||||
}
|
||||
self._crop_w = self.dimen.w - 3*self.scroll_bar_width
|
||||
end
|
||||
if self._max_scroll_offset_x > 0 then
|
||||
self._h_scroll_bar_shift = 0
|
||||
if self._v_scroll_bar then
|
||||
-- Reduce its width so to not overlap with the vertical scroll bar
|
||||
self._h_scroll_bar_shift = 3*self.scroll_bar_width
|
||||
end
|
||||
self._h_scroll_bar = HorizontalScrollBar:new{
|
||||
height = self.scroll_bar_width,
|
||||
width = self.dimen.w - self._h_scroll_bar_shift,
|
||||
scroll_callback = function(ratio)
|
||||
self:scrollToRatio(ratio, nil)
|
||||
end
|
||||
}
|
||||
self._crop_h = self.dimen.h - 3*self.scroll_bar_width
|
||||
end
|
||||
if self._mirroredUI then
|
||||
if self._v_scroll_bar then
|
||||
self._crop_dx = self.dimen.w - self._crop_w
|
||||
end
|
||||
end
|
||||
self:_updateScrollBars()
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollableContainer:getCropRegion()
|
||||
return Geom:new{
|
||||
x = self.dimen.x + self._crop_dx,
|
||||
y = self.dimen.y,
|
||||
w = self._crop_w,
|
||||
h = self._crop_h,
|
||||
}
|
||||
end
|
||||
|
||||
function ScrollableContainer:_updateScrollBars()
|
||||
if self._v_scroll_bar then
|
||||
local dheight = self._crop_h / (self._max_scroll_offset_y + self._crop_h)
|
||||
local low = self._scroll_offset_y / (self._max_scroll_offset_y + self._crop_h)
|
||||
local high = low + dheight
|
||||
self._v_scroll_bar:set(low, high)
|
||||
end
|
||||
if self._h_scroll_bar then
|
||||
local dwidth = self._crop_w / (self._max_scroll_offset_x + self._crop_w)
|
||||
local low = self._scroll_offset_x / (self._max_scroll_offset_x + self._crop_w)
|
||||
local high = low + dwidth
|
||||
self._h_scroll_bar:set(low, high)
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollableContainer:scrollToRatio(ratio_x, ratio_y)
|
||||
if ratio_y then
|
||||
local dy = ratio_y * (self._max_scroll_offset_y + self._crop_h)
|
||||
self._scroll_offset_y = dy - Math.round(self._crop_h/2)
|
||||
if self._scroll_offset_y < 0 then
|
||||
self._scroll_offset_y = 0
|
||||
end
|
||||
if self._scroll_offset_y > self._max_scroll_offset_y then
|
||||
self._scroll_offset_y = self._max_scroll_offset_y
|
||||
end
|
||||
end
|
||||
if ratio_x then
|
||||
local dx = ratio_x * (self._max_scroll_offset_x + self._crop_w)
|
||||
self._scroll_offset_x = dx - Math.round(self._crop_w/2)
|
||||
if self._scroll_offset_x < 0 then
|
||||
self._scroll_offset_x = 0
|
||||
end
|
||||
if self._scroll_offset_x > self._max_scroll_offset_x then
|
||||
self._scroll_offset_x = self._max_scroll_offset_x
|
||||
end
|
||||
end
|
||||
self:_scrollBy(0, 0) -- get the additional work done
|
||||
end
|
||||
|
||||
function ScrollableContainer:_scrollBy(dx, dy)
|
||||
if self._mirroredUI then
|
||||
dx = -dx
|
||||
end
|
||||
self._scroll_offset_x = self._scroll_offset_x + Math.round(dx)
|
||||
self._scroll_offset_y = self._scroll_offset_y + Math.round(dy)
|
||||
if self._scroll_offset_x < 0 then
|
||||
self._scroll_offset_x = 0
|
||||
end
|
||||
if self._scroll_offset_y < 0 then
|
||||
self._scroll_offset_y = 0
|
||||
end
|
||||
if self._scroll_offset_x > self._max_scroll_offset_x then
|
||||
self._scroll_offset_x = self._max_scroll_offset_x
|
||||
end
|
||||
if self._scroll_offset_y > self._max_scroll_offset_y then
|
||||
self._scroll_offset_y = self._max_scroll_offset_y
|
||||
end
|
||||
self:_updateScrollBars()
|
||||
UIManager:setDirty(self.show_parent, function()
|
||||
return "ui", self.dimen
|
||||
end)
|
||||
end
|
||||
|
||||
function ScrollableContainer:getScrolledOffset()
|
||||
return Geom:new{
|
||||
x = self._scroll_offset_x,
|
||||
y = self._scroll_offset_y,
|
||||
}
|
||||
end
|
||||
|
||||
function ScrollableContainer:setScrolledOffset(offset_point)
|
||||
if offset_point and offset_point.x and offset_point.y then
|
||||
self._scroll_offset_x = offset_point.x
|
||||
self._scroll_offset_y = offset_point.y
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollableContainer:onCloseWidget()
|
||||
if self._bb then
|
||||
self._bb:free()
|
||||
self._bb = nil
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollableContainer:paintTo(bb, x, y)
|
||||
if self[1] == nil then
|
||||
return
|
||||
end
|
||||
self.dimen.x = x
|
||||
self.dimen.y = y
|
||||
|
||||
if self._is_scrollable == nil then -- not checked yet
|
||||
self:initState()
|
||||
end
|
||||
|
||||
if not self._is_scrollable then
|
||||
-- nothing to scroll: pass-through
|
||||
self[1]:paintTo(bb, x, y)
|
||||
return
|
||||
end
|
||||
|
||||
local screen_size = Screen:getSize()
|
||||
-- Create/Recreate the compose cache if we changed screen geometry
|
||||
if not self._bb or self._bb:getWidth() ~= screen_size.w or self._bb:getHeight() ~= screen_size.h then
|
||||
if self._bb then
|
||||
self._bb:free()
|
||||
end
|
||||
-- create a canvas for our child widget to paint to
|
||||
self._bb = Blitbuffer.new(screen_size.w, screen_size.h, bb:getType())
|
||||
end
|
||||
|
||||
-- We need to fill it with our usual background color on each drawing,
|
||||
-- to erase bits that may not be overwritten after a scroll
|
||||
self._bb:fill(Blitbuffer.COLOR_WHITE)
|
||||
local dx
|
||||
if self._mirroredUI then
|
||||
dx = self._max_scroll_offset_x - self._scroll_offset_x - self._crop_dx
|
||||
else
|
||||
dx = self._scroll_offset_x
|
||||
end
|
||||
self[1]:paintTo(self._bb, x - dx, y - self._scroll_offset_y)
|
||||
bb:blitFrom(self._bb, x + self._crop_dx, y, x + self._crop_dx, y, self._crop_w, self._crop_h)
|
||||
|
||||
-- Draw our scrollbars over
|
||||
if self._h_scroll_bar then
|
||||
if self._mirroredUI then
|
||||
self._h_scroll_bar:paintTo(bb, x + self._h_scroll_bar_shift, y + self.dimen.h - 2*self.scroll_bar_width)
|
||||
else
|
||||
self._h_scroll_bar:paintTo(bb, x, y + self.dimen.h - 2*self.scroll_bar_width)
|
||||
end
|
||||
end
|
||||
if self._v_scroll_bar then
|
||||
if self._mirroredUI then
|
||||
self._v_scroll_bar:paintTo(bb, x + self.scroll_bar_width, y)
|
||||
else
|
||||
self._v_scroll_bar:paintTo(bb, x + self.dimen.w - 2*self.scroll_bar_width, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollableContainer:propagateEvent(event)
|
||||
-- Override WidgetContainer:propagateEvent() (which propagates an event
|
||||
-- to children before having it handled by the widget itself)
|
||||
if not self._is_scrollable then
|
||||
-- pass-through
|
||||
return InputContainer.propagateEvent(self, event)
|
||||
end
|
||||
if event.handler == "onGesture" and event.argc == 1 then
|
||||
local ges = event.args[1]
|
||||
-- Don't propagate events that happen out of view (in the hidden
|
||||
-- scrolled-out area) to child
|
||||
if ges.pos and not ges.pos:intersectWith(self.dimen) then
|
||||
return false -- we may handle it here
|
||||
end
|
||||
end
|
||||
-- Give any event first to our scrollbars
|
||||
if self._v_scroll_bar and self._v_scroll_bar:handleEvent(event) then
|
||||
return true
|
||||
end
|
||||
if self._h_scroll_bar and self._h_scroll_bar:handleEvent(event) then
|
||||
return true
|
||||
end
|
||||
-- Pass non-gestures events, and gestures event in the view, to our child
|
||||
return InputContainer.propagateEvent(self, event)
|
||||
end
|
||||
|
||||
function ScrollableContainer:onScrollableSwipe(_, ges)
|
||||
if not self._is_scrollable then
|
||||
return false
|
||||
end
|
||||
logger.dbg("ScrollableContainer:onScrollableSwipe", ges)
|
||||
if not ges.pos:intersectWith(self.dimen) then
|
||||
-- with swipe, ges.pos is swipe's start position, which should
|
||||
-- be on us to consider it
|
||||
return false
|
||||
end
|
||||
self._scrolling = false -- could have been set by "pan" event received before "swipe"
|
||||
local direction = ges.direction
|
||||
local distance = ges.distance
|
||||
local sq_distance = math.floor(math.sqrt(distance*distance/2))
|
||||
if direction == "north" then self:_scrollBy(0, distance)
|
||||
elseif direction == "south" then self:_scrollBy(0, -distance)
|
||||
elseif direction == "east" then self:_scrollBy(-distance, 0)
|
||||
elseif direction == "west" then self:_scrollBy(distance, 0)
|
||||
elseif direction == "northeast" then self:_scrollBy(-sq_distance, sq_distance)
|
||||
elseif direction == "northwest" then self:_scrollBy(sq_distance, sq_distance)
|
||||
elseif direction == "southeast" then self:_scrollBy(-sq_distance, -sq_distance)
|
||||
elseif direction == "southwest" then self:_scrollBy(sq_distance, -sq_distance)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function ScrollableContainer:onScrollableTouch(_, ges)
|
||||
if not self._is_scrollable then
|
||||
return false
|
||||
end
|
||||
-- First "pan" event may already be outside of us, we need to
|
||||
-- remember any "touch" event on us prior to "pan"
|
||||
logger.dbg("ScrollableContainer:onScrollableTouch", ges)
|
||||
if ges.pos:intersectWith(self.dimen) then
|
||||
self._touch_pre_pan_was_inside = true
|
||||
self._scroll_relative_x = ges.pos.x
|
||||
self._scroll_relative_y = ges.pos.y
|
||||
else
|
||||
self._touch_pre_pan_was_inside = false
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ScrollableContainer:onScrollableHold(_, ges)
|
||||
if not self._is_scrollable then
|
||||
return false
|
||||
end
|
||||
logger.dbg("ScrollableContainer:onScrollableHold", ges)
|
||||
if ges.pos:intersectWith(self.dimen) then
|
||||
self._scrolling = true -- start of pan
|
||||
self._scroll_relative_x = ges.pos.x
|
||||
self._scroll_relative_y = ges.pos.y
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ScrollableContainer:onScrollableHoldPan(_, ges)
|
||||
if not self._is_scrollable then
|
||||
return false
|
||||
end
|
||||
logger.dbg("ScrollableContainer:onScrollableHoldPan", ges)
|
||||
-- we may sometimes not see the "hold" event
|
||||
if ges.pos:intersectWith(self.dimen) or self._scrolling or self._touch_pre_pan_was_inside then
|
||||
self._touch_pre_pan_was_inside = false -- reset it
|
||||
self._scrolling = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ScrollableContainer:onScrollableHoldRelease(_, ges)
|
||||
if not self._is_scrollable then
|
||||
return false
|
||||
end
|
||||
logger.dbg("ScrollableContainer:onScrollableHoldRelease", ges)
|
||||
if self._scrolling or self._touch_pre_pan_was_inside then
|
||||
self._scrolling = false
|
||||
if not self._scroll_relative_x or not self._scroll_relative_y then
|
||||
-- no previous event gave us accurate scroll info, ignore it
|
||||
return false
|
||||
end
|
||||
self._scroll_relative_x = ges.pos.x - self._scroll_relative_x
|
||||
self._scroll_relative_y = ges.pos.y - self._scroll_relative_y
|
||||
self:_scrollBy(-self._scroll_relative_x, -self._scroll_relative_y)
|
||||
self._scroll_relative_x = nil
|
||||
self._scroll_relative_y = nil
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ScrollableContainer:onScrollablePan(_, ges)
|
||||
if not self._is_scrollable then
|
||||
return false
|
||||
end
|
||||
logger.dbg("ScrollableContainer:onScrollablePan", ges)
|
||||
if ges.pos:intersectWith(self.dimen) or self._scrolling or self._touch_pre_pan_was_inside then
|
||||
self._touch_pre_pan_was_inside = false -- reset it
|
||||
self._scrolling = true
|
||||
self._scroll_relative_x = ges.relative.x
|
||||
self._scroll_relative_y = ges.relative.y
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ScrollableContainer:onScrollablePanRelease(_, ges)
|
||||
if not self._is_scrollable then
|
||||
return false
|
||||
end
|
||||
logger.dbg("ScrollableContainer:onScrollablePanRelease", ges)
|
||||
if self._scrolling then
|
||||
self:_scrollBy(-self._scroll_relative_x, -self._scroll_relative_y)
|
||||
self._scrolling = false
|
||||
self._scroll_relative_x = nil
|
||||
self._scroll_relative_y = nil
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return ScrollableContainer
|
@ -0,0 +1,128 @@
|
||||
local BD = require("ui/bidi")
|
||||
local Blitbuffer = require("ffi/blitbuffer")
|
||||
local Device = require("device")
|
||||
local Geom = require("ui/geometry")
|
||||
local GestureRange = require("ui/gesturerange")
|
||||
local InputContainer = require("ui/widget/container/inputcontainer")
|
||||
local Size = require("ui/size")
|
||||
local Screen = require("device").screen
|
||||
|
||||
local HorizontalScrollBar = InputContainer:new{
|
||||
enable = true,
|
||||
low = 0,
|
||||
high = 1,
|
||||
height = Size.padding.default,
|
||||
width = Size.item.height_large, -- as in VerticalScrollBar
|
||||
bordersize = Size.border.thin,
|
||||
bordercolor = Blitbuffer.COLOR_BLACK,
|
||||
radius = 0,
|
||||
rectcolor = Blitbuffer.COLOR_BLACK,
|
||||
-- minimal width of the thumb/knob/grip (usually showing the current
|
||||
-- view size and position relative to the whole scrollable width):
|
||||
min_thumb_size = Size.line.thick,
|
||||
scroll_callback = nil,
|
||||
-- extra touchable height (for scrolling with pan) can be larger than
|
||||
-- the provided height (this is added on each side)
|
||||
extra_touch_on_side_heightratio = 1, -- make it 3 x height
|
||||
_mirroredUI = BD.mirroredUILayout(),
|
||||
}
|
||||
|
||||
function HorizontalScrollBar:init()
|
||||
self.extra_touch_on_side = math.ceil( self.extra_touch_on_side_heightratio * self.height)
|
||||
if Device:isTouchDevice() then
|
||||
local pan_rate = Screen.low_pan_rate and 2.0 or 5.0
|
||||
self.ges_events = {
|
||||
TapScroll = {
|
||||
GestureRange:new{
|
||||
ges = "tap",
|
||||
range = function() return self.touch_dimen end,
|
||||
},
|
||||
},
|
||||
HoldScroll = {
|
||||
GestureRange:new{
|
||||
ges = "hold",
|
||||
range = function() return self.touch_dimen end,
|
||||
},
|
||||
},
|
||||
HoldPanScroll = {
|
||||
GestureRange:new{
|
||||
ges = "hold_pan",
|
||||
rate = pan_rate,
|
||||
range = function() return self.touch_dimen end,
|
||||
},
|
||||
},
|
||||
HoldReleaseScroll = {
|
||||
GestureRange:new{
|
||||
ges = "hold_release",
|
||||
range = function() return self.touch_dimen end,
|
||||
},
|
||||
},
|
||||
PanScroll = {
|
||||
GestureRange:new{
|
||||
ges = "pan",
|
||||
rate = pan_rate,
|
||||
range = function() return self.touch_dimen end,
|
||||
},
|
||||
},
|
||||
PanScrollRelease = {
|
||||
GestureRange:new{
|
||||
ges = "pan_release",
|
||||
range = function() return self.touch_dimen end,
|
||||
},
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function HorizontalScrollBar:onTapScroll(arg, ges)
|
||||
if self.scroll_callback then
|
||||
local ratio = (ges.pos.x - self.touch_dimen.x) / self.width
|
||||
if self._mirroredUI then
|
||||
ratio = 1 - ratio
|
||||
end
|
||||
self.scroll_callback(ratio)
|
||||
return true
|
||||
end
|
||||
end
|
||||
HorizontalScrollBar.onHoldScroll = HorizontalScrollBar.onTapScroll
|
||||
HorizontalScrollBar.onHoldPanScroll = HorizontalScrollBar.onTapScroll
|
||||
HorizontalScrollBar.onHoldReleaseScroll = HorizontalScrollBar.onTapScroll
|
||||
HorizontalScrollBar.onPanScroll = HorizontalScrollBar.onTapScroll
|
||||
HorizontalScrollBar.onPanScrollRelease = HorizontalScrollBar.onTapScroll
|
||||
|
||||
function HorizontalScrollBar:getSize()
|
||||
return Geom:new{
|
||||
w = self.width,
|
||||
h = self.height
|
||||
}
|
||||
end
|
||||
|
||||
function HorizontalScrollBar:set(low, high)
|
||||
self.low = low > 0 and low or 0
|
||||
self.high = high < 1 and high or 1
|
||||
end
|
||||
|
||||
function HorizontalScrollBar:paintTo(bb, x, y)
|
||||
if not self.enable then return end
|
||||
self.touch_dimen = Geom:new{
|
||||
x = x,
|
||||
y = y - self.extra_touch_on_side,
|
||||
w = self.width,
|
||||
h = self.height + 2 * self.extra_touch_on_side,
|
||||
}
|
||||
bb:paintBorder(x, y, self.width, self.height,
|
||||
self.bordersize, self.bordercolor, self.radius)
|
||||
if self._mirroredUI then
|
||||
bb:paintRect(x + self.bordersize + (1-self.high) * self.width, y + self.bordersize,
|
||||
math.max((self.width - 2 * self.bordersize) * (self.high - self.low), self.min_thumb_size),
|
||||
self.height - 2 * self.bordersize,
|
||||
self.rectcolor)
|
||||
else
|
||||
bb:paintRect(x + self.bordersize + self.low * self.width, y + self.bordersize,
|
||||
math.max((self.width - 2 * self.bordersize) * (self.high - self.low), self.min_thumb_size),
|
||||
self.height - 2 * self.bordersize,
|
||||
self.rectcolor)
|
||||
end
|
||||
end
|
||||
|
||||
return HorizontalScrollBar
|
Loading…
Reference in New Issue