diff --git a/rangeslider/rangeslider.py b/rangeslider/rangeslider.py new file mode 100644 index 0000000..7660660 --- /dev/null +++ b/rangeslider/rangeslider.py @@ -0,0 +1,170 @@ +import wx + +class RangeSlider(wx.Panel): + def __init__(self, parent, id=wx.ID_ANY, minValue=0, maxValue=100, + minRange=20, maxRange=80, orientation=wx.HORIZONTAL, **kwargs): + super().__init__(parent, id, **kwargs) + self.minValue = minValue + self.maxValue = maxValue + self.minRange = minRange + self.maxRange = maxRange + self.orientation = orientation + + self.dragging = None + self.offset = 0 + + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + self.Bind(wx.EVT_MOTION, self.OnMouseMove) + self.Bind(wx.EVT_SIZE, self.OnResize) + + def OnPaint(self, event): + dc = wx.PaintDC(self) + self.Draw(dc) + + def OnResize(self, event): + self.Refresh() + event.Skip() + + def OnLeftDown(self, event): + pos = event.GetPosition() + min_rect = self.GetMinRangeRect() + max_rect = self.GetMaxRangeRect() + + if min_rect.Contains(pos): + self.dragging = 'min' + self.offset = pos - min_rect.GetPosition() + elif max_rect.Contains(pos): + self.dragging = 'max' + self.offset = pos - max_rect.GetPosition() + elif self.GetRangeRect().Contains(pos): + self.dragging = 'range' + self.offset = pos - self.GetRangeRect().GetPosition() + + self.CaptureMouse() + + def OnLeftUp(self, event): + if self.HasCapture(): + self.ReleaseMouse() + self.dragging = None + + def OnMouseMove(self, event): + if self.dragging: + pos = event.GetPosition() + size = self.GetClientSize() + width = size.GetWidth() if self.orientation == wx.HORIZONTAL else size.GetHeight() + total_range = self.maxValue - self.minValue + step = total_range / width + + if self.dragging == 'min': + new_min = self.minValue + int((pos.x - self.offset.x) * step) if self.orientation == wx.HORIZONTAL else self.minValue + int((pos.y - self.offset.y) * step) + new_min = max(self.minValue, min(new_min, self.maxRange - 1)) + self.minRange = new_min + elif self.dragging == 'max': + new_max = self.minValue + int((pos.x - self.offset.x) * step) if self.orientation == wx.HORIZONTAL else self.minValue + int((pos.y - self.offset.y) * step) + new_max = min(self.maxValue, max(new_max, self.minRange + 1)) + self.maxRange = new_max + elif self.dragging == 'range': + range_width = self.maxRange - self.minRange + new_min = self.minValue + int((pos.x - self.offset.x) * step) if self.orientation == wx.HORIZONTAL else self.minValue + int((pos.y - self.offset.y) * step) + new_min = max(self.minValue, min(new_min, self.maxValue - range_width)) + self.minRange = new_min + self.maxRange = new_min + range_width + + self.Refresh() + + def Draw(self, dc): + size = self.GetClientSize() + width, height = size.GetWidth(), size.GetHeight() + + total_range = self.maxValue - self.minValue + if self.orientation == wx.HORIZONTAL: + min_pos = int((self.minRange - self.minValue) / total_range * width) + max_pos = int((self.maxRange - self.minValue) / total_range * width) + else: + min_pos = int((self.minRange - self.minValue) / total_range * height) + max_pos = int((self.maxRange - self.minValue) / total_range * height) + + # Background + dc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))) + dc.DrawRectangle(0, 0, width, height) + + # Draw range bar + dc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT))) + if self.orientation == wx.HORIZONTAL: + dc.DrawRectangle(min_pos, 0, max_pos - min_pos, height) + else: + dc.DrawRectangle(0, min_pos, width, max_pos - min_pos) + + # Draw min and max handles + dc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))) + handle_width = 10 if self.orientation == wx.HORIZONTAL else width + handle_height = 10 if self.orientation == wx.VERTICAL else height + + if self.orientation == wx.HORIZONTAL: + dc.DrawRectangle(min_pos - 5, 0, handle_width, handle_height) + dc.DrawRectangle(max_pos - 5, 0, handle_width, handle_height) + else: + dc.DrawRectangle(0, min_pos - 5, handle_width, handle_height) + dc.DrawRectangle(0, max_pos - 5, handle_width, handle_height) + + def GetMinRangeRect(self): + size = self.GetClientSize() + width, height = size.GetWidth(), size.GetHeight() + total_range = self.maxValue - self.minValue + + if self.orientation == wx.HORIZONTAL: + min_pos = int((self.minRange - self.minValue) / total_range * width) + return wx.Rect(min_pos - 5, 0, 10, height) + else: + min_pos = int((self.minRange - self.minValue) / total_range * height) + return wx.Rect(0, min_pos - 5, width, 10) + + def GetMaxRangeRect(self): + size = self.GetClientSize() + width, height = size.GetWidth(), size.GetHeight() + total_range = self.maxValue - self.minValue + + if self.orientation == wx.HORIZONTAL: + max_pos = int((self.maxRange - self.minValue) / total_range * width) + return wx.Rect(max_pos - 5, 0, 10, height) + else: + max_pos = int((self.maxRange - self.minValue) / total_range * height) + return wx.Rect(0, max_pos - 5, width, 10) + + def GetRangeRect(self): + size = self.GetClientSize() + width, height = size.GetWidth(), size.GetHeight() + total_range = self.maxValue - self.minValue + + if self.orientation == wx.HORIZONTAL: + min_pos = int((self.minRange - self.minValue) / total_range * width) + max_pos = int((self.maxRange - self.minValue) / total_range * width) + return wx.Rect(min_pos, 0, max_pos - min_pos, height) + else: + min_pos = int((self.minRange - self.minValue) / total_range * height) + max_pos = int((self.maxRange - self.minValue) / total_range * height) + return wx.Rect(0, min_pos, width, max_pos - min_pos) + +class MainFrame(wx.Frame): + def __init__(self): + super().__init__(None, title='Range Slider Example', size=(400, 200)) + panel = wx.Panel(self) + + self.horizontal_slider = RangeSlider(panel, minValue=0, maxValue=100, minRange=20, maxRange=80, + orientation=wx.HORIZONTAL, pos=(10, 10), size=(380, 50)) + self.vertical_slider = RangeSlider(panel, minValue=0, maxValue=100, minRange=20, maxRange=80, + orientation=wx.VERTICAL, pos=(10, 70), size=(50, 120)) + + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(self.horizontal_slider, 1, wx.EXPAND | wx.ALL, 10) + sizer.Add(self.vertical_slider, 0, wx.EXPAND | wx.ALL, 10) + panel.SetSizer(sizer) + + self.Show() + +if __name__ == '__main__': + app = wx.App(False) + frame = MainFrame() + app.MainLoop() \ No newline at end of file