def on_mouse(self, event): '''EVT_MOUSE_EVENTS handler.''' # todo: maybe right click should give context menu with 'Sensitivity...' # todo: make check: if left up and has capture, release capture self.Refresh() (w, h) = self.GetClientSize() (x, y) = event.GetPositionTuple() if event.LeftDown(): self.being_dragged = True self.snap_map = SnapMap( snap_point_ratios=self._get_snap_points_as_ratios(), base_drag_radius=self.base_drag_radius, snap_point_drag_well=self.snap_point_drag_well, initial_y=y, initial_ratio=self.current_ratio ) self.SetCursor(cursor_collection.get_closed_grab()) # SetCursor must be before CaptureMouse because of wxPython/GTK # weirdness self.CaptureMouse() return if event.LeftIsDown() and self.HasCapture(): ratio = self.snap_map.y_to_ratio(y) value = self._ratio_to_value(ratio) self.value_setter(value) if event.LeftUp(): # todo: make sure that when leaving # entire app, things don't get f****d if self.HasCapture(): self.ReleaseMouse() # SetCursor must be after ReleaseMouse because of wxPython/GTK # weirdness self.SetCursor(cursor_collection.get_open_grab()) self.being_dragged = False self.snap_map = None return
def handle_finish(self, instance, finished): if finished: self.active_nation = self.nation_selection_page.nation print( f"Nation Selection finished successfully (I'm {self.active_nation})" ) self.screen_manager.current = "CivMap" snap_map = SnapMap(self.civ_map_page.ms) fl = self.civ_map_page.fl for n in self.nation_selection_page.get_selected_nations(): self.civ_map_page.nations.append(Nation(n, fl, snap_map)) for n in self.civ_map_page.nations: n.show_or_hide_stock(self.active_nation) self.update_board(0) Clock.schedule_interval(self.update_board, 1)
class Knob(wx.Panel): ''' A knob that sets a real value between -Infinity and Infinity. (Not really touching infinity.) By turning the knob with the mouse, the user changes a floating point variable. There are three "scales" that one should keep in mind when working with Knob: 1. The "value" scale, which is the value that the actual final variable gets. It spans from -Infinity to Infinity. 2. The "angle" scale, which is the angle in which the knob appears on the screen. It span from (-(5/6) * pi) to ((5/6) * pi). 3. As a more convenient mediator between them there's the "ratio" scale, which spans from -1 to 1, and is mapped linearly to "angle". The knob has snap points that can be modified with `.set_snap_point` and `.remove_snap_point`. These are specified by value. ''' # todo future: make key that disables snapping while dragging # todo: consider letting the knob turn just a bit slower near the edges. # todo: currently forcing size to be constant, in future allow changing def __init__(self, parent, getter, setter, *args, **kwargs): ''' Construct the knob. `getter` is the getter function used to get the value of the variable. `setter` is the setter function used to set the value of the variable. Note that you can't give a size argument to knob, it is always created with a size of (29, 29). ''' assert 'size' not in kwargs kwargs['size'] = (29, 29) assert callable(setter) and callable(getter) self.value_getter, self.value_setter = getter, setter wx.Panel.__init__(self, parent, *args, **kwargs) self.original_bitmap = wx.BitmapFromImage( wx.ImageFromStream( pkg_resources.resource_stream(images_package, 'knob.png'), wx.BITMAP_TYPE_ANY ) ) self.Bind(wx.EVT_PAINT, self.on_paint) self.Bind(wx.EVT_SIZE, self.on_size) self.Bind(wx.EVT_MOUSE_EVENTS, self.on_mouse) # self.Bind(wx.EVT_ERASE_BACKGROUND, self.on_erase) self.SetCursor(cursor_collection.get_open_grab()) self._knob_house_brush = wx.Brush(wx.Color(0, 0, 0)) '''Brush used to paint the circle around the knob.''' self.current_angle = 0 '''The current angle of the knob.''' self.current_ratio = 0 '''The current ratio of the knob.''' self.sensitivity = 25 ''' The knob's sensitivity. Higher values will cause faster changes in value when turning the knob. ''' self.angle_resolution = math.pi / 180 '''The minimal change in angle that will warrant a repaint.''' self.snap_points = [] '''An ordered list of snap points, specified by value.''' self.base_drag_radius = 50 ''' The base drag radius, in pixels. This number is the basis for calculating the height of the area in which the user can play with the mouse to turn the knob. Beyond that area the knob will be turned all the way to one side, and any movement farther will have no effect. If there are no snap points, the total height of that area will be `2 * self.base_drag_radius`. ''' self.snap_point_drag_well = 20 ''' The height of a snap point's drag well, in pixels. This is the height of the area on the screen in which, when the user drags to it, the knob will have the value of the snap point. The bigger this is, the harder the snap point "traps" the mouse. ''' self.being_dragged = False '''Flag saying whether the knob is currently being dragged.''' self.snap_map = None ''' The current snap map used by the knob. See documentation of SnapMap for more info. ''' self.needs_recalculation_flag = True '''Flag saying whether the knob needs to be recalculated.''' self._recalculate() def _angle_to_ratio(self, angle): '''Convert from angle to ratio.''' return angle / (math.pi * 5 / 6) def _ratio_to_value(self, ratio): '''Convert from ratio to value.''' return self.sensitivity * \ math_tools.sign(ratio) * \ (4 / math.pi**2) * \ math.log(math.cos(ratio * math.pi / 2))**2 def _value_to_ratio(self, value): '''Convert from value to ratio.''' return math_tools.sign(value) * \ (2 / math.pi) * \ math.acos( math.exp( - (math.pi * math.sqrt(abs(value))) / \ (2 * math.sqrt(self.sensitivity)) ) ) def _ratio_to_angle(self, ratio): '''Convert from ratio to angle.''' return ratio * (math.pi * 5 / 6) def _get_snap_points_as_ratios(self): '''Get the list of snap points, but as ratios instead of as values.''' return [self._value_to_ratio(value) for value in self.snap_points] def set_snap_point(self, value): '''Set a snap point. Specified as value.''' # Not optimizing with the sorting for now self.snap_points.append(value) self.snap_points.sort() def remove_snap_point(self, value): '''Remove a snap point. Specified as value.''' self.snap_points.remove(value) def _recalculate(self): ''' Recalculate the knob, changing its angle and refreshing if necessary. ''' value = self.value_getter() self.current_ratio = self._value_to_ratio(value) angle = self._ratio_to_angle(self.current_ratio) d_angle = angle - self.current_angle if abs(d_angle) > self.angle_resolution: self.current_angle = angle self.Refresh() self.needs_recalculation_flag = False def on_paint(self, event): '''EVT_PAINT handler.''' # Not checking for recalculation flag, this widget is not real-time # enough to care about the delay. dc = wx.BufferedPaintDC(self) dc.SetBackground(wx_tools.get_background_brush()) dc.Clear() w, h = self.GetClientSize() gc = wx.GraphicsContext.Create(dc) gc.SetPen(wx.TRANSPARENT_PEN) gc.SetBrush(self._knob_house_brush) assert isinstance(gc, wx.GraphicsContext) gc.Translate(w/2, h/2) gc.Rotate(self.current_angle) gc.DrawEllipse(-13.5, -13.5, 27, 27) gc.DrawBitmap(self.original_bitmap, -13, -13, 26, 26) #gc.DrawEllipse(5,5,2,2) #gc.DrawEllipse(100,200,500,500) def on_size(self, event): '''EVT_SIZE handler.''' event.Skip() self.Refresh() def on_mouse(self, event): '''EVT_MOUSE_EVENTS handler.''' # todo: maybe right click should give context menu with 'Sensitivity...' # todo: make check: if left up and has capture, release capture self.Refresh() (w, h) = self.GetClientSize() (x, y) = event.GetPositionTuple() if event.LeftDown(): self.being_dragged = True self.snap_map = SnapMap( snap_point_ratios=self._get_snap_points_as_ratios(), base_drag_radius=self.base_drag_radius, snap_point_drag_well=self.snap_point_drag_well, initial_y=y, initial_ratio=self.current_ratio ) self.SetCursor(cursor_collection.get_closed_grab()) # SetCursor must be before CaptureMouse because of wxPython/GTK # weirdness self.CaptureMouse() return if event.LeftIsDown() and self.HasCapture(): ratio = self.snap_map.y_to_ratio(y) value = self._ratio_to_value(ratio) self.value_setter(value) if event.LeftUp(): # todo: make sure that when leaving # entire app, things don't get f****d if self.HasCapture(): self.ReleaseMouse() # SetCursor must be after ReleaseMouse because of wxPython/GTK # weirdness self.SetCursor(cursor_collection.get_open_grab()) self.being_dragged = False self.snap_map = None return """ def on_erase(self, event): pass """