mirror of
https://github.com/minetest/minetest.git
synced 2025-03-06 20:48:40 +01:00
Smooth scrolling (#14562)
This commit is contained in:
parent
05d5dc4cec
commit
73dbd2f0ab
5 changed files with 105 additions and 44 deletions
|
@ -774,9 +774,9 @@ bool GUIEditBox::processMouse(const SEvent &event)
|
||||||
}
|
}
|
||||||
case EMIE_MOUSE_WHEEL:
|
case EMIE_MOUSE_WHEEL:
|
||||||
if (m_vscrollbar && m_vscrollbar->isVisible()) {
|
if (m_vscrollbar && m_vscrollbar->isVisible()) {
|
||||||
s32 pos = m_vscrollbar->getPos();
|
s32 pos = m_vscrollbar->getTargetPos();
|
||||||
s32 step = m_vscrollbar->getSmallStep();
|
s32 step = m_vscrollbar->getSmallStep();
|
||||||
m_vscrollbar->setPos(pos - event.MouseInput.Wheel * step);
|
m_vscrollbar->setPosInterpolated(pos - event.MouseInput.Wheel * step);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1084,7 +1084,7 @@ bool GUIHyperText::OnEvent(const SEvent &event)
|
||||||
checkHover(event.MouseInput.X, event.MouseInput.Y);
|
checkHover(event.MouseInput.X, event.MouseInput.Y);
|
||||||
|
|
||||||
if (event.MouseInput.Event == EMIE_MOUSE_WHEEL && m_vscrollbar->isVisible()) {
|
if (event.MouseInput.Event == EMIE_MOUSE_WHEEL && m_vscrollbar->isVisible()) {
|
||||||
m_vscrollbar->setPos(m_vscrollbar->getPos() -
|
m_vscrollbar->setPosInterpolated(m_vscrollbar->getTargetPos() -
|
||||||
event.MouseInput.Wheel * m_vscrollbar->getSmallStep());
|
event.MouseInput.Wheel * m_vscrollbar->getSmallStep());
|
||||||
m_text_scrollpos.Y = -m_vscrollbar->getPos();
|
m_text_scrollpos.Y = -m_vscrollbar->getPos();
|
||||||
m_drawer.draw(m_display_text_rect, m_text_scrollpos);
|
m_drawer.draw(m_display_text_rect, m_text_scrollpos);
|
||||||
|
|
|
@ -12,6 +12,7 @@ the arrow buttons where there is insufficient space.
|
||||||
|
|
||||||
#include "guiScrollBar.h"
|
#include "guiScrollBar.h"
|
||||||
#include "guiButton.h"
|
#include "guiButton.h"
|
||||||
|
#include "porting.h"
|
||||||
#include <IGUISkin.h>
|
#include <IGUISkin.h>
|
||||||
|
|
||||||
GUIScrollBar::GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s32 id,
|
GUIScrollBar::GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s32 id,
|
||||||
|
@ -38,40 +39,32 @@ bool GUIScrollBar::OnEvent(const SEvent &event)
|
||||||
switch (event.EventType) {
|
switch (event.EventType) {
|
||||||
case EET_KEY_INPUT_EVENT:
|
case EET_KEY_INPUT_EVENT:
|
||||||
if (event.KeyInput.PressedDown) {
|
if (event.KeyInput.PressedDown) {
|
||||||
const s32 old_pos = scroll_pos;
|
const s32 old_pos = getTargetPos();
|
||||||
bool absorb = true;
|
bool absorb = true;
|
||||||
switch (event.KeyInput.Key) {
|
switch (event.KeyInput.Key) {
|
||||||
case KEY_LEFT:
|
case KEY_LEFT:
|
||||||
case KEY_UP:
|
case KEY_UP:
|
||||||
setPos(scroll_pos - small_step);
|
setPosInterpolated(old_pos - small_step);
|
||||||
break;
|
break;
|
||||||
case KEY_RIGHT:
|
case KEY_RIGHT:
|
||||||
case KEY_DOWN:
|
case KEY_DOWN:
|
||||||
setPos(scroll_pos + small_step);
|
setPosInterpolated(old_pos + small_step);
|
||||||
break;
|
break;
|
||||||
case KEY_HOME:
|
case KEY_HOME:
|
||||||
setPos(min_pos);
|
setPosInterpolated(min_pos);
|
||||||
break;
|
break;
|
||||||
case KEY_PRIOR:
|
case KEY_PRIOR:
|
||||||
setPos(scroll_pos - large_step);
|
setPosInterpolated(old_pos - large_step);
|
||||||
break;
|
break;
|
||||||
case KEY_END:
|
case KEY_END:
|
||||||
setPos(max_pos);
|
setPosInterpolated(max_pos);
|
||||||
break;
|
break;
|
||||||
case KEY_NEXT:
|
case KEY_NEXT:
|
||||||
setPos(scroll_pos + large_step);
|
setPosInterpolated(old_pos + large_step);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
absorb = false;
|
absorb = false;
|
||||||
}
|
}
|
||||||
if (scroll_pos != old_pos) {
|
|
||||||
SEvent e;
|
|
||||||
e.EventType = EET_GUI_EVENT;
|
|
||||||
e.GUIEvent.Caller = this;
|
|
||||||
e.GUIEvent.Element = nullptr;
|
|
||||||
e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
|
|
||||||
Parent->OnEvent(e);
|
|
||||||
}
|
|
||||||
if (absorb)
|
if (absorb)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -79,16 +72,9 @@ bool GUIScrollBar::OnEvent(const SEvent &event)
|
||||||
case EET_GUI_EVENT:
|
case EET_GUI_EVENT:
|
||||||
if (event.GUIEvent.EventType == EGET_BUTTON_CLICKED) {
|
if (event.GUIEvent.EventType == EGET_BUTTON_CLICKED) {
|
||||||
if (event.GUIEvent.Caller == up_button)
|
if (event.GUIEvent.Caller == up_button)
|
||||||
setPos(scroll_pos - small_step);
|
setPosInterpolated(getTargetPos() - small_step);
|
||||||
else if (event.GUIEvent.Caller == down_button)
|
else if (event.GUIEvent.Caller == down_button)
|
||||||
setPos(scroll_pos + small_step);
|
setPosInterpolated(getTargetPos() + small_step);
|
||||||
|
|
||||||
SEvent e;
|
|
||||||
e.EventType = EET_GUI_EVENT;
|
|
||||||
e.GUIEvent.Caller = this;
|
|
||||||
e.GUIEvent.Element = nullptr;
|
|
||||||
e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
|
|
||||||
Parent->OnEvent(e);
|
|
||||||
return true;
|
return true;
|
||||||
} else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
|
} else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
|
||||||
if (event.GUIEvent.Caller == this)
|
if (event.GUIEvent.Caller == this)
|
||||||
|
@ -102,14 +88,7 @@ bool GUIScrollBar::OnEvent(const SEvent &event)
|
||||||
if (Environment->hasFocus(this)) {
|
if (Environment->hasFocus(this)) {
|
||||||
s8 d = event.MouseInput.Wheel < 0 ? -1 : 1;
|
s8 d = event.MouseInput.Wheel < 0 ? -1 : 1;
|
||||||
s8 h = is_horizontal ? 1 : -1;
|
s8 h = is_horizontal ? 1 : -1;
|
||||||
setPos(getPos() + (d * small_step * h));
|
setPosInterpolated(getTargetPos() + (d * small_step * h));
|
||||||
|
|
||||||
SEvent e;
|
|
||||||
e.EventType = EET_GUI_EVENT;
|
|
||||||
e.GUIEvent.Caller = this;
|
|
||||||
e.GUIEvent.Element = nullptr;
|
|
||||||
e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
|
|
||||||
Parent->OnEvent(e);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -228,11 +207,45 @@ void GUIScrollBar::draw()
|
||||||
IGUIElement::draw();
|
IGUIElement::draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline s32 interpolate_scroll(s32 from, s32 to, f32 amount)
|
||||||
|
{
|
||||||
|
s32 step = core::round32((to - from) * core::clamp(amount, 0.001f, 1.0f));
|
||||||
|
if (step == 0)
|
||||||
|
return to;
|
||||||
|
return from + step;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUIScrollBar::interpolatePos()
|
||||||
|
{
|
||||||
|
if (target_pos.has_value()) {
|
||||||
|
// Adjust to match 60 FPS. This also means that interpolation is
|
||||||
|
// effectively disabled at <= 30 FPS.
|
||||||
|
f32 amount = 0.5f * (last_delta_ms / 16.667f);
|
||||||
|
setPosRaw(interpolate_scroll(scroll_pos, *target_pos, amount));
|
||||||
|
if (scroll_pos == target_pos)
|
||||||
|
target_pos = std::nullopt;
|
||||||
|
|
||||||
|
SEvent e;
|
||||||
|
e.EventType = EET_GUI_EVENT;
|
||||||
|
e.GUIEvent.Caller = this;
|
||||||
|
e.GUIEvent.Element = nullptr;
|
||||||
|
e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
|
||||||
|
Parent->OnEvent(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUIScrollBar::OnPostRender(u32 time_ms)
|
||||||
|
{
|
||||||
|
last_delta_ms = porting::getDeltaMs(last_time_ms, time_ms);
|
||||||
|
last_time_ms = time_ms;
|
||||||
|
interpolatePos();
|
||||||
|
}
|
||||||
|
|
||||||
void GUIScrollBar::updateAbsolutePosition()
|
void GUIScrollBar::updateAbsolutePosition()
|
||||||
{
|
{
|
||||||
IGUIElement::updateAbsolutePosition();
|
IGUIElement::updateAbsolutePosition();
|
||||||
refreshControls();
|
refreshControls();
|
||||||
setPos(scroll_pos);
|
updatePos();
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 GUIScrollBar::getPosFromMousePos(const core::position2di &pos) const
|
s32 GUIScrollBar::getPosFromMousePos(const core::position2di &pos) const
|
||||||
|
@ -250,7 +263,12 @@ s32 GUIScrollBar::getPosFromMousePos(const core::position2di &pos) const
|
||||||
return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range() + 0.5f) + min_pos : 0;
|
return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range() + 0.5f) + min_pos : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUIScrollBar::setPos(const s32 &pos)
|
void GUIScrollBar::updatePos()
|
||||||
|
{
|
||||||
|
setPosRaw(scroll_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUIScrollBar::setPosRaw(const s32 &pos)
|
||||||
{
|
{
|
||||||
s32 thumb_area = 0;
|
s32 thumb_area = 0;
|
||||||
s32 thumb_min = 0;
|
s32 thumb_min = 0;
|
||||||
|
@ -276,6 +294,23 @@ void GUIScrollBar::setPos(const s32 &pos)
|
||||||
border_size;
|
border_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GUIScrollBar::setPos(const s32 &pos)
|
||||||
|
{
|
||||||
|
setPosRaw(pos);
|
||||||
|
target_pos = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUIScrollBar::setPosInterpolated(const s32 &pos)
|
||||||
|
{
|
||||||
|
s32 clamped = core::s32_clamp(pos, min_pos, max_pos);
|
||||||
|
if (scroll_pos != clamped) {
|
||||||
|
target_pos = clamped;
|
||||||
|
interpolatePos();
|
||||||
|
} else {
|
||||||
|
target_pos = std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GUIScrollBar::setSmallStep(const s32 &step)
|
void GUIScrollBar::setSmallStep(const s32 &step)
|
||||||
{
|
{
|
||||||
small_step = step > 0 ? step : 10;
|
small_step = step > 0 ? step : 10;
|
||||||
|
@ -295,7 +330,7 @@ void GUIScrollBar::setMax(const s32 &max)
|
||||||
bool enable = core::isnotzero(range());
|
bool enable = core::isnotzero(range());
|
||||||
up_button->setEnabled(enable);
|
up_button->setEnabled(enable);
|
||||||
down_button->setEnabled(enable);
|
down_button->setEnabled(enable);
|
||||||
setPos(scroll_pos);
|
updatePos();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUIScrollBar::setMin(const s32 &min)
|
void GUIScrollBar::setMin(const s32 &min)
|
||||||
|
@ -307,13 +342,13 @@ void GUIScrollBar::setMin(const s32 &min)
|
||||||
bool enable = core::isnotzero(range());
|
bool enable = core::isnotzero(range());
|
||||||
up_button->setEnabled(enable);
|
up_button->setEnabled(enable);
|
||||||
down_button->setEnabled(enable);
|
down_button->setEnabled(enable);
|
||||||
setPos(scroll_pos);
|
updatePos();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUIScrollBar::setPageSize(const s32 &size)
|
void GUIScrollBar::setPageSize(const s32 &size)
|
||||||
{
|
{
|
||||||
page_size = size;
|
page_size = size;
|
||||||
setPos(scroll_pos);
|
updatePos();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUIScrollBar::setArrowsVisible(ArrowVisibility visible)
|
void GUIScrollBar::setArrowsVisible(ArrowVisibility visible)
|
||||||
|
@ -327,6 +362,15 @@ s32 GUIScrollBar::getPos() const
|
||||||
return scroll_pos;
|
return scroll_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s32 GUIScrollBar::getTargetPos() const
|
||||||
|
{
|
||||||
|
if (target_pos.has_value()) {
|
||||||
|
s32 clamped = core::s32_clamp(*target_pos, min_pos, max_pos);
|
||||||
|
return clamped;
|
||||||
|
}
|
||||||
|
return scroll_pos;
|
||||||
|
}
|
||||||
|
|
||||||
void GUIScrollBar::refreshControls()
|
void GUIScrollBar::refreshControls()
|
||||||
{
|
{
|
||||||
IGUISkin *skin = Environment->getSkin();
|
IGUISkin *skin = Environment->getSkin();
|
||||||
|
|
|
@ -13,6 +13,7 @@ the arrow buttons where there is insufficient space.
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "irrlichttypes_extrabloated.h"
|
#include "irrlichttypes_extrabloated.h"
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
class ISimpleTextureSource;
|
class ISimpleTextureSource;
|
||||||
|
|
||||||
|
@ -33,21 +34,30 @@ public:
|
||||||
DEFAULT
|
DEFAULT
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual void draw();
|
virtual void draw() override;
|
||||||
virtual void updateAbsolutePosition();
|
virtual void updateAbsolutePosition() override;
|
||||||
virtual bool OnEvent(const SEvent &event);
|
virtual bool OnEvent(const SEvent &event) override;
|
||||||
|
virtual void OnPostRender(u32 time_ms) override;
|
||||||
|
|
||||||
s32 getMax() const { return max_pos; }
|
s32 getMax() const { return max_pos; }
|
||||||
s32 getMin() const { return min_pos; }
|
s32 getMin() const { return min_pos; }
|
||||||
s32 getLargeStep() const { return large_step; }
|
s32 getLargeStep() const { return large_step; }
|
||||||
s32 getSmallStep() const { return small_step; }
|
s32 getSmallStep() const { return small_step; }
|
||||||
s32 getPos() const;
|
s32 getPos() const;
|
||||||
|
s32 getTargetPos() const;
|
||||||
|
|
||||||
void setMax(const s32 &max);
|
void setMax(const s32 &max);
|
||||||
void setMin(const s32 &min);
|
void setMin(const s32 &min);
|
||||||
void setSmallStep(const s32 &step);
|
void setSmallStep(const s32 &step);
|
||||||
void setLargeStep(const s32 &step);
|
void setLargeStep(const s32 &step);
|
||||||
|
//! Sets a position immediately, aborting any ongoing interpolation.
|
||||||
|
// setPos does not send EGET_SCROLL_BAR_CHANGED events for you.
|
||||||
void setPos(const s32 &pos);
|
void setPos(const s32 &pos);
|
||||||
|
//! Sets a target position for interpolation.
|
||||||
|
// If you want to do an interpolated addition, use
|
||||||
|
// setPosInterpolated(getTargetPos() + x).
|
||||||
|
// setPosInterpolated takes care of sending EGET_SCROLL_BAR_CHANGED events.
|
||||||
|
void setPosInterpolated(const s32 &pos);
|
||||||
void setPageSize(const s32 &size);
|
void setPageSize(const s32 &size);
|
||||||
void setArrowsVisible(ArrowVisibility visible);
|
void setArrowsVisible(ArrowVisibility visible);
|
||||||
|
|
||||||
|
@ -79,4 +89,11 @@ private:
|
||||||
video::SColor current_icon_color;
|
video::SColor current_icon_color;
|
||||||
|
|
||||||
ISimpleTextureSource *m_tsrc;
|
ISimpleTextureSource *m_tsrc;
|
||||||
|
|
||||||
|
void setPosRaw(const s32 &pos);
|
||||||
|
void updatePos();
|
||||||
|
std::optional<s32> target_pos;
|
||||||
|
u32 last_time_ms = 0;
|
||||||
|
u32 last_delta_ms = 17; // assume 60 FPS
|
||||||
|
void interpolatePos();
|
||||||
};
|
};
|
||||||
|
|
|
@ -869,7 +869,7 @@ bool GUITable::OnEvent(const SEvent &event)
|
||||||
core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
|
core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
|
||||||
|
|
||||||
if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
|
if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
|
||||||
m_scrollbar->setPos(m_scrollbar->getPos() +
|
m_scrollbar->setPosInterpolated(m_scrollbar->getTargetPos() +
|
||||||
(event.MouseInput.Wheel < 0 ? -3 : 3) *
|
(event.MouseInput.Wheel < 0 ? -3 : 3) *
|
||||||
- (s32) m_rowheight / 2);
|
- (s32) m_rowheight / 2);
|
||||||
return true;
|
return true;
|
||||||
|
|
Loading…
Add table
Reference in a new issue