Как создать скролл бар
Компоненты скролл бара:
Viewport (Область просмотра / «Окно»)
- Что это: Невидимая (или видимая) рамка, через которую пользователь смотрит на контент.
- Зачем: Ограничивает видимую область. Всё, что выходит за её границы, автоматически обрезается.
- В Defold: Обычно это
Box-нода с включённым свойствомClipping: ON.
Content (Содержимое / «Длинная лента»)
- Что это: Контейнер, в котором лежат все ваши элементы (строки списка, картинки, текст).
- Зачем: Может быть в 2, 5 или 10 раз выше viewport. Когда вы «скроллите», вы на самом деле меняете
position.yэтого контейнера , двигая его внутри окна. - В Defold: Дочерний элемент
viewport. Координаты его дочерних нод считаются относительно него, что упрощает вёрстку.
Scrollbar (Трек / Полоса прокрутки)
- Что это: Статичный фон или «рельсы» справа/слева от контента.
- Зачем:
- Визуально сообщает пользователю: «Здесь есть контент за пределами экрана» .
- Служит опорой для расчёта позиции ползунка. Его высота используется как
100%диапазона прокрутки.
- В Defold: Обычная
Box-нода. Не двигается, не обрабатывает ввод (если не хотите кликать по треку для прыжка).
Handle (Ползунок / Бегунок)
- Что это: Подвижный элемент на треке, который можно тащить пальцем/мышью.
- Зачем:
- Показывает текущую позицию внутри длинного списка.
- Даёт инструмент для быстрой навигации (drag & drop).
- Его высота обратно пропорциональна длине контента: чем длиннее список, тем короче ползунок (как в реальных приложениях).
- В Defold:
Box-нода. Двигается программно черезgui.set_position(), синхронно сcontent, но в уменьшенном масштабе и с инверсией оси Y.
gui_script:
local FIXED_HANDLE_HEIGHT = 60
local INERTIA_DAMPING = 5.0
local INERTIA_THRESHOLD = 5.0
local VELOCITY_MIN = 15.0
-- ======================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ========================
local function update_scroll(self, new_offset)
self.scroll_offset = math.min(math.max(new_offset or 0, 0), self.scroll_max)
gui.set_position(self.content, vmath.vector3(self.content_init_pos.x, self.content_init_pos.y + self.scroll_offset, 0))
if self.scroll_max > 0 and self.scrollbar and self.handle then
local track_h = gui.get_size(self.scrollbar).y
local handle_h = gui.get_size(self.handle).y
local free = track_h - handle_h
local ratio = self.scroll_offset / self.scroll_max
local hx = self.scrollbar_init_pos.x
local hy = self.scrollbar_init_pos.y + free * (1.0 - ratio)
gui.set_position(self.handle, vmath.vector3(hx, hy, 0))
end
end
local function recalculate_scroll(self)
if not self.viewport or not self.content then return end
local content_h = gui.get_size(self.content).y
local viewport_h = gui.get_size(self.viewport).y
self.scroll_max = math.max(0, content_h - viewport_h)
local track_h = gui.get_size(self.scrollbar).y
local handle_h = math.min(FIXED_HANDLE_HEIGHT, track_h)
gui.set_size(self.handle, vmath.vector3(gui.get_size(self.handle).x, handle_h, 0))
gui.set_visible(self.handle, self.scroll_max > 0)
update_scroll(self, self.scroll_offset)
end
-- ======================== ОСНОВНЫЕ ФУНКЦИИ ========================
function init(self)
self.viewport = gui.get_node("viewport")
self.content = gui.get_node("content")
self.scrollbar = gui.get_node("scrollbar")
self.handle = gui.get_node("handle")
self.content_init_pos = gui.get_position(self.content)
self.scrollbar_init_pos = gui.get_position(self.scrollbar)
self.scroll_offset = 0
self.scroll_max = 0
self.is_dragging = false
self.drag_start_y = 0
self.scroll_start_offset = 0
self.scroll_velocity = 0
self.scroll_inertial = false
self.last_touch_y = 0
self.last_touch_time = os.clock()
self.row_count = 0
recalculate_scroll(self)
update_scroll(self, 0)
msg.post(".", "acquire_input_focus")
end
function update(self, dt)
if self.scroll_inertial and self.scroll_max > 0 then
self.scroll_offset = self.scroll_offset + self.scroll_velocity * dt
self.scroll_velocity = self.scroll_velocity * math.exp(-INERTIA_DAMPING * dt)
if self.scroll_offset <= 0 then
self.scroll_offset = 0
self.scroll_velocity = 0
self.scroll_inertial = false
elseif self.scroll_offset >= self.scroll_max then
self.scroll_offset = self.scroll_max
self.scroll_velocity = 0
self.scroll_inertial = false
elseif math.abs(self.scroll_velocity) < INERTIA_THRESHOLD then
self.scroll_velocity = 0
self.scroll_inertial = false
end
update_scroll(self, self.scroll_offset)
end
end
function on_input(self, action_id, action)
-- Поддержка тача и мыши
if action_id ~= hash("touch") and action_id ~= hash("click") then return false end
local x, y = action.x, action.y
local on_handle = self.handle and gui.pick_node(self.handle, x, y)
local on_viewport = self.viewport and gui.pick_node(self.viewport, x, y)
if action.pressed and (on_handle or on_viewport) then
self.is_dragging = true
self.scroll_inertial = false
self.drag_start_y = y
self.scroll_start_offset = self.scroll_offset
self.scroll_velocity = 0
self.last_touch_y = y
self.last_touch_time = os.clock()
return true
elseif self.is_dragging then
if action.released then
self.is_dragging = false
if math.abs(self.scroll_velocity) > VELOCITY_MIN then
self.scroll_inertial = true
else
self.scroll_velocity = 0
self.scroll_inertial = false
end
return true
end
local now = os.clock() -- ← os.clock()
local dt = now - self.last_touch_time
if dt > 0.001 then
self.scroll_velocity = (self.last_touch_y - y) / dt
self.last_touch_y = y
self.last_touch_time = now
end
local dy = self.drag_start_y - y
update_scroll(self, self.scroll_start_offset + dy)
return true
end
return false
end
GUI file:
script: "/main/scrollbar.gui_script"
fonts {
name: "default"
font: "/builtins/fonts/default.font"
}
nodes {
position {
x: 491.0
y: 290.0
}
size {
x: 400.0
y: 400.0
}
type: TYPE_BOX
id: "root_scrool"
inherit_alpha: true
size_mode: SIZE_MODE_AUTO
visible: false
}
nodes {
position {
x: 155.0
y: -169.0
}
size {
x: 30.0
y: 350.0
}
color {
x: 0.4
y: 0.4
z: 0.4
}
type: TYPE_BOX
id: "scrollbar"
pivot: PIVOT_SW
parent: "root_scrool"
inherit_alpha: true
}
nodes {
position {
x: -252.40244
y: -168.60919
}
size {
x: 400.0
y: 350.0
}
color {
x: 0.6
y: 0.0
z: 0.0
}
type: TYPE_BOX
id: "viewport"
pivot: PIVOT_SW
parent: "root_scrool"
inherit_alpha: true
clipping_mode: CLIPPING_MODE_STENCIL
size_mode: SIZE_MODE_AUTO
}
nodes {
position {
x: 10.84433
y: 346.75378
}
size {
x: 350.0
y: 600.0
}
color {
x: 0.2
y: 0.0
z: 0.2
}
type: TYPE_BOX
id: "content"
pivot: PIVOT_NW
parent: "viewport"
inherit_alpha: true
}
nodes {
position {
x: 6.396591
y: -4.561554
}
size {
x: 200.0
y: 100.0
}
type: TYPE_TEXT
text: "<text>"
font: "default"
id: "text"
pivot: PIVOT_NW
parent: "content"
inherit_alpha: true
}
nodes {
position {
x: 155.0
y: -169.0
}
size {
x: 30.0
y: 30.0
}
type: TYPE_BOX
id: "handle"
pivot: PIVOT_SW
parent: "root_scrool"
inherit_alpha: true
size_mode: SIZE_MODE_AUTO
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT