Source

wxPython / demo / GraphicsGradient.py

Full commit

import wx
g = wx

# To test compatibility of gradients with the generic GraphicsContext classes
# uncomment this line
#import wx.lib.graphics as g

#----------------------------------------------------------------------

class GradientPanel(wx.Panel):
    """
    This panel will be painted with the gradient brush created by the
    rest of the sample.
    """
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.SetInitialSize((600,100))
        
        # create a simple default brush we can use until a gradient
        # brush is given to us
        ctx = g.GraphicsContext.CreateMeasuringContext()
        self.brush = ctx.CreateBrush(wx.Brush('white'))

    def DrawWithBrush(self, brush):
        self.brush = brush
        self.Refresh()
               
    def OnPaint(self, evt):
        dc = wx.PaintDC(self)
        gc = g.GraphicsContext.Create(dc)
        gc.SetBrush(self.brush)
        gc.SetPen(wx.Pen('black', 1))
        w, h = gc.GetSize()
        gc.DrawRectangle(0,0,w,h)



class GradientStopPanel(wx.Panel):
    """
    Contains the controls for editing each gradient stop. (Colour,
    alpha and relative position.)
    """
    def __init__(self, parent, posVal, colour=wx.BLACK, alpha=wx.ALPHA_OPAQUE):
        wx.Panel.__init__(self, parent)

        # make some widgets
        self.pos = wx.SpinCtrlDouble(self, value='%2f' % posVal, size=(65,-1),
                                     min=0.0, max=1.0, initial=posVal, inc=0.01)
        self.pos.SetToolTipString(
            "A value between 0 and 1 representing the distance between (x1,y1) "
            "and (x2,y2) for this gradient stop.")
        self.colour = wx.ColourPickerCtrl(self, col=colour)
        self.colour.SetToolTipString("The colour for this gradient stop")
        self.minusBtn = wx.Button(self, -1, " - ", style=wx.BU_EXACTFIT)
        self.minusBtn.SetToolTipString("Remove this gradient stop")

        # put them in a sizer
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.pos, 0, wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(self.colour, 0, wx.ALIGN_CENTER_VERTICAL|wx.LEFT, 10)
        sizer.Add(self.minusBtn, 0, wx.ALIGN_CENTER_VERTICAL|wx.LEFT, 25)
        border = wx.BoxSizer()
        border.Add(sizer, 1, wx.EXPAND|wx.ALL, 4)
        self.SetSizer(border)

        self.Bind(wx.EVT_BUTTON, self.OnMinusButton, self.minusBtn)

        
    def OnMinusButton(self, evt):
        wx.CallAfter(self.Parent.RemoveStop, self)
        
        
    
class TestPanel(wx.Panel):
    """
    The main panel for this sample
    """
    def __init__(self, parent, log):
        self.log = log
        wx.Panel.__init__(self, parent, -1)

        # make the panel that will display the gradient
        self.gpanel = GradientPanel(self)

        # and the other widgets for collecting data about the gradient
        label1 = wx.StaticText(self, -1, "Geometry")
        label1.SetFont(wx.FFont(15, wx.SWISS, wx.FONTFLAG_BOLD))
        x1 = 0
        x2 = self.gpanel.Size.width
        y0 = self.gpanel.Size.height / 2
        self.x1 = wx.TextCtrl(self, value=str(x1), size=(50,-1))
        self.y1 = wx.TextCtrl(self, value=str(y0), size=(50,-1))
        self.x2 = wx.TextCtrl(self, value=str(x2), size=(50,-1))
        self.y2 = wx.TextCtrl(self, value=str(y0), size=(50,-1))

        label2 = wx.StaticText(self, -1, "Stops")
        label2.SetFont(wx.FFont(15, wx.SWISS, wx.FONTFLAG_BOLD))
        firstStop = GradientStopPanel(self, 0.0)
        lastStop =  GradientStopPanel(self, 1.0, wx.WHITE)
        self.stops = [firstStop, lastStop]
        addStopBtn = wx.Button(self, -1, " + ", style=wx.BU_EXACTFIT)

        
        # bind some events
        self.Bind(wx.EVT_BUTTON, self.OnAddStop, addStopBtn)
        self.Bind(wx.EVT_SPINCTRLDOUBLE, self.OnPositionUpdated)
        self.Bind(wx.EVT_COLOURPICKER_CHANGED, self.OnColourChanged)
        self.Bind(wx.EVT_TEXT, self.OnGeometryChanged)
        
        
        # do the layout
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.gpanel)
        sizer.Add((1,15))
        sizer.Add(label1)
        sizer.Add((1, 5))

        fgs = wx.FlexGridSizer(cols=11, hgap=5, vgap=5)
        fgs.AddMany([ (wx.StaticText(self, -1, "x1:"), 0, wx.ALIGN_CENTER_VERTICAL),
                      self.x1,
                      ((5,1)),
                      (wx.StaticText(self, -1, "y1:"), 0, wx.ALIGN_CENTER_VERTICAL),
                      self.y1] )
        fgs.Add((40,1))
        fgs.AddMany([ (wx.StaticText(self, -1, "x2:"), 0, wx.ALIGN_CENTER_VERTICAL),
                      self.x2,
                      ((5,1)),
                      (wx.StaticText(self, -1, "y2:"), 0, wx.ALIGN_CENTER_VERTICAL),
                      self.y2] )
        sizer.Add(fgs, 0, wx.LEFT, 25)
        
        sizer.Add((1,15))
        sizer.Add(label2)
        sizer.Add((1, 5))
        self.stopSizer = wx.BoxSizer(wx.VERTICAL)
        self.stopSizer.Add(firstStop)
        self.stopSizer.Add(lastStop)
        self.stopSizer.Add(addStopBtn, 0, wx.TOP, 10)
        sizer.Add(self.stopSizer, 0, wx.LEFT, 25)
        
        border = wx.BoxSizer()
        border.Add(sizer, 1, wx.EXPAND|wx.ALL, 15)
        self.SetSizer(border)

        self.SetLimits()
        self.UpdateBrush()
        
        
    def RemoveStop(self, stop):
        self.stops.remove(stop)
        stop.Destroy()
        self.Layout()
        self.SetLimits()
        self.UpdateBrush()
        

    def OnAddStop(self, evt):
        newstop = GradientStopPanel(self, 1.0)
        self.stopSizer.Insert(len(self.stops), newstop)
        self.stops.append(newstop)
        self.Layout()
        self.SetLimits()
        self.UpdateBrush()

        
    def OnPositionUpdated(self, evt):
        # called when any of the spinctrls in the nested panels are updated
        self.SetLimits()
        self.UpdateBrush()

        
    def OnColourChanged(self, evt):
        # called when any of the color pickers are updated
        self.UpdateBrush()

    def OnGeometryChanged(self, evt):
        # called for changes to x1,y1 or x2,y2
        self.UpdateBrush()

        
    def SetLimits(self):
        # Tweak the panels in self.stops to set limits on the allowed
        # positions, and to disable those that should not be changed or
        # removed.
        first = self.stops[0]
        last = self.stops[-1]
        
        first.pos.Disable()
        first.minusBtn.Disable()
        last.pos.Disable()
        last.minusBtn.Disable()
        
        for idx in range(1, len(self.stops)-1):
            prev = self.stops[idx-1]
            next = self.stops[idx+1]
            stop = self.stops[idx]
            
            stop.pos.SetMin(prev.pos.GetValue())
            stop.pos.SetMax(next.pos.GetValue())
            stop.pos.Enable()
            stop.minusBtn.Enable()

            
    def UpdateBrush(self):
        """
        This is where the magic happens. We convert all the values from the
        widgets on this panel into a collection of gradient stops and then
        create a brush from them, and finally, ask the display panel to
        repaint itself with that new brush.
        """
        def floatOrZero(value):
            try:
                return float(value)
            except ValueError:
                return 0.0
            
        x1 = floatOrZero(self.x1.Value)
        y1 = floatOrZero(self.y1.Value)
        x2 = floatOrZero(self.x2.Value)
        y2 = floatOrZero(self.y2.Value)
        
        gstops = g.GraphicsGradientStops()
        gstops.SetStartColour(self.stops[0].colour.GetColour())
        gstops.SetEndColour(self.stops[-1].colour.GetColour())
        for s in self.stops[1:-1]:
            gs = g.GraphicsGradientStop(
                s.colour.GetColour(), s.pos.GetValue())
            gstops.Add(gs)
            
        ctx = g.GraphicsContext.CreateMeasuringContext()
        brush = ctx.CreateLinearGradientBrush(x1,y1, x2,y2, gstops)
        self.gpanel.DrawWithBrush(brush)
        
        
        
#----------------------------------------------------------------------

def runTest(frame, nb, log):
    win = TestPanel(nb, log)
    return win

#----------------------------------------------------------------------



overview = """<html><body>
<h2><center> Gradients on a GraphicsContext </center></h2>

Multi-stop gradients can be used as fills in a wx.GraphicsContext.
This sample shows how to use the GraphicsGradientStops class to
accomplish this and allows the user to experiment with gradients.

</body></html>
"""



if __name__ == '__main__':
    import sys,os
    import run
    run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])