rxbag: adding back in double-buffering with Cairo
This commit is contained in:
parent
98c5e9819c
commit
f640aa9c2c
|
@ -59,6 +59,7 @@ def get_end_stamp(bag):
|
|||
for connection_end_stamp in [index[-1].time for index in bag._connection_indexes.values()]:
|
||||
if not end_stamp or connection_end_stamp < end_stamp:
|
||||
end_stamp = connection_end_stamp
|
||||
|
||||
return end_stamp
|
||||
|
||||
def get_topics_by_datatype(bag):
|
||||
|
|
|
@ -37,11 +37,11 @@ import roslib; roslib.load_manifest(PKG)
|
|||
|
||||
import wx
|
||||
|
||||
from util.layer import Layer
|
||||
from util.layer import TransparentLayer
|
||||
|
||||
class PlayheadLayer(Layer):
|
||||
class PlayheadLayer(TransparentLayer):
|
||||
def __init__(self, parent, title, timeline, x, y, width, height):
|
||||
Layer.__init__(self, parent, title, x, y, width, height)
|
||||
TransparentLayer.__init__(self, parent, title, x, y, width, height)
|
||||
|
||||
self.timeline = timeline
|
||||
|
||||
|
@ -56,7 +56,7 @@ class PlayheadLayer(Layer):
|
|||
self.update_position()
|
||||
|
||||
def paint(self, dc):
|
||||
Layer.paint(self, dc)
|
||||
TransparentLayer.paint(self, dc)
|
||||
|
||||
if not self.timeline.playhead:
|
||||
return
|
||||
|
|
|
@ -64,12 +64,11 @@ class IndexCacheThread(threading.Thread):
|
|||
def __init__(self, timeline, period=2.0):
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
self.setDaemon(True)
|
||||
|
||||
self.timeline = timeline
|
||||
self.period = period
|
||||
self.stop_flag = False
|
||||
|
||||
|
||||
self.setDaemon(True)
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
|
@ -557,6 +556,7 @@ class Timeline(Layer):
|
|||
self.rendered_topics.clear()
|
||||
|
||||
self.invalidate()
|
||||
self.parent.playhead.invalidate()
|
||||
|
||||
def set_renderer_active(self, topic, active):
|
||||
if active:
|
||||
|
@ -569,6 +569,7 @@ class Timeline(Layer):
|
|||
self.rendered_topics.remove(topic)
|
||||
|
||||
self.invalidate()
|
||||
self.parent.playhead.invalidate()
|
||||
|
||||
###
|
||||
|
||||
|
@ -673,6 +674,8 @@ class Timeline(Layer):
|
|||
self._stamp_left += dstamp
|
||||
self._stamp_right += dstamp
|
||||
|
||||
self.invalidate()
|
||||
|
||||
elif playhead_secs < self._stamp_left:
|
||||
dstamp = self._stamp_left - playhead_secs + (self._stamp_right - self._stamp_left) * 0.75
|
||||
if dstamp > self._stamp_left - self.start_stamp.to_sec():
|
||||
|
@ -681,24 +684,22 @@ class Timeline(Layer):
|
|||
self._stamp_left -= dstamp
|
||||
self._stamp_right -= dstamp
|
||||
|
||||
self.invalidate()
|
||||
|
||||
self.parent.playhead.update_position()
|
||||
|
||||
self.parent.playhead.invalidate()
|
||||
self.parent.status.invalidate()
|
||||
|
||||
for topic in self.topics:
|
||||
bag, entry = self.get_entry(self._playhead, topic)
|
||||
if entry:
|
||||
topic_pos = (bag, entry.position)
|
||||
if topic in self._playhead_positions and self._playhead_positions[topic] == topic_pos:
|
||||
continue
|
||||
self._playhead_positions[topic] = topic_pos
|
||||
self._playhead_positions[topic] = (bag, entry.position)
|
||||
else:
|
||||
if topic not in self._playhead_positions:
|
||||
continue
|
||||
del self._playhead_positions[topic]
|
||||
self._playhead_positions[topic] = None, None
|
||||
|
||||
self._update_message_view_for_topic(topic)
|
||||
|
||||
self.invalidate()
|
||||
|
||||
### Rendering
|
||||
|
||||
def on_size(self, event):
|
||||
|
@ -946,6 +947,7 @@ class Timeline(Layer):
|
|||
"""
|
||||
Draw boxes to show message regions on timelines.
|
||||
"""
|
||||
|
||||
x, y, w, h = self.history_bounds[topic]
|
||||
|
||||
msg_y = y + 1
|
||||
|
@ -983,6 +985,7 @@ class Timeline(Layer):
|
|||
dc.rectangle(self.history_left, self.history_top, self.history_width, self.history_height)
|
||||
dc.clip()
|
||||
|
||||
# Draw stamps
|
||||
dc.set_line_width(1)
|
||||
dc.set_source_rgb(*datatype_color)
|
||||
for (stamp_start, stamp_end) in self._find_regions(all_stamps[:end_index], self.map_dx_to_dstamp(self.default_msg_combine_px)):
|
||||
|
@ -1211,12 +1214,13 @@ class Timeline(Layer):
|
|||
def _update_message_view_for_topic(self, topic):
|
||||
if not self._playhead_positions or not topic in self.listeners:
|
||||
return
|
||||
|
||||
|
||||
bag, playhead_position = self._playhead_positions[topic]
|
||||
if playhead_position is None:
|
||||
return
|
||||
|
||||
msg_data = self._get_message(bag, topic, playhead_position)
|
||||
msg_data = None
|
||||
else:
|
||||
msg_data = self._get_message(bag, topic, playhead_position)
|
||||
|
||||
if msg_data:
|
||||
for listener in self.listeners[topic]:
|
||||
try:
|
||||
|
|
|
@ -137,10 +137,10 @@ class TimelinePopupMenu(wx.Menu):
|
|||
self.hide_thumbnails_menu = wx.MenuItem(self.thumbnail_menu, wx.NewId(), 'Hide All')
|
||||
self.thumbnail_menu.AppendItem(self.hide_thumbnails_menu)
|
||||
self.thumbnail_menu.Bind(wx.EVT_MENU, lambda e: self.timeline.set_renderers_active(False), id=self.hide_thumbnails_menu.GetId())
|
||||
|
||||
|
||||
# ---
|
||||
self.thumbnail_menu.AppendSeparator()
|
||||
|
||||
|
||||
# Thumbnails... / topic
|
||||
for topic, renderer in renderers:
|
||||
renderer_item = self.TimelineRendererMenuItem(self.thumbnail_menu, wx.NewId(), topic.lstrip('/'), topic, renderer, self.timeline)
|
||||
|
|
|
@ -47,12 +47,21 @@ class Layer:
|
|||
self._height = height
|
||||
|
||||
self.parent = parent
|
||||
self.title = title
|
||||
self.title = title
|
||||
|
||||
self.self_paint = False
|
||||
|
||||
if not self.self_paint:
|
||||
self.bitmap = wx.EmptyBitmap(self._width, self._height)
|
||||
|
||||
self._last_repaint = None
|
||||
self._dirty = True
|
||||
|
||||
# Interface to implement in derived classes
|
||||
|
||||
def check_dirty(self): pass
|
||||
def paint(self, dc): pass
|
||||
|
||||
def on_mouse_move(self, event): pass
|
||||
def on_mousewheel(self, event): pass
|
||||
def on_left_down(self, event): pass
|
||||
|
@ -77,13 +86,17 @@ class Layer:
|
|||
return
|
||||
|
||||
self._width, self._height = width, height
|
||||
|
||||
|
||||
if not self.self_paint:
|
||||
self.bitmap = wx.EmptyBitmap(self._width, self._height)
|
||||
|
||||
self.invalidate()
|
||||
|
||||
def contains(self, x, y):
|
||||
return x >= self._x and y >= self._y and x <= self.right and y <= self.bottom
|
||||
|
||||
def invalidate(self):
|
||||
self._dirty = True
|
||||
self.parent.Refresh()
|
||||
|
||||
@property
|
||||
|
@ -106,8 +119,67 @@ class Layer:
|
|||
|
||||
# Painting
|
||||
|
||||
def paint_to_bitmap(self):
|
||||
mem_dc = wx.MemoryDC()
|
||||
mem_dc.SelectObject(self.bitmap)
|
||||
|
||||
self.clear_background(mem_dc)
|
||||
|
||||
cairo_dc = wx.lib.wxcairo.ContextFromDC(mem_dc)
|
||||
self.paint(cairo_dc)
|
||||
mem_dc.SelectObject(wx.NullBitmap)
|
||||
|
||||
self._dirty = False
|
||||
|
||||
def clear_background(self, dc):
|
||||
dc.SetBackground(wx.WHITE_BRUSH)
|
||||
dc.Clear()
|
||||
|
||||
def draw(self, dc):
|
||||
self.paint(dc)
|
||||
if not self.self_paint:
|
||||
dc.DrawBitmap(self.bitmap, self._x, self._y)
|
||||
|
||||
class TransparentLayer(Layer):
|
||||
TRANSPARENT_COLOR = wx.Colour(5, 5, 5)
|
||||
|
||||
def __init__(self, parent, title, x, y, width, height):
|
||||
Layer.__init__(self, parent, title, x, y, width, height)
|
||||
|
||||
self._transparent_bitmap = None
|
||||
|
||||
self.bitmap.SetMask(wx.Mask(self.bitmap, self.TRANSPARENT_COLOR))
|
||||
|
||||
def paint_to_bitmap(self):
|
||||
Layer.paint_to_bitmap(self)
|
||||
|
||||
self._transparent_bitmap = self._make_transparent(self.bitmap)
|
||||
|
||||
def draw(self, dc):
|
||||
if self._transparent_bitmap:
|
||||
dc.DrawBitmap(self._transparent_bitmap, self.x, self.y, useMask=True)
|
||||
|
||||
def paint(self, dc):
|
||||
pass
|
||||
|
||||
def clear_background(self, dc):
|
||||
dc.SetBackground(wx.Brush(self.TRANSPARENT_COLOR, wx.SOLID))
|
||||
dc.Clear()
|
||||
|
||||
## A bug in wxPython with transparent bitmaps: need to convert to/from Image to enable transparency.
|
||||
## (see http://aspn.activestate.com/ASPN/Mail/Message/wxpython-users/3668628)
|
||||
def _make_transparent(self, bitmap):
|
||||
image = bitmap.ConvertToImage()
|
||||
if not image.HasAlpha():
|
||||
image.InitAlpha()
|
||||
|
||||
w, h = image.GetWidth(), image.GetHeight()
|
||||
for y in xrange(h):
|
||||
for x in xrange(w):
|
||||
pix = wx.Colour(image.GetRed(x, y), image.GetGreen(x, y), image.GetBlue(x, y))
|
||||
if pix == self.TRANSPARENT_COLOR:
|
||||
image.SetAlpha(x, y, 0)
|
||||
|
||||
return image.ConvertToBitmap()
|
||||
|
||||
class LayerPanel(wx.Window):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -122,7 +194,6 @@ class LayerPanel(wx.Window):
|
|||
|
||||
self.Bind(wx.EVT_PAINT, self.on_paint)
|
||||
self.Bind(wx.EVT_SIZE, self.on_size)
|
||||
self.Bind(wx.EVT_TIMER, self.on_timer)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
|
||||
self.Bind(wx.EVT_MIDDLE_DOWN, self.on_middle_down)
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.on_right_down)
|
||||
|
@ -135,19 +206,55 @@ class LayerPanel(wx.Window):
|
|||
self.GetParent().Bind(wx.EVT_CLOSE, self.on_close)
|
||||
|
||||
@property
|
||||
def width(self): return wx.Window.GetSize(self)[0]
|
||||
def width(self): return self.Size[0]
|
||||
|
||||
@property
|
||||
def height(self): return wx.Window.GetSize(self)[1]
|
||||
def height(self): return self.Size[1]
|
||||
|
||||
# Painting events
|
||||
|
||||
def on_paint(self, event):
|
||||
if not self.bitmap:
|
||||
return
|
||||
|
||||
# Ask layers to recheck whether they're dirty or not
|
||||
for layer in self.layers:
|
||||
layer.check_dirty()
|
||||
|
||||
# Find layers that need to be repainted
|
||||
dirty_layers = [l for l in self.layers if l._dirty]
|
||||
|
||||
# If none are dirty, nothing to do
|
||||
if len(dirty_layers) == 0:
|
||||
return
|
||||
|
||||
# Repaint dirty layers
|
||||
for layer in dirty_layers:
|
||||
layer.paint_to_bitmap()
|
||||
|
||||
# Compose layers and blit to window
|
||||
self.paint()
|
||||
|
||||
def paint(self):
|
||||
pdc = wx.PaintDC(self)
|
||||
dc = wx.lib.wxcairo.ContextFromDC(pdc)
|
||||
paint_layers = [layer for layer in self.layers if not layer.self_paint]
|
||||
if len(paint_layers) == 0:
|
||||
return
|
||||
|
||||
# Compose all layers into a buffer
|
||||
bitmap_dc = wx.MemoryDC()
|
||||
bitmap_dc.SelectObject(self.bitmap)
|
||||
bitmap_dc.SetBackground(self.background_brush)
|
||||
bitmap_dc.Clear()
|
||||
for layer in paint_layers:
|
||||
layer.draw(bitmap_dc)
|
||||
bitmap_dc.SelectObject(wx.NullBitmap)
|
||||
|
||||
# Draw buffer to the window
|
||||
window_dc = wx.ClientDC(self)
|
||||
window_dc.DrawBitmap(self.bitmap, 0, 0)
|
||||
|
||||
def _paint_layers(self, wx_dc):
|
||||
dc = wx.lib.wxcairo.ContextFromDC(wx_dc)
|
||||
|
||||
dc.set_source_rgba(1, 1, 1, 1)
|
||||
dc.rectangle(0, 0, self.width, self.height)
|
||||
|
@ -159,12 +266,13 @@ class LayerPanel(wx.Window):
|
|||
layer.draw(dc)
|
||||
dc.restore()
|
||||
|
||||
def on_timer(self, event):
|
||||
self.Refresh()
|
||||
|
||||
def on_size(self, event):
|
||||
for layer in self.layers:
|
||||
layer.on_size(event)
|
||||
size = self.GetClientSize()
|
||||
if not self.bitmap or self.bitmap.GetSize() != size:
|
||||
self.bitmap = wx.EmptyBitmap(*self.GetClientSize())
|
||||
|
||||
for layer in self.layers:
|
||||
layer.on_size(event)
|
||||
|
||||
# Mouse events
|
||||
|
||||
|
|
Loading…
Reference in New Issue