Hello,
I write here with an idea for extension so to be discussed and eventually approved. If it goes approved i will make a PR in the repo.
The idea is extending the skinning engine to supports attaching renderer to any screen gui component (not only to Sources).
I propose this because currently when you use renderer you have to connect it to specific source and there is no chance to have linked/connected visual components without it.
Implementation of Source however have some requirements that in my opinion limits users when they want completely custom functionality.
All this is in connection to my skin and my wish to attach a custom page to listbox instead of scroller, but it can be used for any other functionality that somebody can wish.
pagerexample.jpg 124.08KB 8 downloads
So this to work the Skin.py have to be slightly extended with new widget parameter (i name it "connection")
wname = widget.attrib.get("name") wsource = widget.attrib.get("source") wconnection = widget.attrib.get("connection") source = None if wname is None and wsource is None and wconnection is None: raise SkinError("The widget has no name, no source and no connection") return if wname: # print("[Skin] DEBUG: Widget name='%s'." % wname) usedComponents.add(wname) try: # Get corresponding "gui" object. attributes = screen[wname].skinAttributes = [] except Exception: raise SkinError("Component with name '%s' was not found in skin of screen '%s'" % (wname, name)) # assert screen[wname] is not Source collectAttributes(attributes, widget, context, skinPath, ignore=("name",)) elif wsource or wconnection: if wsource: # print("[Skin] DEBUG: Widget source='%s'." % wsource) while True: # Get corresponding source until we found a non-obsolete source. # Parse our current "wsource", which might specify a "related screen" before the dot, # for example to reference a parent, global or session-global screen. scr = screen path = wsource.split(".") # Resolve all path components. while len(path) > 1: scr = screen.getRelatedScreen(path[0]) if scr is None: # print("[Skin] DEBUG: wsource='%s', name='%s'." % (wsource, name)) raise SkinError("Specified related screen '%s' was not found in screen '%s'" % (wsource, name)) path = path[1:] source = scr.get(path[0]) # Resolve the source. if isinstance(source, ObsoleteSource): # If we found an "obsolete source", issue warning, and resolve the real source. print("[Skin] WARNING: SKIN '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.newSource)) print("[Skin] OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % source.removalDate) if source.description: print("[Skin] Source description: '%s'." % source.description) wsource = source.new_source else: break # Otherwise, use the source. if source is None: raise SkinError("The source '%s' was not found in screen '%s'" % (wsource, name)) wrender = widget.attrib.get("render") if not wrender: if wsource: raise SkinError("For source '%s' a renderer must be defined with a 'render=' attribute" % wsource) elif wconnection: raise SkinError("For connection '%s' a renderer must be defined with a 'render=' attribute" % wconnection) for converter in widget.findall("convert"): ctype = converter.get("type") assert ctype, "[Skin] The 'convert' tag needs a 'type' attribute!" # print("[Skin] DEBUG: Converter='%s'." % ctype) try: parms = converter.text.strip() except Exception: parms = "" # print("[Skin] DEBUG: Params='%s'." % parms) try: converterClass = my_import(".".join(("Components", "Converter", ctype))).__dict__.get(ctype) except ImportError: raise SkinError("Converter '%s' not found" % ctype) c = None for i in source.downstream_elements: if isinstance(i, converterClass) and i.converter_arguments == parms: c = i if c is None: c = converterClass(parms) c.connect(source) source = c try: rendererClass = my_import(".".join(("Components", "Renderer", wrender))).__dict__.get(wrender) except ImportError: raise SkinError("Renderer '%s' not found" % wrender) renderer = rendererClass() # Instantiate renderer. if source: renderer.connect(source) # Connect to source.
In that way you can connect 2 visual elements by name. "connection" parameter takes a name of element to connect to. For example in the skin:
<screen name="OSD3DSetupScreen" position="fill" title="Select Location" flags="wfNoBorder" backgroundColor="transparent"> <panel name="Screen_Menu1" /> <widget name="config" position="396,210" size="1130,635" itemHeight="45" font="Regular;28" scrollbarMode="showNever" selectionPixmap="selections/sel_1130x45.png" /> <widget render="Pager" connection="config" position="396,849" size="1130,25" transparent="1" backgroundColor="background" pagerForeground="white" pagerBackground="background" /> </screen>
Ofcource there have to be custom renderer made. But that renderer can be completely custom with only requirement to handle "connection" attribute. For example:
def applySkin(self, desktop, parent): attribs = [ ] for (attrib, value) in self.skinAttributes: if attrib == "pagerForeground": self.pagerForeground = parseColor(value).argb() elif attrib == "pagerBackground": self.pagerBackground = parseColor(value).argb() elif attrib == "picPage": pic = LoadPixmap(resolveFilename(SCOPE_GUISKIN, value)) if pic: self.picDotPage = pic elif attrib == "picPageCurrent": pic = LoadPixmap(resolveFilename(SCOPE_GUISKIN, value)) if pic: self.picDotCurPage = pic elif attrib == "connection": self.source = parent[value] parent.onShow.append(self.onShowed) else: attribs.append((attrib, value)) self.skinAttributes = attribs return Renderer.applySkin(self, desktop, parent)
In my opinion this will be handy for skin creators that also write renderers. But tell me your thoughts about it.