forked from Mirrors/apostrophe
85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
from gi.repository import Gtk
|
|
|
|
|
|
class Scroller:
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.smooth_scroll_starttime = 0
|
|
self.smooth_scroll_endtime = 0
|
|
self.smooth_scroll_acttarget = 0
|
|
self.smooth_scroll_data = {
|
|
'target_pos': -1,
|
|
'source_pos': -1,
|
|
'duration': 0
|
|
}
|
|
self.smooth_scroll_tickid = -1
|
|
|
|
def scroll_to(self, text_view, mark=None, center=False):
|
|
"""Scrolls if needed to ensure mark is visible.
|
|
|
|
If mark is unspecified, the cursor is used."""
|
|
|
|
margin = 80
|
|
scrolled_window = text_view.get_ancestor(Gtk.ScrolledWindow.__gtype__)
|
|
va = scrolled_window.get_vadjustment()
|
|
if va.props.page_size < margin * 2:
|
|
return
|
|
|
|
text_buffer = text_view.get_buffer()
|
|
if mark:
|
|
ins_it = text_buffer.get_iter_at_mark(mark)
|
|
else:
|
|
ins_it = text_buffer.get_iter_at_mark(text_buffer.get_insert())
|
|
loc_rect = text_view.get_iter_location(ins_it)
|
|
|
|
pos_y = loc_rect.y + loc_rect.height + text_view.props.top_margin
|
|
pos = pos_y - va.props.value
|
|
target_pos = -1
|
|
if center:
|
|
if pos != (va.props.page_size * 0.5):
|
|
target_pos = pos_y - (va.props.page_size * 0.5)
|
|
elif pos > va.props.page_size - margin:
|
|
target_pos = pos_y - va.props.page_size + margin
|
|
elif pos < margin:
|
|
target_pos = pos_y - margin
|
|
self.smooth_scroll_data = {
|
|
'target_pos': target_pos,
|
|
'source_pos': va.props.value,
|
|
'duration': 2000
|
|
}
|
|
if self.smooth_scroll_tickid == -1:
|
|
self.smooth_scroll_tickid = scrolled_window.add_tick_callback(self.on_tick)
|
|
|
|
def on_tick(self, widget, frame_clock, _data=None):
|
|
if self.smooth_scroll_data['target_pos'] == -1:
|
|
return True
|
|
|
|
def ease_out_cubic(value):
|
|
return pow(value - 1, 3) + 1
|
|
|
|
now = frame_clock.get_frame_time()
|
|
if self.smooth_scroll_acttarget != self.smooth_scroll_data['target_pos']:
|
|
self.smooth_scroll_starttime = now
|
|
self.smooth_scroll_endtime = now + self.smooth_scroll_data['duration'] * 100
|
|
self.smooth_scroll_acttarget = self.smooth_scroll_data['target_pos']
|
|
|
|
if now < self.smooth_scroll_endtime:
|
|
time = float(now - self.smooth_scroll_starttime) / float(
|
|
self.smooth_scroll_endtime - self.smooth_scroll_starttime)
|
|
else:
|
|
time = 1
|
|
pos = self.smooth_scroll_data['source_pos'] \
|
|
+ (time * (self.smooth_scroll_data['target_pos']
|
|
- self.smooth_scroll_data['source_pos']))
|
|
widget.get_vadjustment().props.value = pos
|
|
self.smooth_scroll_data['target_pos'] = -1
|
|
return True
|
|
|
|
time = ease_out_cubic(time)
|
|
pos = self.smooth_scroll_data['source_pos'] \
|
|
+ (time * (self.smooth_scroll_data['target_pos']
|
|
- self.smooth_scroll_data['source_pos']))
|
|
widget.get_vadjustment().props.value = pos
|
|
return True
|