367 lines
15 KiB
Python
367 lines
15 KiB
Python
import clutter
|
|
import pango
|
|
import pygtk
|
|
import gtk
|
|
from InputQueue import InputQueue
|
|
|
|
class LabelList(clutter.Group):
|
|
DIRECTION_UP, DIRECTION_DOWN = range(2)
|
|
|
|
fps = 35
|
|
frames = 25
|
|
|
|
#Default font
|
|
font_string = "Tahoma 30"
|
|
item_gap = 0
|
|
|
|
def __init__(self, length):
|
|
clutter.Group.__init__(self)
|
|
self.items = []
|
|
|
|
#Setup input queue controller
|
|
self.input_queue = InputQueue()
|
|
self.input_queue.set_action(InputQueue.NORTH, self.move_up)
|
|
self.input_queue.set_action(InputQueue.SOUTH, self.move_down)
|
|
|
|
self.selected = 0
|
|
self.displayMin = 0 #The number of menu items that will be shown at a time
|
|
self.displayMax = length
|
|
self.displaySize = self.displayMax - self.displayMin
|
|
|
|
self.inactive_item_background = None
|
|
self.background_group = clutter.Group()
|
|
self.background_group.show()
|
|
self.add(self.background_group)
|
|
|
|
#Selector bar image, moves with selections to show current item
|
|
self.selector_bar = None
|
|
|
|
def setup_from_theme_id(self, themeMgr, id):
|
|
element = themeMgr.search_docs("label_list", id).childNodes
|
|
#Quick check to make sure we found something
|
|
if element is None:
|
|
return None
|
|
|
|
self.item_gap = int(themeMgr.find_child_value(element, "item_gap"))
|
|
self.displayMax = int(themeMgr.find_child_value(element, "num_visible_elements"))
|
|
|
|
#Grab the font
|
|
font_node = themeMgr.get_subnode(element, "font")
|
|
fontString = themeMgr.get_font("main", font_node)
|
|
self.font = fontString
|
|
|
|
#Set the selection effect steps
|
|
self.zoomStep0 = float(themeMgr.find_child_value(element, "scale_step0"))
|
|
self.zoomStep1 = float(themeMgr.find_child_value(element, "scale_step1"))
|
|
self.zoomStep2 = float(themeMgr.find_child_value(element, "scale_step2"))
|
|
self.opacityStep0 = int(themeMgr.find_child_value(element, "opacity_step0"))
|
|
self.opacityStep1 = int(themeMgr.find_child_value(element, "opacity_step1"))
|
|
self.opacityStep2 = int(themeMgr.find_child_value(element, "opacity_step2"))
|
|
|
|
self.fps = int(themeMgr.find_child_value(element, "transition_fps"))
|
|
self.frames = int(themeMgr.find_child_value(element, "transition_frames"))
|
|
|
|
|
|
|
|
themeMgr.setup_actor(self, element, themeMgr.stage)
|
|
|
|
img_element = themeMgr.search_docs("label_list", id).getElementsByTagName("texture")
|
|
if len(img_element) > 0: img_element = img_element[0].childNodes
|
|
#img_element = themeMgr.find_element(element, "id", "inactive_background")
|
|
self.inactive_item_background = themeMgr.get_texture("inactive_background", self, element = img_element)
|
|
|
|
def on_key_press_event (self, event):
|
|
self.input_queue.input(event)
|
|
return self.timeline
|
|
|
|
def add_item(self, itemLabel):
|
|
if len(self.items) == 0:
|
|
#Perform a cheap hack to figure out the height of a label
|
|
tempLabel = clutter.Label()
|
|
tempLabel.set_font_name(self.font_string)
|
|
tempLabel.set_text("S")
|
|
|
|
self.label_height = tempLabel.get_height()
|
|
label_width = 0
|
|
|
|
label_y = len(self.items) * (self.label_height + self.item_gap)
|
|
|
|
#If a background pic is specified in the theme, clone it and add
|
|
if not self.inactive_item_background is None:
|
|
bg_img = clutter.CloneTexture(self.inactive_item_background)
|
|
bg_img.set_height(self.label_height)
|
|
bg_img.set_width(self.get_width())
|
|
bg_img.set_position(0, label_y)
|
|
bg_img.show()
|
|
self.background_group.add(bg_img)
|
|
|
|
|
|
newItem = ListItem(self.font_string, itemLabel, label_list = self)
|
|
newItem.set_position(0, label_y)
|
|
newItem.show()
|
|
self.items.append(newItem)
|
|
|
|
self.add(newItem)
|
|
return newItem
|
|
|
|
#Removes all items from the list
|
|
def clear(self):
|
|
for item in self.items:
|
|
self.remove(item)
|
|
item = None
|
|
self.items = []
|
|
self.selected = 0
|
|
|
|
|
|
def display(self):
|
|
if self.displayMax > len(self.items):
|
|
self.displayMax = len(self.items)
|
|
self.displaySize = self.displayMax - self.displayMin
|
|
|
|
#for i in range(self.displaySize):
|
|
# self.menuItems[i].show()
|
|
|
|
self.show()
|
|
|
|
def move_selection(self, direction):
|
|
|
|
if direction == self.DIRECTION_DOWN:
|
|
#Check if we're at the last item in the list
|
|
if (self.selected) == (len(self.items)-1):
|
|
return
|
|
else:
|
|
self.selected = self.selected+1
|
|
elif direction == self.DIRECTION_UP:
|
|
#Check if we're at the first / last item in the list
|
|
if (self.selected) == 0:
|
|
return
|
|
else:
|
|
self.selected = self.selected-1
|
|
|
|
self.timeline = clutter.Timeline (self.frames, self.fps)
|
|
self.input_queue.set_timeline(self.timeline)
|
|
|
|
|
|
#This horrible loop does all the scaling
|
|
#This includes, the selected item and the ones on either side of it
|
|
for i in range(len(self.items)):
|
|
#print str(i)
|
|
if i == self.selected:
|
|
self.items[i].scaleLabel(ListItem.SCALE_FULL, self.timeline)
|
|
elif (i == self.selected-1) and (i >= self.displayMin):
|
|
self.items[i].scaleLabel(ListItem.SCALE_MEDIUM, self.timeline)
|
|
elif (i == self.selected+1) and (i <= self.displayMax-1):
|
|
self.items[i].scaleLabel(ListItem.SCALE_MEDIUM, self.timeline)
|
|
else:
|
|
self.items[i].scaleLabel(ListItem.SCALE_NONE, self.timeline)
|
|
|
|
#Check we're at the top of the viewable list
|
|
if self.selected < (self.displayMin):
|
|
#If yes, move the menu, leave the selection bar where is
|
|
#self.menuItems[self.selected].set_opacity(0)
|
|
#self.menuItems[self.selected].show()
|
|
self.rollList( self.menuItems[self.selected], self.menuItems[self.selected+self.displaySize], self.timeline)
|
|
else:
|
|
if not self.selector_bar is None:
|
|
#move the selection bar
|
|
self.selector_bar().selectItem(self.menuItems[self.selected], self.timeline)
|
|
|
|
self.timeline.start()
|
|
|
|
def move_up(self):
|
|
self.move_selection(self.DIRECTION_UP)
|
|
|
|
def move_down(self):
|
|
self.move_selection(self.DIRECTION_DOWN)
|
|
|
|
def select_first(self, frames = 1, fps = 75):
|
|
if self.input_queue.is_in_queue():
|
|
"ERROR: Timeline should NOT be playing here!"
|
|
return
|
|
|
|
self.timeline = clutter.Timeline(frames, fps)
|
|
self.input_queue.set_timeline(self.timeline)
|
|
self.selected = 0
|
|
for i in range(0,len(self.items)):
|
|
if i == 0:
|
|
self.items[i].scaleLabel(ListItem.SCALE_FULL, self.timeline)
|
|
elif i == 1:
|
|
self.items[i].scaleLabel(ListItem.SCALE_MEDIUM, self.timeline)
|
|
else:
|
|
self.items[i].scaleLabel(ListItem.SCALE_NONE, self.timeline)
|
|
|
|
self.timeline.start()
|
|
|
|
#Just calls the above function with different arguements to result in the first item being selected in a 'prettier' manner
|
|
def select_first_elegant(self):
|
|
self.select_first(frames=self.frames, fps=self.fps)
|
|
|
|
def select_none(self, frames = 1, fps = 75):
|
|
self.timeline = clutter.Timeline(frames, fps)
|
|
self.input_queue.set_timeline(self.timeline)
|
|
self.selected = None
|
|
for i in range(0,len(self.items)):
|
|
self.items[i].scaleLabel(ListItem.SCALE_NONE, self.timeline)
|
|
|
|
self.selected = 0
|
|
self.timeline.start()
|
|
|
|
def select_none_elegant(self):
|
|
self.select_none(self.frames, self.fps)
|
|
|
|
#When the menu needs to display a new item from the top or bottom, it rolls
|
|
# The distance the menu moves is the distance (in pixels) between the incoming item and the selector bar
|
|
def rollList(self, incomingMenuItem, outgoingMenuItem, timeline):
|
|
print "Rolling: " + incomingMenuItem.data + "<------" + outgoingMenuItem.data
|
|
(group_x, group_y) = self.itemGroup.get_abs_position()
|
|
(bar_x, bar_y) = self.glossMgr.get_selector_bar().get_abs_position() # incomingMenuItem.get_menu().getMenuMgr().
|
|
(incoming_x, incoming_y) = self.glossMgr.get_selector_bar().get_true_abs_position(incomingMenuItem) #incomingMenuItem.get_abs_position()
|
|
|
|
print "Starting group position: " + str(self.itemGroup.get_abs_position())
|
|
|
|
if incoming_y > bar_y:
|
|
#Then the incoming item is below the selector bar
|
|
height_diff = int(self.glossMgr.get_selector_bar().get_height() - incomingMenuItem.get_height()) #self.glossMgr.get_selector_bar().get_height())
|
|
#print "height diff: " + str(height_diff)
|
|
gap = ((incoming_y - bar_y) - math.floor(height_diff/2)) * -1
|
|
gap = int(gap)
|
|
#gap = -65
|
|
self.displayMin = self.displayMin+1
|
|
self.displayMax = self.displayMax+1
|
|
else:
|
|
#Then the incoming item is above the selector bar
|
|
height_diff = int(self.glossMgr.get_selector_bar().get_height() - incomingMenuItem.get_height()) #self.glossMgr.get_selector_bar().get_height())
|
|
gap = bar_y - incoming_y + math.ceil(height_diff/2)
|
|
gap = int(gap)
|
|
#gap = 65
|
|
self.displayMin = self.displayMin-1
|
|
self.displayMax = self.displayMax-1
|
|
|
|
#print "Gap: " + str(gap)
|
|
new_y = (group_y + gap)
|
|
knots = (\
|
|
(group_x, group_y),\
|
|
(group_x, new_y )\
|
|
)
|
|
alpha = clutter.Alpha(timeline, clutter.ramp_inc_func)
|
|
self.behaviour1 = clutter.BehaviourPath(alpha, knots)
|
|
self.behaviour2 = clutter.BehaviourOpacity(opacity_start=outgoingMenuItem.get_opacity(), opacity_end=0, alpha=alpha)
|
|
|
|
#print "Going to: "+ str(new_y)
|
|
#print behaviour1.get_knots()
|
|
|
|
self.behaviour1.apply(self)
|
|
self.behaviour2.apply(outgoingMenuItem)
|
|
|
|
import gobject
|
|
class ListItem(clutter.Group):
|
|
#Setup signals
|
|
__gsignals__ = {
|
|
"selected": (
|
|
gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
|
|
"deselected": (
|
|
gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
|
|
}
|
|
|
|
SCALE_NONE, SCALE_MEDIUM, SCALE_FULL = range(3)
|
|
|
|
#Default values for zoom and opacity
|
|
opacity_step_full = 255
|
|
opacity_step_medium = 135
|
|
opacity_step_none = 50
|
|
scale_step_full = 1
|
|
scale_step_medium = 0.5
|
|
scale_step_none = 0.4
|
|
|
|
def __init__ (self, font, label_left = "", label_right = "", label_list = None):
|
|
clutter.Group.__init__ (self) #, menu, itemLabel, y)
|
|
self.set_anchor_point_from_gravity(clutter.GRAVITY_WEST)
|
|
|
|
self.label_left = clutter.Label()
|
|
self.label_right = clutter.Label()
|
|
|
|
#Takes the scale and opacity values from a label list, if given
|
|
if not label_list is None:
|
|
self.opacity_step_full = label_list.opacityStep0
|
|
self.opacity_step_medium = label_list.opacityStep1
|
|
self.opacity_step_none = label_list.opacityStep2
|
|
self.scale_step_full = label_list.zoomStep0
|
|
self.scale_step_medium = label_list.zoomStep1
|
|
self.scale_step_none = label_list.zoomStep2
|
|
|
|
#setup the label/s
|
|
self.add(self.label_left)
|
|
self.label_left.show()
|
|
self.label_left.set_font_name(font)
|
|
self.label_left.set_text(label_left)
|
|
self.color = clutter.Color(0xff, 0xff, 0xff, 0xdd)
|
|
self.label_left.set_color(self.color)
|
|
self.currentOpacity = 255
|
|
self.data = label_left #By default the items data is simply its label
|
|
|
|
#The width is the length of the selector bar minus its offset
|
|
#width = self.glossMgr.get_selector_bar().get_width() + self.glossMgr.get_selector_bar().get_x_offset()
|
|
#self.set_width(width)
|
|
|
|
self.label_left.set_ellipsize(pango.ELLIPSIZE_END)
|
|
|
|
#Text is actually scaled down in 'regular' position so that it doesn't get jaggies when zoomed in
|
|
#self.set_scale(self.zoomLevel, self.zoomLevel)
|
|
self.currentZoom = self.scale_step_medium
|
|
self.currentOpacity = self.opacity_step_medium
|
|
self.set_scale(self.currentZoom, self.currentZoom)
|
|
self.set_opacity(self.currentOpacity)
|
|
"""
|
|
#(label_width, label_height) = self.label.get_size()
|
|
label_x = 0 #x #self.stage.get_width() - label_width - 50
|
|
label_y = y #self.stage.get_height() - label_height
|
|
self.set_position(0, y)
|
|
"""
|
|
|
|
def scaleLabel(self, level, timeline):
|
|
|
|
#Default values (Just in case)
|
|
zoomTo=0
|
|
opacityTo = 255
|
|
|
|
|
|
if level == self.SCALE_FULL:
|
|
zoomTo = self.scale_step_full #self.menu.zoomStep0
|
|
opacityTo = self.opacity_step_full #self.menu.opacityStep0
|
|
self.emit("selected")
|
|
if level == self.SCALE_MEDIUM:
|
|
zoomTo = self.scale_step_medium #self.menu.zoomStep1
|
|
opacityTo = self.opacity_step_medium #self.menu.opacityStep1
|
|
self.emit("deselected")
|
|
#self.itemTexturesGroup.hide_all()
|
|
if level == self.SCALE_NONE:
|
|
zoomTo = self.scale_step_none #self.menu.zoomStep2
|
|
opacityTo = self.opacity_step_none #self.menu.opacityStep2
|
|
|
|
if (zoomTo == self.currentZoom) and (opacityTo == self.currentOpacity):
|
|
return None
|
|
|
|
alpha = clutter.Alpha(timeline, clutter.ramp_inc_func)
|
|
self.behaviourScale = clutter.BehaviourScale(x_scale_start=self.currentZoom, y_scale_start=self.currentZoom, x_scale_end=zoomTo, y_scale_end=zoomTo, alpha=alpha) #scale_gravity=clutter.GRAVITY_WEST,
|
|
self.behaviourOpacity = clutter.BehaviourOpacity(opacity_start=self.currentOpacity, opacity_end=opacityTo, alpha=alpha)
|
|
self.behaviourScale.apply(self)
|
|
self.behaviourOpacity.apply(self)
|
|
|
|
#timeline.connect('completed', self.scale_end_event, zoomTo, opacityTo)
|
|
self.currentZoom = zoomTo
|
|
self.currentOpacity = opacityTo
|
|
|
|
def scale_end_event(self, data, zoomTo, opacityTo):
|
|
pass
|
|
|
|
def get_zoom_level(self):
|
|
return self.zoomLevel
|
|
|
|
def set_data(self, data):
|
|
self.data = data
|
|
|
|
def get_data(self):
|
|
return self.data
|
|
|
|
def get_menu(self):
|
|
return self.menu |