Source code for pi3d.util.Gui

from __future__ import absolute_import, division, print_function, unicode_literals

import sys, os, time

import pi3d

DTK = 0.05 #minimum time between key strokes
DTM = 0.15 #minimum time between mouse clicks

[docs]class Gui(object): def __init__(self, font, show_pointer=True, fontscale=0.24): """hold information on all widgets and the pointer, creates a 2D Camera and uv_flat shader. Needs to have a Font object passed to it keeps track of when the last mouse click or key stroke to avoid double counting. Arguments *font* pi3d.Font object A Gui instance has to be passed to each gui widget (Button, Menu etc) as it is created to allow resources to the used and for checking. """ self.shader = pi3d.Shader("uv_flat") dummycam = pi3d.Camera.instance() # in case this is prior to one being created self.camera = pi3d.Camera(is_3d=False) self.widgets = [] self.font = font self.font.blend = True self.focus = None self.show_pointer = show_pointer self.fontscale = fontscale for p in sys.path: icon_path = p + '/pi3d/util/icons/' if os.path.exists(icon_path): self.icon_path = icon_path break if self.show_pointer: tex = pi3d.Texture(self.icon_path + "pointer.png", blend=True, mipmap=False) self.pointer = pi3d.Sprite(camera=self.camera, w=tex.ix, h=tex.iy, z=0.5) self.pointer.set_draw_details(self.shader, [tex]) self.p_dx, self.p_dy = tex.ix/2.0, -tex.iy/2.0 self.next_tm = time.time() + DTM
[docs] def draw(self, x, y): """draw all visible widges and pointer at x, y """ for w in self.widgets: if w.visible: w.draw() if self.show_pointer: self.pointer.position(x + self.p_dx, y + self.p_dy, 0.5) self.pointer.draw()
[docs] def check(self, x, y): tm = time.time() if tm < self.next_tm: return self.next_tm = tm + DTM for w in self.widgets: if w.visible and w.check(x, y): self.focus = w break
[docs] def checkkey(self, k): tm = time.time() if tm < self.next_tm: return self.next_tm = tm + DTK if type(self.focus) is TextBox: self.focus._click(k) else: for w in self.widgets: if w.visible and w.checkkey(k): self.focus = w break
[docs]class Widget(object): def __init__(self, gui, shapes, x, y, callback=None, label=None, label_pos='left', shortcut=None): """contains functionality of buttons and is inherited by other gui components. Arguments: *gui* Gui object parent to this widget *shapes* drawable object such as Sprite or String, or list of two to toggle *x,y* location of top left corner of this widget *label* string to use as label *label_pos* position of label 'left', 'right', 'above', 'below' *shortcut* shortcut key to have same effect as clicking wit mouse??? """ if not (type(shapes) is list or type(shapes) is tuple): self.shapes = [shapes] else: self.shapes = shapes self.toggle = len(self.shapes) > 1 #this widget can toggle between two self.clicked = False self.callback = callback self.shortcut = shortcut self.label_pos = label_pos if label is not None: self.labelobj = pi3d.String(font=gui.font, string=label, is_3d=False, camera=gui.camera, justify='L', size=gui.fontscale) self.labelobj.set_shader(gui.shader) else: self.labelobj = None self.relocate(x, y) if not (self in gui.widgets): #because TextBox re-runs Widget.__init__ gui.widgets.append(self) self.visible = True
[docs] def relocate(self, x, y): if len(self.shapes[0].buf[0].array_buffer) > 0: b = self.shapes[0].get_bounds() else: b = [x, y, 1.0, x, y, 1.0] self.bounds = [x, y - b[4] + b[1], x + b[3] - b[0], y] for s in self.shapes: s.position(x - b[0], y - b[4], 1.0) if self.labelobj is not None: b = self.labelobj.get_bounds() if self.label_pos == 'above': self.labelobj.position((x + self.bounds[2] - b[3] - b[0]) / 2.0, y - b[1], 1.0) elif self.label_pos == 'below': self.labelobj.position((x + self.bounds[2] - b[3] - b[0]) / 2.0, self.bounds[1] - b[4], 1.0) elif self.label_pos == 'right': self.labelobj.position(self.bounds[2] + b[0] - 5.0, (y + self.bounds[1] - b[1] - b[4]) / 2.0, 1.0) else: self.labelobj.position(x - b[3] - 5.0, (y + self.bounds[1] - b[1] - b[4]) / 2.0, 1.0)
[docs] def draw(self): if self.toggle and self.clicked: self.shapes[1].draw() else: self.shapes[0].draw() for s in self.shapes[2:]: s.draw() if self.labelobj: self.labelobj.draw()
[docs] def check(self, x, y): if x > self.bounds[0] and x < self.bounds[2] and y > self.bounds[1] and y < self.bounds[3]: self._click(x, y) return True return False
[docs] def checkkey(self, k): if k == self.shortcut: self._click(k) return True return False
def _click(self, *args): if self.toggle: self.clicked = not self.clicked if self.callback: self.callback(args)
[docs]class Button(Widget): def __init__(self, gui, imgs, x, y, callback=None, label=None, label_pos='left', shortcut=None): """This inherits pretty much everything it needs from Widget, it just takes a list of images rather than Shapes. Otherwise same Arguments. *imgs* list of strings. If these exist as files using the path and file name then those will be used. Otherwise the pi3d/util/icons path will be prepended """ if not (type(imgs) is list or type(imgs) is tuple): imgs = [imgs] shapes = [] for i in imgs: if not os.path.isfile(i): i = gui.icon_path + i tex = pi3d.Texture(i, blend=True, mipmap=False) shape = pi3d.Sprite(camera=gui.camera, w=tex.ix, h=tex.iy, z=2.0) shape.set_draw_details(gui.shader, [tex]) shapes.append(shape) super(Button, self).__init__(gui, shapes, x, y, callback=callback, label=label, label_pos=label_pos, shortcut=shortcut)
[docs]class Radio(Button): def __init__(self, gui, x, y, callback=None, label=None, label_pos='left', shortcut=None): """This is a toggle button with two checkbox images. Same Arguments as Widget but missing the list of Shapes completely. """ super(Radio, self).__init__(gui, ['cba0.gif', 'cba1.gif'], x, y, callback=callback, label=label, label_pos=label_pos, shortcut=shortcut)
[docs]class Scrollbar(Widget): def __init__(self, gui, x, y, width, start_val=None, callback=None, label=None, label_pos='left', shortcut=None): """This consists of four Shapes but the first one is duplicated so the Widget.draw() method can be used unchanged. The images are hard coded so no list of Shapes or images needs to be supplied however arguments additional to those for Widget are *width* width of the scroll (excluding buttons on either end) *start_val* proportion of the way across i.e. if width = 200 then start_val of 150 would be three quarters NB the callback is called with *args equal to the position of the slider so the function needs to be defined with this in mind i.e. ``def cb(*args):`` args will then be available as a tuple (0.343,) """ imgs = ["scroll.gif", "scroll.gif", "scroll_lh.gif", "scroll_rh.gif", "scroll_thumb.gif"] shapes = [] end_w = 0 for i, im in enumerate(imgs): tex = pi3d.Texture(gui.icon_path + im, blend=True, mipmap=False) w = tex.ix if i > 0 else width if i == 2: end_w = tex.ix #offsets for end buttons if i == 4: thumb_w = tex.ix / 2.0 #offset for thumb shape = pi3d.Sprite(camera=gui.camera, w=w, h=tex.iy, z=2.0) shape.set_draw_details(gui.shader, [tex]) shapes.append(shape) super(Scrollbar, self).__init__(gui, shapes, x, y, callback=callback, label=label, label_pos=label_pos, shortcut=shortcut) self.toggle = False self.t_stop = [self.bounds[0] + thumb_w, self.bounds[2] - thumb_w] if start_val is None: start_val = width / 2.0 self.thumbpos = start_val / width * (self.t_stop[1] - self.t_stop[0]) self.shapes[4].positionX(self.t_stop[0] + self.thumbpos) self.shapes[4].translateZ(-0.1) self.shapes[2].translateX((-width - end_w) / 2.0) self.shapes[3].translateX((width + end_w) / 2.0) self.bounds[0] -= end_w self.bounds[2] += end_w if self.labelobj is not None: if label_pos == 'left': self.labelobj.translateX(-end_w) elif label_pos == 'right': self.labelobj.translateX(end_w) def _click(self, *args): thumb_x = self.t_stop[0] + self.thumbpos if len(args) == 2: x, y = args if x < thumb_x and thumb_x > self.t_stop[0]: self.thumbpos -= (thumb_x - x) / 2.0 if x > thumb_x and thumb_x < self.t_stop[1]: self.thumbpos += (x - thumb_x) / 2.0 if self.thumbpos < 0: self.thumbpos = 0 if self.thumbpos > (self.t_stop[1] - self.t_stop[0]): self.thumbpos = (self.t_stop[1] - self.t_stop[0]) self.shapes[4].positionX(self.t_stop[0] + self.thumbpos) if self.callback: self.callback((thumb_x - self.t_stop[0])/(self.t_stop[1] - self.t_stop[0]))
[docs]class TextBox(Widget): def __init__(self, gui, txt, x, y, callback=None, label=None, label_pos='left', shortcut=None): self.gui = gui self.txt = txt self.x = x self.y = y self.callback = callback self.label = label self.label_pos = label_pos self.shortcut = shortcut self.cursor = len(txt) tex = pi3d.Texture(gui.icon_path + 'tool_stop.gif', blend=True, mipmap=False) self.cursor_shape = pi3d.Sprite(camera=gui.camera, w=tex.ix/10.0, h=tex.iy, z=1.1) self.cursor_shape.set_draw_details(gui.shader, [tex]) self.recreate()
[docs] def recreate(self): self.c_lookup = [] #mapping between clicked char and char in string for i, l in enumerate(self.txt): if l != '\n': self.c_lookup.append(i) textbox = pi3d.String(font=self.gui.font, string=self.txt, is_3d=False, camera=self.gui.camera, justify='L', z=1.0) textbox.set_shader(self.gui.shader) super(TextBox, self).__init__(self.gui, [textbox], self.x, self.y, callback=self.callback, label=self.label, label_pos=self.label_pos, shortcut=self.shortcut)
def _get_charindex(self, x, y): """Find the x,y location of each letter's bottom left and top right vertices to return a character index of the click x,y """ verts = self.shapes[0].buf[0].array_buffer[:,0:3] x = x - self.x + verts[2][0] y = y - self.y + verts[0][1] nv = len(verts) for i in range(0, nv, 4): vtr = verts[i] # top right vbl = verts[i + 2] # bottom left if x >= vbl[0] and x < vtr[0] and y >= vbl[1] and y < vtr[1]: i = int(i / 4) c_i = self.c_lookup[i] if c_i == (len(self.txt) - 1) or self.c_lookup[i + 1] > c_i + 1: if (vtr[0] - x) < (x - vbl[0]): c_i += 1 return c_i return len(self.txt) def _get_cursor_loc(self, i): verts = self.shapes[0].buf[0].array_buffer[:,0:3] maxi = int(len(verts) / 4 - 1) if maxi < 0: return self.x, self.y if i > maxi: x = self.x - verts[2][0] + verts[maxi * 4][0] y = self.y - verts[0][1] + (verts[maxi * 4][1] + verts[maxi * 4 + 2][1]) / 2.0 else: x = self.x - verts[2][0] + verts[i * 4 + 2][0] y = self.y - verts[0][1] + (verts[i * 4][1] + verts[i * 4 + 2][1]) / 2.0 return x, y
[docs] def checkkey(self, k): """have to use a slightly different version without the _click() call """ if k == self.shortcut: return True return False
def _click(self, *args): if len(args) == 2: #mouse click x, y = args self.cursor = self._get_charindex(x, y) else: #keyboard input k = args[0] if k == '\t': #backspace use tab char if self.cursor > 0: self.txt = self.txt[:(self.cursor - 1)] + self.txt[self.cursor:] self.cursor -= 1 elif k == '\r': #delete use car ret char self.txt = self.txt[:self.cursor] + self.txt[(self.cursor + 1):] else: self.txt = self.txt[:self.cursor] + k + self.txt[(self.cursor):] self.cursor += 1 self.recreate() super(TextBox, self)._click()
[docs] def draw(self): if self.gui.focus == self: x, y = self._get_cursor_loc(self.cursor) self.cursor_shape.position(x, y, 1.1) self.cursor_shape.draw() super(TextBox, self).draw()