def _refresh(self): # get geometry X, Y = self.scroll cw, ch = self.charSize sl, sr, st, sb = self._borders cl, rw = self.screenSize # create device context dc = MemoryDC() # select bitmap dc.SelectObject(self.bitmapBuffer) # clear buffer self._clear(dc) # draw _borders self.drawBorders(dc) # draw each lines for y, line in enumerate(self.textBuffer[Y:]): # check for last visible line if y+st >= rw-sb: break # get line number lineNumber = f'{y+Y:{sl-1}d}' # draw number for x, c in enumerate(lineNumber): # get character position u, v = x*cw, (y+st)*ch # draw character dc.DrawBitmap(self.CS.get(c, 'emp'), u, v) # draw line # x = 0 # empty line jumps straight to else case for x, c in enumerate(line[X:]+'¶'): # check for last visible character if x+sl >= cl-sr: break # check if character selected selected = self.isSelected(x+X, y+Y) # adjust display style style = 'sel' if selected else 'nrm' # get screen position u, v = (x+sl)*cw, (y+st)*ch # draw character dc.DrawBitmap(self.CS.get(c, style), u, v) # draw cursor self.drawCursor(dc) # done dc.SelectObject(NullBitmap) return
def SaveBackground(self, dc): """ Save the given dc as the new background image. @param DC dc : the dc to save """ w, h = self.GetSize() bb = self.__backgroundBitmap if (bb.GetWidth(), bb.GetHeight()) != (w, h): bb = self.__backgroundBitmap = Bitmap(w, h) mem = MemoryDC() mem.SelectObject(bb) if __version__ > "2.3.2": x, y = self.CalcUnscrolledPosition(0, 0) mem.Blit(0, 0, w, h, dc, x, y) else: mem.Blit(0, 0, w, h, dc, 0, 0) mem.SelectObject(NullBitmap)
def OnFacename(self, facename): idx = len(self.font_list) self.font_list.append(facename) w, h = self.GetTextExtentCached(idx) self.bitmaps[idx] = wx.Bitmap(w, h) dc = MemoryDC() dc.SelectObject(self.bitmaps[idx]) dc.Clear() font = self.MakeFont(facename=facename) dc.SetFont(font) dc.DrawText(facename, 0, 0) wx.CallAfter(self.Append, facename, idx) return True
def _createCharBitmap(self, name, font, foreground, background): # create device context dc = MemoryDC() # set font dc.SetFont(self._fonts[font]) # get font size _cw, _ch = dc.GetTextExtent(' ') BitmapSet = {} for c in self._CharSet: # create bitmap bitmap = Bitmap(_cw, _ch, BITMAP_SCREEN_DEPTH) # select bitmap dc.SelectObject(bitmap) # set background dc.SetBackground(self._brushes[background]) # clear bitmap dc.Clear() # set text color dc.SetTextForeground(self._colours[foreground]) # record reference BitmapSet[c] = bitmap # check alternate drawing 1 if name == 'nrm' and c == '¶': dc.DrawText(' ',0,0) continue # check alternate drawing 2 if name == 'sel' and c == ' ': dc.DrawText('·',0,0) continue # draw character dc.DrawText(c,0,0) # release bitmap dc.SelectObject(NullBitmap) # record set self._CharBitmaps[name] = BitmapSet # done return
def createBufferBitmap(self, width, height): # get geometry cw, ch = self.charSize # create bitmap buffer self.bmpBuf = Bitmap( width *cw, # bitmap width height*ch, # bitmap heigh BITMAP_SCREEN_DEPTH) # bitmap depth # create device context dc = MemoryDC() # select buffer dc.SelectObject(self.bmpBuf) # set background dc.SetBackground(self.brushes['bkgd']) # clear buffer dc.Clear() # release bitmap dc.SelectObject(NullBitmap) # hook BackgroundBitmap to bitmapBuffer handle self.Panel.BackgroundBitmap = self.bmpBuf # record geometry self.screenSize = width, height # done return
def refreshBuffer(self): # get geometry X, Y = self.scroll cw, ch = self.charSize sl, sr, st, sb = 5, 15, 5, 5 cl, rw = self.screenSize # create device context dc = MemoryDC() # select dc.SelectObject(self.bmpBuf) # set background dc.SetBackground(self.brushes['bkgd']) # and clear buffer dc.Clear() # draw margins x3, x4 = 0*cw, cl*cw x1, y1 = (sl-0.5)*cw, (st-0.5)*ch x2, y2 = (cl-sr+0.5)*cw, (rw-sb+0.5)*ch dc.SetPen(self.pens['margin']) dc.DrawLine(x1, y1, x1, y2) dc.DrawLine(x2, y1, x2, y2) dc.DrawLine(x3, y1, x4, y1) dc.DrawLine(x3, y2, x4, y2) # get block geometry c1, r1, c2, r2 = self.getBlockGeometry() # set brush dc.SetBrush(Brush(Colour(100,100,100))) dc.SetPen(self.pens['transparent']) # draw text x, y = 0, -1 for line in self.chrBuf[Y:]: y += 1 if y+st >= rw-sb: break # draw line number self.drawText(dc, f'{y+Y:4d}', 0, y+st, self.numberChars) # the line is outside of the block if y+Y < r1 or y+Y > r2: self.drawText(dc, line[X:], x+sl, y+st, self.normalChars) continue # block is inside if the line if r1 == r2: self.drawText(dc, line[X:], x+sl, y+st, self.normalChars) self.drawText(dc, line[X:c2], x+sl, y+st, self.selectedChars) self.drawText(dc, line[X:c1], x+sl, y+st, self.normalChars) continue # line at the start of the block if y+Y == r1: self.drawText(dc, line[X:]+'¶', x+sl, y+st, self.selectedChars) self.drawText(dc, line[X:c1], x+sl, y+st, self.normalChars) continue # line at the end of the block if y+Y == r2: self.drawText(dc, line[X:], x+sl, y+st, self.normalChars) self.drawText(dc, line[X:c2], x+sl, y+st, self.selectedChars) continue # line is inside the block self.drawText(dc, line[X:]+'¶', x+sl, y+st, self.selectedChars) # draw cursor x, y = self.cursor x1, y1, y2 = (x+sl)*cw, (y+st)*ch, (y+st+1)*ch-1 dc.SetPen(self.pens['cursor']) dc.DrawLine(x1, y1, x1, y2) # done dc.SelectObject(NullBitmap) return
def createCharBitmaps(self): # create device context dc = MemoryDC() # set font dc.SetFont(self.fonts['monospace']) # get font size (monospace) cw, ch = dc.GetTextExtent(' ') # record geometry self.charSize = cw, ch # NORMAL CHARACTERS self.normalChars = {} # create bitmaps for c in self.charSet: # create black and white bitmap bitmap = Bitmap(cw, ch, BITMAP_SCREEN_DEPTH) # select bitmap dc.SelectObject(bitmap) # set background dc.SetBackground(self.brushes['bkgd']) # clear bitmap dc.Clear() # set text color dc.SetTextForeground(Colour(230,230,230)) # write character dc.DrawText(c,0,0) # create reference to bitmap self.normalChars[c] = bitmap # SELECTED CHARACTERS self.selectedChars = {} # create bitmaps for c in self.charSet: # create black and white bitmap bitmap = Bitmap(cw, ch, BITMAP_SCREEN_DEPTH) # select bitmap dc.SelectObject(bitmap) # set background dc.SetBackground(Brush(Colour(70, 80, 90))) # clear bitmap dc.Clear() # set text color dc.SetTextForeground(Colour(160,160,180)) # write character dc.DrawText(c,0,0) # create reference to bitmap self.selectedChars[c] = bitmap # NUMBER CHARACTERS self.numberChars = {} # create bitmaps for c in ' 0123456789': # create black and white bitmap bitmap = Bitmap(cw, ch, BITMAP_SCREEN_DEPTH) # select bitmap dc.SelectObject(bitmap) # set background dc.SetBackground(self.brushes['bkgd']) # clear bitmap dc.Clear() # set text color dc.SetTextForeground(Colour(150,150,200)) # write character dc.DrawText(c,0,0) # create reference to bitmap self.numberChars[c] = bitmap # release bitmap dc.SelectObject(NullBitmap) # done return
def OnRefreshPanel(self, event): """ Refresh dialog box """ import time # constants backRed: int = 230 backGreen: int = 255 backBlue: int = 230 # Background color frontRed: int = 64 frontGreen: int = 0 frontBlue: int = 64 # Foreground color FADE_IN_LENGTH: int = 63 self.logger.debug(f'Enter OnRefreshPanel') # Init memory buffer tdc: MemoryDC = MemoryDC() tdc.SelectObject(Bitmap(FrameWidth, FrameHeight)) while not tdc.IsOk(): time.sleep(0.05) tdc = MemoryDC() tdc.SelectObject(Bitmap(FrameWidth, FrameHeight)) tdc.SetTextForeground(Colour(frontRed, frontGreen, frontBlue)) tdc.SetBackground( Brush(Colour(backRed, backGreen, backBlue), BRUSHSTYLE_SOLID)) tdc.Clear() font = tdc.GetFont() font.SetFamily(FONTFAMILY_MODERN) font.SetPointSize(12) tdc.SetFont(font) # Fade-in position = self.textPosition if position < FADE_IN_LENGTH: n = float(position) / float(FADE_IN_LENGTH) r = backRed - n * (backRed - frontRed) g = backGreen - n * (backGreen - frontGreen) b = backBlue - n * (backBlue - frontBlue) r = int(r) g = int(g) b = int(b) tdc.SetTextForeground(Colour(r, g, b)) # Display text for j in range(1, len(self._textToShow)): # Draw text ? if position > FADE_IN_LENGTH: y = y0 + j * dy - (position - FADE_IN_LENGTH) if -dy < y < FrameHeight: tdc.DrawText(self._textToShow[j], x0, y) else: # Draw initial screen with fade in y = y0 + j * dy if -dy < y < FrameHeight: tdc.DrawText(self._textToShow[j], x0, y) # Show memory dc to current dc (blit) dc = PaintDC(self._panel) dc.Blit(0, 0, FrameWidth, FrameHeight, tdc, 0, 0) tdc.SelectObject(NullBitmap) self.logger.debug(f'Exit OnRefreshPanel')
def refreshBuffer(self): # get geometry X, Y = self.scroll cw, ch = self.charSize sl, sr, st, sb = 5, 15, 5, 5 cl, rw = self.screenSize # create device context dc = MemoryDC() # select dc.SelectObject(self.bitmapBuffer) # set background dc.SetBackground(self.brushes['bkgd']) # and clear buffer dc.Clear() # draw margins x3, x4 = 0 * cw, cl * cw x1, y1 = (sl - 0.5) * cw, (st - 0.5) * ch x2, y2 = (cl - sr + 0.5) * cw, (rw - sb + 0.5) * ch dc.SetPen(self.pens['margin']) dc.DrawLine(x1, y1, x1, y2) dc.DrawLine(x2, y1, x2, y2) dc.DrawLine(x3, y1, x4, y1) dc.DrawLine(x3, y2, x4, y2) # get block geometry r1, c1 = self.blockBegin r2, c2 = self.blockEnd block = False # draw text x, y = 0, 0 for line in self.characterBuffer[Y:]: # draw line number self.drawText(dc, f'{y+Y:4d}', 0, y + st, self.numberChars) # the line is outside of the block if y + Y < r1 or y + Y > r2: self.drawText(dc, line[X:], x + sl, y + st, self.normalChars) # done y += 1 if y + st >= rw - sb: break continue # block is inside if the line if r1 == r2: # draw normal text # self.drawText(dc, line[X:], x+sl, y+st, self.normalChars) # # block has size zero # if c1 == c2: # # done # y += 1 # if y+st >= rw-sb: break # continue # block has finite size self.drawText(dc, line[X:], x + sl, y + st, self.normalChars) self.drawText(dc, line[X:c2], x + sl, y + st, self.selectedChars) self.drawText(dc, line[X:c1], x + sl, y + st, self.normalChars) # done y += 1 if y + st >= rw - sb: break continue # line at the start of the block if y + Y == r1: self.drawText(dc, line[X:], x + sl, y + st, self.selectedChars) self.drawText(dc, line[X:c1], x + sl, y + st, self.normalChars) # done y += 1 if y + st >= rw - sb: break continue # line at the end of the block if y + Y == r2: self.drawText(dc, line[X:], x + sl, y + st, self.normalChars) self.drawText(dc, line[X:c2], x + sl, y + st, self.selectedChars) # done y += 1 if y + st >= rw - sb: break continue # line in the middle of the block self.drawText(dc, line[X:], x + sl, y + st, self.selectedChars) y += 1 if y + st >= rw - sb: break # draw cursor x, y = self.cursor x1, y1, y2 = (x + sl) * cw, (y + st) * ch, (y + st + 1) * ch - 1 dc.SetPen(self.pens['cursor']) dc.DrawLine(x1, y1, x1, y2) # done dc.SelectObject(NullBitmap) return
def Start(self): # define colors self.colours = {} self.colours['bkgd'] = Colour(52, 61, 70) self.colours['text'] = Colour(255, 255, 255) self.colours['numb'] = Colour(160, 160, 160) self.colours['sepr'] = Colour(200, 120, 120) self.colours['selbgd'] = Colour(92, 101, 110) self.colours['seltxt'] = Colour(255, 255, 255) # brushes self.brushes = {} # normal background b = Brush() b.SetStyle(BRUSHSTYLE_SOLID) b.SetColour(self.colours['bkgd']) self.brushes['bkgd'] = b # selected background b = Brush() b.SetStyle(BRUSHSTYLE_SOLID) b.SetColour(self.colours['selbgd']) self.brushes['selbgd'] = b # pens self.pens = {} p = Pen() p.SetColour(self.colours['sepr']) p.SetWidth(1) p.SetStyle(PENSTYLE_SOLID) self.pens['sepr'] = p # create device context to work on bitmaps dc = MemoryDC() # define font self.fonts = {} f = Font() f.SetFamily(FONTFAMILY_ROMAN) f.SetFaceName("Monospace") f.SetEncoding(FONTENCODING_DEFAULT) f.SetStyle(FONTSTYLE_NORMAL) f.SetWeight(FONTWEIGHT_NORMAL) f.SetUnderlined(False) f.SetPointSize(10) self.fonts['monospace'] = f # select font f = 'monospace' # get font size dc.SetFont(self.fonts[f]) w, h = dc.GetTextExtent(' ') # build character dictionary self.rawChars = {} # standard printable ascii table for c in range(32, 126): self.rawChars[chr(c)] = None # extra characters (UK) self.rawChars['£'] = None # save character size self.rawChars['Size'] = w, h # set background color dc.SetBackground(self.brushes['bkgd']) # build characters bitmaps for C in self.rawChars.keys(): if C == 'Size': continue # instanciate character bitmap self.rawChars[C] = Bitmap(w, h, BITMAP_SCREEN_DEPTH) # select bitmap dc.SelectObject(self.rawChars[C]) # clear bitmap dc.Clear() # set text color dc.SetTextForeground(self.colours['text']) # draw character shape and color dc.DrawText(C, 0, 0) # done dc.SelectObject(NullBitmap) # build selected dictionary self.selChars = {} # standard printable ascii table for c in range(32, 126): self.selChars[chr(c)] = None # extra characters (UK) self.selChars['£'] = None # save character size self.selChars['Size'] = w, h # set background color dc.SetBackground(self.brushes['selbgd']) # build selected bitmaps for C in self.selChars.keys(): if C == 'Size': continue # instanciate character bitmap self.selChars[C] = Bitmap(w, h, BITMAP_SCREEN_DEPTH) # select bitmap dc.SelectObject(self.selChars[C]) # clear bitmap dc.Clear() # set text color dc.SetTextForeground(self.colours['seltxt']) # draw character shape and color dc.DrawText(C, 0, 0) # done dc.SelectObject(NullBitmap) # create bitmap buffer display and clear buffer self.bitmapBuffer = Bitmap( 128 * w, # bitmap width 10 * h, # bitmap height BITMAP_SCREEN_DEPTH) # bitmap depth dc.SelectObject(self.bitmapBuffer) dc.SetBackground(self.brushes['bkgd']) dc.Clear() dc.SelectObject(NullBitmap) # reference BackgroundBitmap to bitmapBuffer self.Panel.BackgroundBitmap = self.bitmapBuffer # adjust frame position scrw, scrh = 3840, 1200 # screen size w, h = self.Panel.BackgroundBitmap.GetSize() self.Frame.SetPosition(Point(scrw / 4 - 128 * 8 / 2, 800)) # draw text string dc.SelectObject(self.bitmapBuffer) t = list("hello") # select buffer # draw text X = 0 w, h = self.rawChars['Size'] for c in t: dc.DrawBitmap(self.rawChars[c], X, 0) X += w t = list("World!") # select buffer # draw text X = 0 w, h = self.selChars['Size'] for c in t: dc.DrawBitmap(self.selChars[c], X, h) X += w # release bitmap dc.SelectObject(NullBitmap) # done return
def OnDrawItem(self, dc, rect, item, flags): idx = self.GetClientData(item) dc_img = MemoryDC() dc_img.SelectObject(self.bitmaps[idx]) w, h = self.bitmaps[idx].GetWidth(), self.bitmaps[idx].GetHeight() dc.Blit(rect.left, rect.top, w, h, dc_img, 0, 0, logicalFunc=wx.AND)
class RowScroller(wx.Window): """ A VScrolledWindow that allows for pixel size scrolling """ def __init__(self, datamodel, parent, id=wx.ID_ANY, label="", pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.NO_BORDER): super().__init__(parent) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_CHAR, self.__OnChar) self.Bind(wx.EVT_SCROLLWIN, self.OnScroll) self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) self.line_size = 16 # Scroll this many pixels each time self.pixels_hidden_first_row = 0 self.pixels_hidden_last_row = 0 self.client_height = 0 self.fixed_row = None self.SetBackgroundStyle(wx.BG_STYLE_PAINT) self.current_pos = 0 self.margin = 8 #above this, use scroll directly without retrieving all intermediary rows self.scroll_row_threshold = 500 self.datamodel = datamodel self.datamodel.INSERTED.subscribe(self.OnInserted) self.datamodel.DELETED.subscribe(self.OnRemoved) self.datamodel.MODIFIED.subscribe(self.OnModified) # New self.displayed_rows = deque() self.heights = RowHeigths() def SetFixedRow(self, rowpos): """ The current row is the row that stays fixed when rows are inserted, or removed""" self.fixed_row = rowpos self.PaintRect(self.GetClientRect()) def GetFixedRow(self): return first(self.displayed_rows, lambda e: e.rowpos == self.fixed_row) def GetRowCached(self, row_id): result = self.datamodel.Get(row_id) self.heights.add(row_id, result.height) return result def ReindexDisplayedRows(self, reindex_func): if self.fixed_row is not None: self.fixed_row = reindex_func(self.fixed_row) for d in self.displayed_rows: d.rowpos = reindex_func(d.rowpos) self.heights.reindex(reindex_func) def OnInserted(self, insert_pos, reindex_func): self.ReindexDisplayedRows(reindex_func) fixed_row = self.GetFixedRow() if self.fixed_row: self.ScrollToLayout(self.fixed_row, fixed_row.y) else: self.ScrollToLayout(self.displayed_rows[0].rowpos, self.displayed_rows[0].y) self.PaintRect(self.GetClientRect()) wx.PostEvent(self, RowScrollerDisplayChanged()) def OnRemoved(self, pos, reindex_func): if self.fixed_row == pos: self.fixed_row = None fixed_row = self.GetFixedRow() self.ReindexDisplayedRows(reindex_func) if self.fixed_row: self.ScrollToLayout(self.fixed_row, fixed_row.y) else: self.ScrollToLayout(self.displayed_rows[0].rowpos, self.displayed_rows[0].y) self.PaintRect(self.GetClientRect()) def OnModified(self, rowpos): disprow = first(self.displayed_rows, lambda e: e.rowpos == rowpos) if not disprow: return row = self.GetRowCached(rowpos) if row.height != disprow.height(): fixed_row = self.GetFixedRow() if self.fixed_row: self.ScrollToLayout(self.fixed_row, fixed_row.y) else: self.ScrollToLayout(self.displayed_rows[0].rowpos, self.displayed_rows[0].y) self.PaintRect(self.GetClientRect()) else: self.RepaintRow(rowpos) self.BlitToScreen(self.GetLayoutRect(rowpos)) def GetLayoutRect(self, rowpos): disprow = first(self.displayed_rows, lambda e: e.rowpos == rowpos) result = wx.Rect(0, disprow.y, self.client_width, disprow.end_y) def PaintRow(self, dc, row, start_y, end_y): dc.SetClippingRegion( wx.Rect( (self.margin, start_y, self.client_width, end_y - start_y))) dc.Clear() row.Paint(dc, self.margin, start_y) dc.DestroyClippingRegion() def RepaintRow(self, rowpos): d = first(self.displayed_rows, lambda e: e.rowpos == rowpos) #rowpos, row, start_y, next_y = self.displayed_rows(rowpos) self.dc_back.SetClippingRegion( wx.Rect(self.margin, d.y, self.client_width, d.height())) self.dc_back.Clear() d.row.Paint(self.dc_back, self.margin, d.y) self.dc_back.DestroyClippingRegion() def BlitToScreen(self, rect): # TODO: Remove this.. rect = self.GetClientRect() dc = wx.ClientDC(self) dc.Blit(rect.x, rect.y, rect.width, rect.height, self.dc_back, rect.x, rect.y) def PaintRect(self, rect, refresh=True): self.dc_back.SetClippingRegion(rect) self.dc_back.Clear() for displayed_row in self.displayed_rows: if displayed_row.y + displayed_row.row.height >= rect.top: self.PaintRow(self.dc_back, displayed_row.row, displayed_row.y, displayed_row.y + displayed_row.row.height) if displayed_row.y >= rect.bottom: break if self.fixed_row is not None: self.dc_back.SetBrush(wx.TRANSPARENT_BRUSH) self.dc_back.SetPen(wx.BLACK_PEN) fixed_row = first(self.displayed_rows, lambda e: e.rowpos == self.fixed_row) if fixed_row: self.dc_back.DrawRectangle( 1, fixed_row.y + 1, self.client_width - 2, fixed_row.end_y - fixed_row.y - 2) self.BlitToScreen(rect) self.dc_back.DestroyClippingRegion() def ScrollBackBufferRow(self, start_y, end_y, distance): self.dc_back.Blit(0, start_y + distance, self.client_width, end_y - start_y, self.dc_back, 0, start_y) def ScrollBackBuffer(self, distance): # do we need another image (e.g. test Blit for overlapping regions) self.dc_back.Blit(0, distance, self.client_width, self.inner_height, self.dc_back, 0, 0) def HitTest(self, x, y): x -= self.margin for displayed_row in self.displayed_rows: if displayed_row.y <= y <= displayed_row.end_y: return (displayed_row.rowpos, ) + displayed_row.row.HitTest( x, y - displayed_row.y) def OnSize(self, event): #print ("OnSize", event) self.client_width, self.client_height = self.GetClientSize() self.inner_height = self.client_height self.BackBuffer = wx.Bitmap(self.client_width, self.client_height) self.dc_back = MemoryDC() self.dc_back.SelectObject(self.BackBuffer) #self.ResetHeightEstimate() # Estimate Height max_idx = self.datamodel.GetApproximateCount() if max_idx < 50: rowsample = range(max_idx) else: rowsample = set([ self.datamodel.GetApproximatePos(random.randrange(0, max_idx)) for _ in range(50) ]) for pos in rowsample: self.heights.add(pos, self.datamodel.Get(pos).height) if self.displayed_rows: rowpos, y = self.displayed_rows[0].rowpos, self.displayed_rows[0].y else: rowpos, y = self.datamodel.GetFirstPos(), 0 self.ScrollToLayout(rowpos, y) self.PaintRect(self.GetClientRect(), refresh=True) event.Skip() def GetScrollPosition(self): return self.current_pos def GetInnerHeight(self): return self.inner_height def RefreshScrollBar(self): # idx = self.GetApproximateIndex(self.layout_rows[0][0]) # This line was removed as it was unused. Would we need this? self.SetScrollbar(wx.VERTICAL, self.current_pos, self.inner_height, self.heights.estimate() * self.datamodel.GetApproximateCount(), refresh=True) def Display(self, displayed_row, position): #print ("Displaying", displayed_row) self.displayed_rows.insert(position, displayed_row) def Hide(self, top=True): if top: result = self.displayed_rows.popleft() else: result = self.displayed_rows.pop() #print ("Hiding", result) def FillRemaingRows(self): if self.displayed_rows: # Expand bottom displayed_row = self.displayed_rows[-1] y = displayed_row.y rowpos = self.datamodel.GetNextPos(displayed_row.rowpos) while y + displayed_row.row.height < self.inner_height and rowpos is not None: row = self.GetRowCached(rowpos) y += displayed_row.row.height displayed_row = DisplayedRow(y, y + row.height, rowpos, row) self.Display(displayed_row, len(self.displayed_rows)) rowpos = self.datamodel.GetNextPos(displayed_row.rowpos) # Expand top displayed_row = self.displayed_rows[0] y = displayed_row.y rowpos = self.datamodel.GetPrevPos(displayed_row.rowpos) while y > 0 and rowpos is not None: row = self.GetRowCached(rowpos) y -= row.height self.Display(DisplayedRow(y, y + row.height, rowpos, row), 0) rowpos = self.datamodel.GetPrevPos(rowpos) def ClearExtraRows(self): # Remove Top while self.displayed_rows and self.displayed_rows[ 0].y + self.displayed_rows[0].row.height <= 0: self.Hide(True) # Remove Bottom while self.displayed_rows and self.displayed_rows[ -1].y >= self.inner_height: self.Hide(False) def ScrollToLayout(self, rowpos, start_px): """ Absolute scroll rows such that the row 'rowpos' is now at 'start_px' pixels from the top of the window (start_px can be negative). """ y = start_px self.displayed_rows = deque() if rowpos is not None: row = self.GetRowCached(rowpos) self.displayed_rows.append( DisplayedRow(y, y + row.height, rowpos, row)) self.FillRemaingRows() self.RefreshScrollBar() def _ScrollNoRefresh(self, distance): ''' Relative Scroll distance in pixels (negative is up) ''' if distance == 0: return 0 if distance > 0: distance_moved = self.MoveDownBottom(distance) self.MoveDownTop(distance_moved) else: distance_moved = self.MoveUpTop(-distance) self.MoveUpBottom(distance_moved) distance_moved = math.copysign(distance_moved, distance) self.current_pos = max( min(self.current_pos + distance_moved, self.estimated_height), 0) return distance_moved def _RefreshAfterScrolling(self, distance): ''' Refresh after scrolling distance pixels (negative is up) ''' if abs(distance) < self.inner_height: if distance > 0: paint_rect = wx.Rect(0, self.inner_height - distance, self.client_width, self.client_height) else: paint_rect = wx.Rect(0, 0, self.client_width, -distance) self.ScrollBackBuffer(-distance) self.ScrollWindow(0, -distance) else: paint_rect = wx.Rect(0, 0, self.client_width, self.inner_height) self.PaintRect(paint_rect, refresh=True) self.RefreshScrollBar() def _ScrollUntil(self, distance_increment, fct): ''' Relative Scroll distance_increment in pixels until fct returns True. Then, refresh the screen. This can be used for "Scroll until cursor is in view" ''' distance_moved = 0 while not fct: distance_moved += self._ScrollNoRefresh(distance_increment) def ScrollIntoViewXY(self, row_id, x, y): return self.ScrollIntoRectView(row_id, wx.Rect(x, y, 0, 0)) def ScrollIntoRectView(self, row_id, rect): # FIXME print("ScrollIntoRectView") first, last = self.layout_row_ids[0], self.layout_row_ids[-1] if row_id > last: #self.ScrollIntoViewXY2(row_id, rect.left, rect.bottom) self.ScrollToLayout(row_id, self.inner_height - 1 - rect.bottom) self.PaintRect(self.GetClientRect(), refresh=True) self.RefreshScrollBar() elif row_id < first: self.ScrollToLayout(row_id, -rect.top) self.PaintRect(self.GetClientRect(), refresh=True) self.RefreshScrollBar() #self.ScrollIntoViewXY2(row_id, rect.left, rect.top) elif row_id == last: missing = max(rect.bottom - self.PixelsVisibleLastRow(), 0) distance_moved = self.Scroll(missing) elif row_id == first: missing = max(self.PixelsHiddenFirstRow() - rect.top, 0) distance_moved = self.Scroll(-missing) def _ScrollRows(self, distance): for displayed_row in self.displayed_rows: displayed_row.y -= distance displayed_row.end_y -= distance def Scroll(self, distance, bounded_scolling=True): if not self.displayed_rows: return self._ScrollRows(distance) self.FillRemaingRows() scrolled_pixels = distance # Now we might have scrolled too far if bounded_scolling: if distance < 0: if self.displayed_rows[0].y > 0: dist = self.displayed_rows[0].y self._ScrollRows(dist) scrolled_pixels += dist elif distance > 0: if self.displayed_rows[ -1].y < self.inner_height - self.displayed_rows[ -1].row.height: dist = self.displayed_rows[-1].y - ( self.inner_height - self.displayed_rows[-1].row.height) self._ScrollRows(dist) scrolled_pixels += dist self.FillRemaingRows() self.ClearExtraRows() self.current_pos += scrolled_pixels paint_rect = wx.Rect(0, 0, self.client_width, self.inner_height) self.PaintRect(paint_rect, refresh=True) self.RefreshScrollBar() wx.PostEvent(self, RowScrollerScrolledEvent()) def ScrollTo(self, pos): '''Absolute scroll pos in pixels''' # pos varies between 0 and self.estimated_height - self.inner_height if abs(pos - self.current_pos) < 1000: # Avoid flickering, when scrolling small distances by doing a relative scroll return self.Scroll(pos - self.current_pos) else: estimated_row_height = self.heights.estimate() rowpos = self.datamodel.GetApproximatePos( pos // int(estimated_row_height)) hidden_first_row = pos % int(estimated_row_height) self.ScrollToLayout(rowpos, hidden_first_row) self.PaintRect(self.GetClientRect(), refresh=True) self.current_pos = pos self.RefreshScrollBar() wx.PostEvent(self, RowScrollerScrolledEvent()) def OnScroll(self, event): event_type = event.GetEventType() if event_type == wx.EVT_SCROLLWIN_PAGEDOWN.typeId: pos = event.GetPosition() self.Scroll(self.inner_height) elif event_type == wx.EVT_SCROLLWIN_PAGEUP.typeId: self.Scroll(-self.inner_height) elif event_type == wx.EVT_SCROLLWIN_THUMBTRACK.typeId: pos = event.GetPosition() self.ScrollTo(pos) elif event_type == wx.EVT_SCROLLWIN_THUMBRELEASE.typeId: pos = event.GetPosition() self.ScrollTo(pos) def OnMouseWheel(self, event): rotation = event.GetWheelRotation() delta = event.GetWheelDelta() self.Scroll(-self.line_size * (rotation / delta)) def __OnChar(self, event): key_code = event.GetKeyCode() if key_code == WXK_PAGEDOWN: self.Scroll(self.inner_height) elif key_code == WXK_PAGEUP: self.Scroll(-self.inner_height) elif key_code == WXK_UP: self.Scroll(-self.line_size) elif key_code == WXK_DOWN: self.Scroll(self.line_size) event.Skip()