forked from Mirrors/apostrophe
Remove tick callback after scrolling is done
Adding a tick callback starts updating the frame clock:
d1cdb9b5cf/gtk/gtkwidget.c (L3629)
Gdk's documentation implies that this is taxing, as it continuously
requests a new frame (which makes sense, this API is for animations):
https://developer.gnome.org/gdk3/stable/GdkFrameClock.html#gdk-frame-clock-begin-updating
Most likely fixes #75
ft.font-size^2
parent
6688eb259e
commit
372d2c8a65
|
@ -0,0 +1,48 @@
|
||||||
|
class Scroller:
|
||||||
|
def __init__(self, scrolled_window, target_pos, source_pos, duration=2000):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.scrolled_window = scrolled_window
|
||||||
|
self.target_pos = target_pos
|
||||||
|
self.source_pos = source_pos
|
||||||
|
self.duration = duration
|
||||||
|
|
||||||
|
self.is_started = False
|
||||||
|
self.start_time = 0
|
||||||
|
self.end_time = 0
|
||||||
|
|
||||||
|
self.tick_callback_id = 0
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.tick_callback_id = self.scrolled_window.add_tick_callback(self.on_tick, self)
|
||||||
|
|
||||||
|
def end(self):
|
||||||
|
self.is_started = False
|
||||||
|
self.scrolled_window.remove_tick_callback(self.tick_callback_id)
|
||||||
|
|
||||||
|
def do_start(self, time):
|
||||||
|
self.is_started = True
|
||||||
|
self.start_time = time
|
||||||
|
self.end_time = time + self.duration * 100
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def on_tick(widget, frame_clock, scroller):
|
||||||
|
def ease_out_cubic(value):
|
||||||
|
return pow(value - 1, 3) + 1
|
||||||
|
|
||||||
|
now = frame_clock.get_frame_time()
|
||||||
|
if not scroller.is_started:
|
||||||
|
scroller.do_start(now)
|
||||||
|
|
||||||
|
if now < scroller.end_time:
|
||||||
|
time = float(now - scroller.start_time) / float(scroller.end_time - scroller.start_time)
|
||||||
|
else:
|
||||||
|
time = 1
|
||||||
|
scroller.end()
|
||||||
|
|
||||||
|
time = ease_out_cubic(time)
|
||||||
|
pos = scroller.source_pos + (time * (scroller.target_pos - scroller.source_pos))
|
||||||
|
widget.get_vadjustment().props.value = pos
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,8 @@ from uberwriter.inline_preview import InlinePreview
|
||||||
from uberwriter.text_view_format_inserter import FormatInserter
|
from uberwriter.text_view_format_inserter import FormatInserter
|
||||||
from uberwriter.text_view_markup_handler import MarkupHandler
|
from uberwriter.text_view_markup_handler import MarkupHandler
|
||||||
from uberwriter.text_view_undo_redo_handler import UndoRedoHandler
|
from uberwriter.text_view_undo_redo_handler import UndoRedoHandler
|
||||||
from uberwriter.text_view_drag_drop_handler import DragDropHandler, TARGET_URI, \
|
from uberwriter.text_view_drag_drop_handler import DragDropHandler, TARGET_URI, TARGET_TEXT
|
||||||
TARGET_TEXT
|
from uberwriter.scroller import Scroller
|
||||||
from uberwriter.text_view_scroller import Scroller
|
|
||||||
|
|
||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
from gi.repository import Gtk, Gdk, GObject
|
from gi.repository import Gtk, Gdk, GObject
|
||||||
|
@ -81,7 +80,7 @@ class TextView(Gtk.TextView):
|
||||||
self.drag_drop = DragDropHandler(self, TARGET_URI, TARGET_TEXT)
|
self.drag_drop = DragDropHandler(self, TARGET_URI, TARGET_TEXT)
|
||||||
|
|
||||||
# Scrolling
|
# Scrolling
|
||||||
self.scroller = Scroller()
|
self.scroller = None
|
||||||
self.get_buffer().connect('mark-set', self.on_mark_set)
|
self.get_buffer().connect('mark-set', self.on_mark_set)
|
||||||
|
|
||||||
# Focus mode
|
# Focus mode
|
||||||
|
@ -165,7 +164,35 @@ class TextView(Gtk.TextView):
|
||||||
|
|
||||||
If mark is unspecified, the cursor is used."""
|
If mark is unspecified, the cursor is used."""
|
||||||
|
|
||||||
self.scroller.scroll_to(self, mark, self.focus_mode)
|
margin = 80
|
||||||
|
scrolled_window = self.get_ancestor(Gtk.ScrolledWindow.__gtype__)
|
||||||
|
va = scrolled_window.get_vadjustment()
|
||||||
|
if va.props.page_size < margin * 2:
|
||||||
|
return
|
||||||
|
|
||||||
|
text_buffer = self.get_buffer()
|
||||||
|
if mark:
|
||||||
|
mark_iter = text_buffer.get_iter_at_mark(mark)
|
||||||
|
else:
|
||||||
|
mark_iter = text_buffer.get_iter_at_mark(text_buffer.get_insert())
|
||||||
|
mark_rect = self.get_iter_location(mark_iter)
|
||||||
|
|
||||||
|
pos_y = mark_rect.y + mark_rect.height + self.props.top_margin
|
||||||
|
pos = pos_y - va.props.value
|
||||||
|
target_pos = None
|
||||||
|
if self.focus_mode:
|
||||||
|
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
|
||||||
|
|
||||||
|
if self.scroller and self.scroller.is_started:
|
||||||
|
self.scroller.end()
|
||||||
|
if target_pos:
|
||||||
|
self.scroller = Scroller(scrolled_window, target_pos, va.props.value)
|
||||||
|
self.scroller.start()
|
||||||
|
|
||||||
def on_mark_set(self, _text_buffer, _location, mark, _data=None):
|
def on_mark_set(self, _text_buffer, _location, mark, _data=None):
|
||||||
if mark.get_name() == 'insert':
|
if mark.get_name() == 'insert':
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
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
|
|
Loading…
Reference in New Issue