--- a/printrun-src/printrun/gui/xybuttons.py Tue Jan 19 20:45:09 2021 +0100 +++ b/printrun-src/printrun/gui/xybuttons.py Wed Jan 20 10:15:13 2021 +0100 @@ -23,13 +23,36 @@ elif n > 0: return 1 else: return 0 -class XYButtons(BufferedCanvas): +DASHES = [4, 7] +# Brush and pen for grey overlay when mouse hovers over +HOVER_PEN_COLOR = wx.Colour(100, 100, 100, 172) +HOVER_BRUSH_COLOR = wx.Colour(0, 0, 0, 128) + +class FocusCanvas(BufferedCanvas): + def __init__(self, *args, **kwds): + super().__init__(*args, **kwds) + self.Bind(wx.EVT_SET_FOCUS, self.onFocus) + + def onFocus(self, evt): + self.Refresh() + evt.Skip() + + def drawFocusRect(self, dc): + if self.HasFocus(): + pen = wx.Pen(wx.BLACK, 1, wx.PENSTYLE_USER_DASH) + pen.SetDashes(DASHES) + dc.Pen = pen + dc.Brush = wx.Brush(wx.TRANSPARENT_BRUSH) + dc.DrawRectangle(self.ClientRect) + +class XYButtons(FocusCanvas): keypad_positions = { - 0: (106, 100), + 0: (104, 99), 1: (86, 83), 2: (68, 65), 3: (53, 50) } + keypad_radius = 9 corner_size = (49, 49) corner_inset = (7, 13) label_overlay_positions = { @@ -55,6 +78,7 @@ self.bg_bmp = wx.Image(imagefile(self.imagename), wx.BITMAP_TYPE_PNG).ConvertToBitmap() self.keypad_bmp = wx.Image(imagefile("arrow_keys.png"), wx.BITMAP_TYPE_PNG).ConvertToBitmap() self.keypad_idx = -1 + self.hovered_keypad = None self.quadrant = None self.concentric = None self.corner = None @@ -68,10 +92,10 @@ self.lastCorner = None self.bgcolor = wx.Colour() - self.bgcolor.SetFromName(bgcolor) + self.bgcolor.Set(bgcolor) self.bgcolormask = wx.Colour(self.bgcolor.Red(), self.bgcolor.Green(), self.bgcolor.Blue(), 128) - BufferedCanvas.__init__(self, parent, ID, size=self.bg_bmp.GetSize()) + super().__init__(parent, ID, size=self.bg_bmp.GetSize()) self.bind_events() @@ -81,15 +105,19 @@ self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) - self.Bind(wx.EVT_KEY_UP, self.OnKey) - wx.GetTopLevelParent(self).Bind(wx.EVT_CHAR_HOOK, self.OnTopLevelKey) + self.Bind(wx.EVT_CHAR_HOOK, self.OnKey) + self.Bind(wx.EVT_KILL_FOCUS, self.onKillFocus) + + def onKillFocus(self, evt): + self.setKeypadIndex(-1) + evt.Skip() def disable(self): - self.enabled = False + self.Enabled = self.enabled = False self.update() def enable(self): - self.enabled = True + self.Enabled = self.enabled = True self.update() def repeatLast(self): @@ -112,21 +140,23 @@ def distanceToPoint(self, x1, y1, x2, y2): return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) - def cycleKeypadIndex(self): - idx = self.keypad_idx + 1 - if idx > 2: idx = 0 - return idx + def cycleKeypadIndex(self, forward): + idx = self.keypad_idx + (1 if forward else -1) + # do not really cycle to allow exiting of jog controls widget + return idx if idx < len(self.keypad_positions) else -1 def setKeypadIndex(self, idx): self.keypad_idx = idx self.update() - def getMovement(self): + def getMovement(self, event): xdir = [1, 0, -1, 0, 0, 0][self.quadrant] ydir = [0, 1, 0, -1, 0, 0][self.quadrant] zdir = [0, 0, 0, 0, 1, -1][self.quadrant] magnitude = math.pow(10, self.concentric - 2) - if not zdir == 0: + magnitude *= event.ShiftDown() and 2 or event.ControlDown() and 0.5 or 1 + + if zdir: magnitude = min(magnitude, 10) return (magnitude * xdir, magnitude * ydir, magnitude * zdir) @@ -141,7 +171,6 @@ def getQuadrantConcentricFromPosition(self, pos): rel_x = pos[0] - self.center[0] rel_y = pos[1] - self.center[1] - radius = math.sqrt(rel_x ** 2 + rel_y ** 2) if rel_x > rel_y and rel_x > -rel_y: quadrant = 0 # Right elif rel_x <= rel_y and rel_x > -rel_y: @@ -151,13 +180,14 @@ else: quadrant = 2 # Left + radius = math.sqrt(rel_x ** 2 + rel_y ** 2) idx = self.lookupConcentric(radius) return (quadrant, idx) def mouseOverKeypad(self, mpos): for idx, kpos in self.keypad_positions.items(): radius = self.distanceToPoint(mpos[0], mpos[1], kpos[0], kpos[1]) - if radius < 9: + if radius < XYButtons.keypad_radius: return idx return None @@ -172,7 +202,8 @@ gc.DrawPath(path) def highlightQuadrant(self, gc, quadrant, concentric): - assert(quadrant >= 0 and quadrant <= 3) + if not 0 <= quadrant <= 3: + return assert(concentric >= 0 and concentric <= 4) inner_ring_radius = self.concentric_inset @@ -218,7 +249,7 @@ w, h = self.corner_size xinset, yinset = self.corner_inset cx, cy = self.center - ww, wh = self.GetSizeTuple() + ww, wh = self.GetSize() if corner == 0: x, y = (cx - ww / 2 + xinset + 1, cy - wh / 2 + yinset) @@ -247,9 +278,8 @@ gc.DrawBitmap(self.bg_bmp, 0, 0, w, h) if self.enabled and self.IsEnabled(): - # Brush and pen for grey overlay when mouse hovers over - gc.SetPen(wx.Pen(wx.Colour(100, 100, 100, 172), 4)) - gc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 128))) + gc.SetPen(wx.Pen(HOVER_PEN_COLOR, 4)) + gc.SetBrush(wx.Brush(HOVER_BRUSH_COLOR)) if self.concentric is not None: if self.concentric < len(self.concentric_circle_radii): @@ -266,6 +296,11 @@ pos = (pos[0] - padw / 2 - 3, pos[1] - padh / 2 - 3) gc.DrawBitmap(self.keypad_bmp, pos[0], pos[1], padw, padh) + if self.hovered_keypad is not None and self.hovered_keypad != self.keypad_idx: + pos = self.keypad_positions[self.hovered_keypad] + r = XYButtons.keypad_radius + gc.DrawEllipse(pos[0]-r/2, pos[1]-r/2, r, r) + # Draw label overlays gc.SetPen(wx.Pen(wx.Colour(255, 255, 255, 128), 1)) gc.SetBrush(wx.Brush(wx.Colour(255, 255, 255, 128 + 64))) @@ -277,6 +312,9 @@ gc.SetPen(wx.Pen(self.bgcolor, 0)) gc.SetBrush(wx.Brush(self.bgcolormask)) gc.DrawRectangle(0, 0, w, h) + + self.drawFocusRect(dc) + # Used to check exact position of keypad dots, should we ever resize the bg image # for idx, kpos in self.label_overlay_positions.items(): # dc.DrawCircle(kpos[0], kpos[1], kpos[2]) @@ -284,43 +322,57 @@ # ------ # # Events # # ------ # - def OnTopLevelKey(self, evt): - # Let user press escape on any control, and return focus here - if evt.GetKeyCode() == wx.WXK_ESCAPE: - self.SetFocus() - evt.Skip() - def OnKey(self, evt): + # print('XYButtons key', evt.GetKeyCode()) if not self.enabled: + evt.Skip() return + key = evt.KeyCode if self.keypad_idx >= 0: - if evt.GetKeyCode() == wx.WXK_TAB: - self.setKeypadIndex(self.cycleKeypadIndex()) - elif evt.GetKeyCode() == wx.WXK_UP: + if key == wx.WXK_TAB: + keypad = self.cycleKeypadIndex(not evt.ShiftDown()) + self.setKeypadIndex(keypad) + if keypad == -1: + # exit widget after largest step + # evt.Skip() + # On MS Windows if tab event is delivered, + # it is not handled + self.Navigate(not evt.ShiftDown()) + return + elif key == wx.WXK_ESCAPE: + self.setKeypadIndex(-1) + elif key == wx.WXK_UP: self.quadrant = 1 - elif evt.GetKeyCode() == wx.WXK_DOWN: + elif key == wx.WXK_DOWN: self.quadrant = 3 - elif evt.GetKeyCode() == wx.WXK_LEFT: + elif key == wx.WXK_LEFT: self.quadrant = 2 - elif evt.GetKeyCode() == wx.WXK_RIGHT: + elif key == wx.WXK_RIGHT: self.quadrant = 0 - elif evt.GetKeyCode() == wx.WXK_PAGEUP: + elif key == wx.WXK_PAGEUP: self.quadrant = 4 - elif evt.GetKeyCode() == wx.WXK_PAGEDOWN: + elif key == wx.WXK_PAGEDOWN: self.quadrant = 5 else: evt.Skip() return - self.concentric = self.keypad_idx - x, y, z = self.getMovement() - - if x != 0 or y != 0 and self.moveCallback: - self.moveCallback(x, y) - if z != 0 and self.zCallback: - self.zCallback(z) - elif evt.GetKeyCode() == wx.WXK_SPACE: + self.concentric = self.keypad_idx + 1 + + if self.quadrant is not None: + x, y, z = self.getMovement(evt) + if (x or y) and self.moveCallback: + self.moveCallback(x, y) + if z and self.zCallback: + self.zCallback(z) + self.Refresh() + elif key == wx.WXK_SPACE: self.spacebarCallback() + elif key == wx.WXK_TAB: + self.setKeypadIndex(len(self.keypad_positions)-1 if evt.ShiftDown() else 0) + else: + # handle arrows elsewhere + evt.Skip() def OnMotion(self, event): if not self.enabled: @@ -328,12 +380,13 @@ oldcorner = self.corner oldq, oldc = self.quadrant, self.concentric + old_hovered_keypad = self.hovered_keypad mpos = event.GetPosition() - idx = self.mouseOverKeypad(mpos) + self.hovered_keypad = self.mouseOverKeypad(mpos) self.quadrant = None self.concentric = None - if idx is None: + if self.hovered_keypad is None: center = wx.Point(self.center[0], self.center[1]) riseDist = self.distanceToLine(mpos, center.x - 1, center.y - 1, center.x + 1, center.y + 1) fallDist = self.distanceToLine(mpos, center.x - 1, center.y + 1, center.x + 1, center.y - 1) @@ -353,7 +406,8 @@ if mpos.x < cx and mpos.y >= cy: self.corner = 3 - if oldq != self.quadrant or oldc != self.concentric or oldcorner != self.corner: + if oldq != self.quadrant or oldc != self.concentric or oldcorner != self.corner \ + or old_hovered_keypad != self.hovered_keypad: self.update() def OnLeftDown(self, event): @@ -375,7 +429,7 @@ self.lastMove = None self.cornerCallback(self.corner_to_axis[-1]) elif self.quadrant is not None: - x, y, z = self.getMovement() + x, y, z = self.getMovement(event) if self.moveCallback: self.lastMove = (x, y) self.lastCorner = None @@ -386,10 +440,7 @@ self.lastMove = None self.cornerCallback(self.corner_to_axis[self.corner]) else: - if self.keypad_idx == idx: - self.setKeypadIndex(-1) - else: - self.setKeypadIndex(idx) + self.setKeypadIndex(-1 if self.keypad_idx == idx else idx) def OnLeaveWindow(self, evt): self.quadrant = None @@ -486,9 +537,8 @@ gc.DrawBitmap(self.bg_bmp, 0, 0, w, h) if self.enabled and self.IsEnabled(): - # Brush and pen for grey overlay when mouse hovers over - gc.SetPen(wx.Pen(wx.Colour(100, 100, 100, 172), 4)) - gc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 128))) + gc.SetPen(wx.Pen(HOVER_PEN_COLOR, 4)) + gc.SetBrush(wx.Brush(HOVER_BRUSH_COLOR)) if self.concentric is not None: if self.concentric < len(self.concentric_circle_radii):