#Todo#-----##Give windows Z-depth so that they fade into the background in order of focus.#I think this can easily be done by list splicing and appends - DONEfrom Blender.BGL import *import Blenderfrom math import sin, cos, pifrom statictype import static_castfrom Blender.Draw import *class colour: grey_20 = (0.2, 0.2, 0.2, 1.0) grey_40 = (0.4, 0.4, 0.4, 1.0) grey_60 = (0.6, 0.6, 0.6, 1.0) grey_80 = (0.8, 0.8, 0.8, 1.0) white = (1.0, 1.0, 1.0, 1.0) black = (0.0, 0.0, 0.0, 1.0) dark_grey = (0.3, 0.3, 0.3, 1.0) mid_grey = (0.5, 0.5, 0.5, 1.0) light_grey = (0.7, 0.7, 0.7, 1.0) orange = (1.0, 0.5, 0.0, 1.0) darkorange = (0.8, 0.35, 0.0, 1.0) selected = grey_20 connector = grey_20 text = grey_60 list = grey_60 background = grey_40 #form background colour grid = dark_grey #grid colour window = (0.2, 0.2, 0.2, 0.75) #base window colour windowsel = (0.2, 0.2, 0.2, 0.5) #selected window colour titlebar = dark_grey #base title bar colour titlebarsel = (0.4, 0.4, 0.4, 0.75) #selected title bar colour outline = grey_20 #window outline colour outlinesel = mid_grey #selected window outline colour titletext = grey_60 #window title text colour titletextsel = grey_20 #selected window title text colour button = grey_40 buttonsel = grey_20 buttonoutline = grey_20 buttonoutsel = grey_20 buttontext = grey_20 buttontextsel = grey_60def get_window_size(): #gets almost correct window size box = Buffer(GL_INT, 4) #still needs offset correction glGetIntegerv(GL_SCISSOR_BOX, box) #better than Blender.Window.GetAreaSize return boxdef get_mouse_coords(): #gets the mouse coordinates in local space x, y, w, h = get_window_size() xm, ym = Blender.Window.GetMouseCoords() return xm - x, ym - ydef offset_correct(): #will be used for offsetting the blender header return 0def constrain_canvas(x, y, w, h): glEnable(GL_SCISSOR_TEST) glPushAttrib(GL_VIEWPORT) #save the viewport glPushAttrib(GL_SCISSOR_BIT) glMatrixMode(GL_PROJECTION) glPushMatrix() #save the projection matrix glLoadIdentity() #return proj matrix to identity offset = offset_correct() #correct header offset problems glOrtho(0, w, 0 + offset, h, -1.0, 1.0) #set coordinate system glScissor(x, y, w, h) glViewport(x, y, w, h)def unconstrain_canvas(): glPopAttrib() #return scissor box to old state glPopAttrib() #return viewport box to old state glPopMatrix() #return proj matrix to old statedef no_event(): #simple no event for buttons print 'No event' return@static_cast(int, int)def glScaledRasterPos2i(x, y): viewMatrix = Buffer(GL_FLOAT, 16) #space for modelview matrix glGetFloatv(GL_MODELVIEW_MATRIX, viewMatrix) #get modelview f = 1/viewMatrix[0] #get scale factor glRasterPos2f(x*f, y*f) #instead of the usual glRasterPos2i(x, y)def glLine(x1, y1, x2, y2, col=None): #draw a line between two points if col: glColor4f(*col) glBegin(GL_LINES) glVertex2i(x1,y1) glVertex2i(x2,y2) glEnd()def glBox(x1, y1, x2, y2, col=None, type=GL_POLYGON): #draw a box between bottom-left (x1,y1)... if col: #and top-right (x2, y2) glColor4f(*col) glBegin(type) glVertex2i(x1,y1) glVertex2i(x2,y1) glVertex2i(x2,y2) glVertex2i(x1,y2) glEnd()def glArc(x, y, rad, beg=0, end=90, steps=12): #draw an arc - used by glRoundedBox... beg = pi / 180 * beg #to round off the corners end = pi / 180 * end ang_r = end - beg ang_per_step = ang_r / steps for step in range(steps): xc = x + rad * cos(ang_per_step * step + beg) yc = y + rad * sin(ang_per_step * step + beg) glVertex2f(xc, yc)def glRoundedBox(x1, y1, x2, y2, rad=5, col=None, type=GL_POLYGON, flags=15): if col: glColor4f(*col) if y1 > y2: y1, y2 = y2, y1 #swap the y coords if y1 is bigger than y2 glBegin(type) if flags & 1 == 1: glArc(x1 + rad, y1 + rad, rad, 180, 270) else: glVertex2i(x1, y1) if flags & 2 == 2: glArc(x2 - rad, y1 + rad, rad, 270, 360) else: glVertex2i(x2, y1) if flags & 4 == 4: glArc(x2 - rad, y2 - rad, rad, 0, 90) else: glVertex2i(x2, y2) if flags & 8 == 8: glArc(x1 + rad, y2 - rad, rad, 90, 180) else: glVertex2i(x1, y2) glEnd()def glInsetBox(x1, y1, x2, y2, inset=1): glBox(x1, y1, x2, y2, colour.grey_20) glBox(x1 + inset, y1, x2, y2 - inset, colour.grey_60) glBox(x1 + inset, y1 + inset, x2 - inset, y2 - inset, colour.grey_40)def glOutsetBox(x1, y1, x2, y2, outset=1): glBox(x1, y1, x2, y2, colour.grey_60) glBox(x1 + outset, y1, x2, y2 - outset, colour.grey_20) glBox(x1 + outset, y1 + outset, x2 - outset, y2 - outset, colour.grey_40)class Element: def __init__(self, x, y, w, h, visible): self.visible = True #state variables self.x1 = x self.y1 = y self.x2 = x + w self.y2 = y + h self.h = h self.w = w self.dx = self.dy = 0 self.parent = None def absolute_coordinates(self): #this recurses through the hierarchy to if self.parent: #get the absolute coordinates for a particular x, y = self.parent.absolute_coordinates() #elements so that we can do hit testing etc. return self.x1 + x, self.y1 + y else: return self.x1, self.y1 def set_focus(self, element): #routine to set z level based on focus self.elements.remove(element) #not all elements will be able to use this self.elements.extend([element])class Layer(Element): '''Standard interface layer''' @static_cast(None, int, int, int) #make everything integers for opengl def __init__(self, x, y, w, h): Element.__init__(self, x, y, w, h, True) self.elements = [] #store ui elements def draw(self): if not self.visible: return False #return if the layer isnt drawn (maybe used for caching later) glEnable(GL_SCISSOR_TEST) #enable scissor testing glPushAttrib(GL_SCISSOR_BIT) #save the old scissor box glScissor(self.x1, self.y1, self.w, self.h) #constrain drawing to layer bounds for element in self.elements: #draw all elements element.draw() glPopAttrib() #return scissor box to old state def link(self, element): #link an element to the layer element.parent = self self.elements.append(element) def event(self, evt, val): x, y = Interface.mloc #make a reversed copy of the element list for events... evtlist = self.elements[:] #since the top item is the last in the list evtlist.reverse() for element in evtlist: x1, y1 = element.absolute_coordinates() x2, y2 = x1 + element.w, y1 + element.h if x1 < x < x2 and y1 < y < y2: #if mouse coords are inside the element pass to the element... handled = element.event(evt, val) #event handler if handled: return True return Falseclass GridLayer(Layer): '''Standard interface layer with grid background''' @static_cast(None, int, int, int, int) #make everything integers for opengl def __init__(self, x, y, w, h, step): self.step = step #step variable for distance between lines Layer.__init__(self, x, y, w, h) def draw(self): if not self.visible: return False #return if the layer isnt drawn (maybe used for caching later) #glDisable(GL_LINE_SMOOTH) #straight lines so no AA needed for y in range(self.y1, self.y2, self.step): #draw grid lines glLine(self.x1, y, self.x2, y, colour.grid) for x in range(self.x1, self.x2, self.step): glLine(x, self.y1, x, self.y2, colour.grid) #glEnable(GL_LINE_SMOOTH) #reenable AA for lines Layer.draw(self) #call to superclass draw method to draw everything elseclass Window(Element): @static_cast(None, int, int, int, int, title=str, visible=bool, radius=int) def __init__(self, x, y, w, h, title='Window', visible=True, radius=10): Element.__init__(self, x, y, w, h, True) self.title = title #title of the window self.title_height = 24 #titelbar height self.has_title = True #does it have a titlebar self.radius = radius #radius of corners self.elements = [] #elements of the window self.sel = False #is it currently selected def draw(self): #glDisable(GL_LINE_SMOOTH) #smoothed lines look bad for this if self.has_title: #draw the window itself with title bar titlecol = colour.titlebar if not self.sel else colour.titlebarsel #set colours depending... outcol = colour.outline if not self.sel else colour.outlinesel #on selection status wincol = colour.window if not self.sel else colour.windowsel glRoundedBox(self.x1, self.y1, self.x2, self.y2 - self.title_height, self.radius, wincol, type=GL_POLYGON, flags=3) glRoundedBox(self.x1, self.y2 - self.title_height, self.x2, self.y2, self.radius, titlecol, type=GL_POLYGON, flags=12) glLine(self.x1, self.y2 - self.title_height, self.x2, self.y2 - self.title_height, outcol) if self.sel: glColor4f(*colour.titletextsel) else: glColor4f(*colour.titletext) glScaledRasterPos2i(self.x1 + 16, self.y2 - self.title_height + 6) Blender.Draw.Text(self.title, 'large') else: #draw the window itself without title bar glRoundedBox(self.x1, self.y1, self.x2, self.y2, self.radius, colour.window, type=GL_POLYGON) glRoundedBox(self.x1, self.y1, self.x2, self.y2, self.radius, outcol, type=GL_LINE_LOOP) #glEnable(GL_LINE_SMOOTH) #reenable AA line constrain_canvas(self.x1, self.y1, self.w, self.h - self.title_height) #limit drawing to this window for element in self.elements: #draw all elements element.draw() unconstrain_canvas() #allow drawing on parent def event(self, evt, val): x, y = Interface.mloc #mouse loc ax, ay = self.absolute_coordinates() #absolute coords of element rx, ry = x - ax, y - ay #relative coords if evt == LEFTMOUSE and val == 1: #left mouse button down if 0 < rx < self.w and self.h - self.title_height < ry < self.h: #if clicked on the titlebar... self.parent.set_focus(self) #set the focus to the current window self.sel = True #then set selectection status and ... Interface.focus = self #main interface focus and... self.dx = x - self.x1 #set deltas for moving element self.dy = y - self.y1 else: #if clicked elsewhere then... for element in self.elements: #check which element to pass the event to if element.x1 < rx < element.x2 and element.y1 < ry < element.y2: handled = element.event(evt, val) if handled: self.parent.set_focus(self) #set the focus to the current window return True return False elif evt == LEFTMOUSE and val == 0: #left mouse button up self.sel = False if Interface.focus is self: Interface.focus = None elif evt == MOUSEX or evt == MOUSEY: #mouse move if self.sel: self.x1 = -self.dx + x self.y1 = -self.dy + y self.x2 = self.x1 + self.w self.y2 = self.y1 + self.h def link(self, element): #link element to window element.parent = self self.elements.append(element)class Button(Element): @static_cast(int, int, int, int, str, None, visible=bool, radius=int) def __init__(self, x, y, w, h, text, event, visible=True, radius=10): Element.__init__(self, x, y, w, h, visible) self.text = text #button text self.evt = event #callback self.radius = radius #button radius self.sel = 0 #selection status def draw(self): if self.sel: #button down glRoundedBox(self.x1, self.y1, self.x2, self.y2, col=colour.buttonsel, rad=self.radius) glRoundedBox(self.x1, self.y1, self.x2, self.y2, col=colour.buttonoutsel, type=GL_LINE_LOOP, rad=self.radius) else: #button up glRoundedBox(self.x1, self.y1, self.x2, self.y2, col=colour.button, rad=self.radius) glRoundedBox(self.x1, self.y1, self.x2, self.y2, col=colour.buttonoutline, type=GL_LINE_LOOP, rad=self.radius) x, y = self.absolute_coordinates() #get button abs coords constrain_canvas(x, y, self.w, self.h) #set bounding box width = Blender.Draw.GetStringWidth(self.text) #calculate offset for centering x offsetx = (self.w - width) / 2 height = 10 #calculate offset for centering y offsety = (self.h - height)/ 2 glScaledRasterPos2i(offsetx, offsety) #offset the text tcol = colour.buttontext if not self.sel else colour.buttontextsel glColor4f(*tcol) Blender.Draw.Text(self.text) #draw text unconstrain_canvas() #unset bounding box def event(self, evt, val): x, y = Interface.mloc #mouse loc ax, ay = self.absolute_coordinates() #absolute coords of element rx, ry = x - ax, y - ay #relative coords if evt == LEFTMOUSE and val == 1: self.parent.set_focus(self) self.sel = True Interface.focus = self elif evt == LEFTMOUSE and val == 0: if 0 < rx < self.w and 0 < ry < self.h: self.evt() self.sel = 0 Interface.focus = None return 1 elif evt == MOUSEX or evt == MOUSEY: if 0 < rx < self.w and 0 < ry < self.h: self.sel = 1 else: self.sel = 0class Interface(Element): focus = None mloc = [0,0] def __init__(self): self.elements = [] #storage for interface elements x,y,w,h = get_window_size() Element.__init__(self, x, y, w, h, True) self.parent = None #needed for recursive absolute coords def draw(self): x,y,w,h = get_window_size() glShadeModel(GL_SMOOTH) #set up gl state variables glDisable(GL_LINE_SMOOTH) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) glClearColor(*colour.background) glClear(GL_COLOR_BUFFER_BIT) for element in self.elements: #draw everything element.draw() def event(self, evt, val=0): Interface.mloc = get_mouse_coords() if Interface.focus: handled = Interface.focus.event(evt, val) if handled: return for element in self.elements: handled = element.event(evt, val) if handled: return Blender.Draw.Redraw() def launch(self): Blender.Draw.Register(self.draw, self.event, self.event)#start the interface def link(self, element): #link an element to the ui element.parent = self self.elements.append(element)x,y,w,h = get_window_size()box = Window(100, 100, 400, 300, 'Test Window', radius=10)box2 = Window(50, 50, 300, 200, 'Window 2', radius=15)but = Button(10, 50, 64, 24, 'Test', no_event)box.link(but)canvas = GridLayer(x, 0, w, h, 30)canvas.link(box)#canvas.link(box2)ui = Interface()ui.link(canvas)ui.launch()