UniPAGe as a bridge between Kivy and Pythonista
The Universal Python Adaptable GUI Extensions (UniPAGe) started as a general interface for researches I was doing in a mathematical form of memory. Multiple platforms graphical user interfaces are now the norm and I did not want to have to continuously rewrite my interface for every single OS that i use. I decided to implement my study in Python with the understanding that the Kivy environment will allow me to use the same code under all five major platforms. I soon realize that although Kivy cover all those environments you have to compile you script to run them in Android and send them to an intermediate script compatible with Xcode in order to test them under IOS. This was unacceptable since my interfaces evolved two to three times a week. Then I discovered Pythonista and the Kivy Launcher came along. Although the Kivy Launcher allowed me to test my scripts on the fly within Android, Pythonista was by no means compatible with Kivy. While experimenting with Pythonista I gradually discerned that it was syntactically more closely related to Kivy than I originally thought. A “view” in Pythonista becomes a “widget” under Kivy. Therefore “add_subview” in the former is equivalent to “add_widget” in the latter. Most of the common ui elements either share the same name or same etymology. “Label” and “Button” are the same while “TextField” and “ImageView” are expressed as “TextInput” and “Image” respectively. I could not post my original modules since they are intertwine with the work mentioned above and could conflict with their “Patent Pending” status. Realizing that this is a popular topic in the Pythonista community (1.6 k views on a 2 year old post that has been revived recently with no proposed solutions) I wrote a new concise but functional and useful version of UniPAGe. As @Phuket2 would say “the below” is the first result of that effort. By the way @Phuket2 I mean no disrespect I admire the way you work and became used to your syntax.
''' Name: UniPAGe Purpose: Adaptable Graphic User Interface for Python Author: Ti Leyon Created: 04/04/2017 Copyright: (c) Ti Leyon 2017 Github: https://github.com/Tileyon/UniPAGe ''' class unipage(object): def __init__(self, kivy, screen_size): self.kivy = kivy self.screen_size = screen_size self.unibuttons =  self.unitexts =  self.unilabels =  self.unimages =  self.uniframes =  def setscreen(self): if self.kivy: from kivy.uix.floatlayout import FloatLayout from kivy.core.window import Window self.root = FloatLayout() Window.size = self.screen_size else: import ui self.root = ui.View(frame=(0,0,self.screen_size, self.screen_size)) def unibutton(self, params): xratio = self.screen_size / 800.0 yratio = self.screen_size / 600.0 self.unibuttons.append() if len(params) == 6: function = params else: function = nofunction if self.kivy: from kivy.uix.button import Button self.unibuttons[len(self.unibuttons) - 1] = Button( text = params, size_hint_y = None, size_hint_x = None, height = params * yratio, width = params * xratio, pos = (params * xratio, params * yratio), on_press = function ) self.root.add_widget(self.unibuttons[len(self.unibuttons) - 1]) else: import ui self.unibuttons[len(self.unibuttons) - 1] = ui.Button(frame= \ (params * xratio, (600 - params - params) * yratio, \ params * xratio, params * yratio), title = params) self.unibuttons[len(self.unibuttons) - 1].background_color \ = (0.4,0.4,0.4) self.unibuttons[len(self.unibuttons) - 1].action = function self.unibuttons[len(self.unibuttons) - 1].height = params * xratio self.unibuttons[len(self.unibuttons) - 1].width = params * yratio self.unibuttons[len(self.unibuttons) - 1].tint_color = 'white' self.root.add_subview(self.unibuttons[len(self.unibuttons) - 1]) def unitext(self, params): xratio = self.screen_size / 800.0 yratio = self.screen_size / 600.0 self.unitexts.append() if self.kivy: from kivy.uix.textinput import TextInput self.unitexts[len(self.unitexts) - 1] = TextInput ( id = 'text' + str(len(self.unitexts) - 1), size_hint_y = None, size_hint_x = None, height = params * yratio, width = params * xratio, text = params, multiline = True, pos = (params * xratio, params * yratio)) self.root.add_widget(self.unitexts[len(self.unitexts) - 1]) else: import ui self.unitexts[len(self.unitexts) - 1] = ui.TextField(frame= (params * xratio, (600 - params - params) * \ yratio, params * xratio, params * yratio)) self.unitexts[len(self.unitexts) - 1].bordered = False self.unitexts[len(self.unitexts) - 1].background_color = 'white' self.unitexts[len(self.unitexts) - 1].font = ('<system>', 23 * xratio) self.unitexts[len(self.unitexts) - 1].text = params self.root.add_subview(self.unitexts[len(self.unitexts) - 1]) def unilabel(self, params): xratio = self.screen_size / 800.0 yratio = self.screen_size / 600.0 self.unilabels.append() if self.kivy: from kivy.uix.label import Label self.unilabels[len(self.unilabels) - 1] = Label(pos = \ (params * xratio, params * yratio), \ size_hint=(1.0,1.0), halign="left", \ valign="bottom", text = params) self.unilabels[len(self.unilabels) - 1].bind(size= \ self.unilabels[len(self.unilabels) - 1].setter('text_size')) self.root.add_widget(self.unilabels[len(self.unilabels) - 1]) else: import ui self.unilabels[len(self.unilabels) - 1] = ui.Label(frame= \ (params * xratio, (600 - params - params) * yratio, \ params * xratio, params * yratio)) self.unilabels[len(self.unilabels) - 1].text = params self.unilabels[len(self.unilabels) - 1].text_color = 'white' self.unilabels[len(self.unilabels) - 1].alignment = ALIGN_LEFT = True self.unilabels[len(self.unilabels) - 1].font = ('<system>', 18 * xratio) self.root.add_subview(self.unilabels[len(self.unilabels) - 1]) def unimage(self, params): xratio = self.screen_size / 800.0 yratio = self.screen_size / 600.0 self.unimages.append() if self.kivy: from kivy.uix.image import Image self.unimages[len(self.unimages) - 1] = Image( source= params, allow_stretch = True, size_hint = (None, None), size=(params * xratio, params * yratio), pos=(params * xratio, params * yratio)) self.root.add_widget(self.unimages[len(self.unitexts) - 1]) else: import ui self.unimages[len(self.unimages) - 1] = (ui.ImageView (name = 'Image', frame = (params * xratio, \ (600 - params - params) * yratio, \ params * xratio, params * yratio))) self.root.add_subview (self.unimages[len(self.unimages) - 1]) self.unimages[len(self.unitexts) - 1].image = ui.Image.named(params) def uniframe(self, params): xratio = self.screen_size / 800.0 yratio = self.screen_size / 600.0 if self.kivy: from kivy.graphics import Color from kivy.graphics import Rectangle self.root.canvas.add(Color (params,params, params)) self.root.canvas.add(Rectangle(pos = (params * xratio, \ params * yratio), size = (params * xratio, \ params * yratio))) else: import ui xratio = self.screen_size / 800.0 yratio = self.screen_size / 600.0 self.uniframes.append() self.uniframes[len(self.uniframes) - 1] = ui.View(frame=(params * xratio, \ (600 - params - params) * yratio, \ params * xratio, params * yratio)) self.uniframes[len(self.uniframes) - 1].background_color = (params,params, params,1.0) self.root.add_subview(self.uniframes[len(self.uniframes) - 1]) def showpage(self): if self.kivy: from kivy.base import runTouchApp runTouchApp(self.root) else: self.root.present('sheet') class uniscreen(unipage): screendef =  def __init__(self, screendef): try: from kivy.uix.floatlayout import FloatLayout kivy = True except: import ui kivy = False unipage.__init__(self, kivy, screendef) self.setscreen() self.screendef = screendef def setpage(self): for k in range(1, len(self.screendef)): self.screendef[k](self, self.screendef[k]) def closepage(sender): if mypage.kivy: from kivy.utils import platform as core_platform from kivy.core.window import Window import sys if core_platform == 'android': sys.exit() else: Window.close() else: mypage.root.close() def function_1(sender): mypage.unitexts.text = 'Oh! You clicked my button.' def nofunction(sender): pass if __name__ == '__main__': unilabel = unipage.unilabel uniframe = unipage.uniframe unitext = unipage.unitext unibutton = unipage.unibutton unimage = unipage.unimage widgets = [(600, 450), (uniframe,(0, 0, 600, 450,(.6,.6,.6))), (unilabel,(80, 10, 240, 20, 'Hey I am just a simple label.')), (unibutton,(40, 40, 100, 40, 'Click me', function_1)), (unibutton,(460, 40, 100, 40, 'Close me', closepage)), (unitext,(40, 120, 300, 40, 'I am a text field')), (unimage,(460, 310, 100, 100,'insidelogo.png')) ] mypage = uniscreen(widgets) mypage.setpage() mypage.showpage()
Copy the code above and you can run it without modification under any desktop or mobile python that include Kivy or Pythonista’s UI. Place a picture under the same directory and rename it to “inside.png”. If you want to test the script under Android you must place it in a subdirectory inside the kivy folder. Besides the picture you must also create a text file named “android.txt” containing the following three lines: title=<your title>, author=<programmer’s name>, orientation=<landscape or portrait>. If you do not mind spending a few bucks you may also purchase “Pydroid” which is the only decent Python IDE under Android (Do not confuse with the Pydroid projects on Github). I must warn you that it is a work in progress that only implements Python 2.7x but it is quite stable, very promising and integrates Kivy right off the box. You may also download all the UniPAGe files from GitHub at: https://github.com/Tileyon/UniPAGe. Testing the demo under different OS will make you realize that the “look and feel” is quasi-identical across all targeted platforms. This is because UniPAGe is not just a bridge between Kivy and Pythonista but a fully adaptable GUI framework designed to feel and look the same under all screen resolutions and orientations. To accomplish this feat UniPAGe had to take into consideration the fundamental differences between the two programming paradigms. First UniPAGe detects the working environment by “trying” to import a Kivy module. If it succeeds it sets a class level boolean variable named “kivy” to “True”. Otherwise it sets its value to “False” and use it to redirect the creation of GUI elements to specific functions and to activate the given instance. To adapt the GUI components to various screen and window resolutions and orientations the module defines a basic view ratio of 800x600 pixels which incidentally is the default resolution under Kivy. The user must be familiar with absolute positioning of the various constituent of the GUI in order to work with the extension. Every interface must be conceptualized at that level before placing the different parts on the main scenery. Each element must be conceived in the context of the 800 by 600 landscape. The extension will automatically adjust their size and that of their sub-components such as text and glyph according to variations in resolution or orientation. Try changing the original frame by modifying the first tuple parameter of the widget variable under the main section to experiment with the adaptability factor. Currently aspect ratios on most devices including desktops vary within a range of 1.33 to 1.88. Although the extension’s base ratio resides a the bottom of that range, surprisingly, it adapts with no excessive distortion to the full range.That is if you are not using basic drawings in the pictures. However, you can always adjust images ratios within the main framework. Test the demo on different devices and judge for yourself. You may also use a technic that only uses a portion of the screen as the working area and fill the rest with an artistic texture. The usable space is computed from the smaller size in perfect proportion to 800x600 resolution. On phones and tablets I often use that technic by forcing the device into portrait mode and use the smaller size as the horizontal definition while computing the vertical size from the first one base on the same 800x600 proportion. That way I can use the keyboard without covering any part of the working area. To use UniPAGe you must put the functions that create screen elements (unitext, unibutton, unilabel, unimage, uniframe) into variables. Then create a list of tuples to hold the definitions of the screen elements. The first tuple in that list must contain two values that define the horizontal and vertical sizes of the main window. All the other tuples define various screen components. They contain two main elements: the variable holding the function that creates it and a tuple which contains the parameters for that function. These parameters follow the following order: horizontal position, vertical position, width, height and a character string. That string will serve as the text for the component and in the case of an image it indicates the filename of that image. Buttons may contain a sixth optional parameter that refers to its targeted function. If it is omitted the button will be automatically assigned to a dummy function. At that point create instance of the uniscreen class which itself is a descendent of unipage. That instance is initialized by using the list of tuples decribed above as its parameter. The window is then initialized by calling the setpage method. Use the showpage method to run the application.
Computers are molds that help create digital universes. Operating systems are the gatekeepers of these universes. Programming languages are the keys to open the gates. Python with its numerous free and open source modules and extensions is definitely a golden key. My hope is that gradually with the help of the community, UniPAGe will evolve into a master key. The five components implemented in this first release are used exclusively in 90% of all programs in all platforms excluding games. Other basic structures such as checkmarks, radio buttons, grids and even scroll views can be easily added to UniPAGe since their implementation in kivy and Pythonista are also quite similar. The main structure for games under Pythonista is the “Scene view”. Kivy also comprises a similar structure called scatter. Someone has also developed an entity-based game engine for Kivy called KivEnt. I hope this modest contribution is of use to the reader.
I am sorry if I hurt your feelings. I thought that this code will help somebody like me who is familiar with pythonista but not with kivy. I just spent two to three hours to code this. I do not have any projects related to this and I am not associated with any company. I am a retired old man and my only intention is to help others. Once again I am sorry if I hurt your feelings.
No need for apologies @abcabc I absolutely did not take anything personally. It was only an impassionate plea for simplicity and balance in coding. If my prose sounded rude I myself apologize.
Did you try my code on mac or PC? (I have tested on PC ,(windows 10, kivy 1.9.1, python 3.6) it works fine.)
@abcabc I really like the direction that you have taken with this... Making a ui module that allows Pythonista ui code to work unmodified on non-Pythonista platforms is intriguing. The app makes one import of
uiand under the covers this code could manage ~10 different kivy imports. That is a nice bit of complexity hiding. If you would be willing to convert https://gist.github.com/balachandrana/4e9accfc894785682f230c54dc5da816#file-ui-py into a repo with a reasonable license (Apache or MIT) then I would send you some pull requests.
I will put it in a github repo with MIT license after two/three weeks ( Need to do some cleanup, add some more features, add few more
Come on @abcabc what do you mean you are going to put it on github with "MIT license"? You ripped off my code, turn it into a pythonista bastard that it was not intended to be and now you want to get a license on it. This is a subject that two days ago, above, you said you do not know anything about. Did you not read at the very beginning of this post that it is already copyrighted material under "Ti Leyon". What you are doing constitute copyright infringement and is against the law. This is why I never really publish in forums. Definitely @omz has to look into this kind of practices and establish rules to avoid this type of blatant theft of intellectual property.
Of course it's not okay (and illegal) to publish someone else's code with a license that the original author never intended. You can't just assume that everyone uses the MIT license, just because it's common.
In @abcabc's defense though, it is fairly unusual to post source code publicly, and not apply any open-source license. This makes it impossible to collaborate in any meaningful way, and if collaboration is not desired, it's not clear what the purpose of posting the code in the first place would be.
That said, this is @tileyon's decision, of course. Btw, it's possible to edit existing posts, in case you want to remove your code from this forum.
Thank you @omz for participating in this discussion. I published this post primarily as an educative material. Users of this forum have been requesting and commenting for years about integrating other platforms within Pythonista’s UI. Kivy loyalists for their part seem to remain oblivious to the virtues of Pythonista. My intents are clearly stated in the introduction to this discussion or even in its title. The codes and other files are on Github where anyone can start a branch and initiate a pull request. Although this type of collaboration is welcomed, plagiarizing the concept without any real creativity or functional contribution is definitely undesirable. Masquerading the original script as a parasitic afterthought of the UI is even more disturbing. This clearly defeats the spirit of unity between the targeted platforms and can not be tolerated. Is it not the norm in the industry? Is it not the motive of lawsuits between Sun Microsystem and Microsoft in the late 90’s about Java or more recently between Oracle and Google concerning the same development platform? Even with Linux, one of the first and widely adopted open source project, nothing goes in the kernel without the blessing of Linus Torvalds. The introduction of UniPAGe to the Kivy community will probably attract countless users of that group into the realm of Pythonista. This post also constitutes a small step to help users of this forum easily foray into alien GUI structures. Therefore, there is no need to antagonize adepts of either platform. Since it seems that a license is expected, I hereby propose the following license to be applied to UniPAGe. The J.U.L.I.A. G.E.E.K.S. license or julia geeks (all lower case) license is an educational licensure and an acronym for "Just Understand, Learn, Innovate And Go Enhance Every Known Statements”. julia geeks strongly discourage all form of plagiarism or mindless alteration of a given intellectual production. It promotes innovative, constructive and original addition or improvement to the said production. Such additions will be added to the current release and their authors credited in the subsequent “commits”. In the unfortunate event that the material should be reproduced, transformed or otherwise manipulated in whole or in parts then current local, state and international copyright laws should apply. julia geeks is also a reminder of Albert’s observation that “imagination is more important than knowledge”. Thank you again @omz for your leadership as well as the accuracy and fairness of your position.
Thanks for sharing.
One comment, you might consider using a ui.Transform on your root view, to set the scale. Somewhat simpler, in that scale only needs to be set in one location. This would also allow future enhancements that make use of pinch zooming or panning to show ui's at their native aspect ratio (for instance, showing a landscape ui in portrait), or in cases such as images where aspect ratio might make a difference.
I think the ideal kivy <--> pythonista bridge would either look like a ui.py on kivy systems, or kivy.py on pythonista systems, which mock the interfaces of each other, rather than creating an entirely new api. That way, existing scripts would "just work" with the addition of one file. I didn't read abcabc's post as trying to steal your work, but instead was trying to build on your idea, and offer a refactored version from a mainly pythonista user perspective. Someone who works mostly in pythonista generally wants to be able to
import uiand have thier existing scripts work, load pyui's developed on pythonista's ui editor, and so forth. I would imagine someone with lots of work already in kivy would want something similar on pythonista.
Of course mocking of the full
ui, or full
kivywould be a ton of work to capture all of the little quirks, features etc of either system. Without a ton of effort, any sort of "universal" api means giving up functionality and performance -- but no doubt still useful to many.
@tileyon What are you trying to achieve with that attitude? I'm still not sure what the issue is with abcabc's code that you keep insulting. Is it that the code was based on your code? (Then why did you post your code on a public forum?) Is it that they wanted to put a license on it? (That could have been solved by putting a license on the original code in the first place.) Is it that it's modeled after Pythonista's instead of Kivy's API? (I don't see what's wrong with that.)
Can this "Julia Geeks" license be found anywhere? A web search didn't reveal anything useful. I'd be interested in seeing how exactly it differentiates between "innovative, constructive, original addition or improvement" and "mindless alteration".
Glad to see you here @JonB. The first thing I considered while designing this scheme was native scaling in Kivy and Pythonista. However I did not have a good experience dealing with it in Kivy. It either did not work or I did not know enough. I also read in one of these posts that in Pythonista fonts lose their quality during scaling. Since I must take full responsibility for the functioning of my codes I decided to implement it myself. That way I retain full control of it and can adapt it whichever way I want. You are right about the redundancy of scaling ration. This is an oversight on my part and can be easily improved by setting the vertical and horizontal ratios as class variables and assigning their values at the initialization phase. The future enhancements that you mentioned could also come handy in a visual designer. A few months ago I wrote a simple Python IDE for the Android platform that included a visual designer which produced hybrid Pythonista/Kivy codes. If I can spare the time I will probably adapt it to generate UniPAGe scripts. Once I finish the modification I will place the app on the Android market for free with no adds and publish the code in Github under, uhhh..., the julia geeks license. I wrote it in “RFO BASIC”, an excellent free BASIC interpreter written by Paul Laughton a veteran BASIC interpreter/compiler developer who designed the BASICs versions of Atari, Apple II, Amiga computers back in the days. I wanted to see how far I could push RFO BASIC and also because a developer who goes by the handle Mougino publish and app that compiles “RFO BASIC” scripts straight into Android APKs and a matter minutes. It was a delightful experience dealing with both. In case I decide to port that IDE to Python through UniPAGe zooming, pinching and panning will be a must. Maybe, if you want to, you can help me with the Pythonista part. I will try to insert a screenshot of that designer below. Concerning the post that you mentioned above my reaction results from the logical implication of the following events. The modification was rush into Github just a day after I posted the original; That post did not mention my copyright or this thread; Intent to assign licenses implies a declaration of ownership through copyright or other means. You may judge for yourself. I do not know if you tried UniPAGe but it can run on any platform without modification to the code whatsoever. What would be the point of making it more complicated by having to modify codes depending on platforms? I must also tell you that I use your “PhoneManager.py” on a regular basis. I modified it to suit my needs and style. However, I never posted those modifications in any forum, let alone claiming ownership of the script and seek a license for it. Even if I judged the modifications important or original enough to be posted I would always refer to it as “modifications to YOUR script”. Thank you for participating @JonB.
By the way I intend to modify Einstein's well known and "well publicized" equation E = m * c ^ 2 to m = E / c ^ 2 because I am more interested in mass than energy. Therefore it will become "my" equation and I will apply for a patent for it. I am sure that a few participants in this thread will vouch for me at the patent office.
Sorry about the screenshot I could not find an easy way to insert it.
Hi Ti Leyon, I haven't found a "PhoneManager.py" at https://github.com/jsbain. If you used this PhoneManager by chance you're free to make everything, except selling the code. I'm a bit lazy so I never care about copyright things :), sorry. So what's the best license for my purpose?
Look's like every license has a permission for commercial use :(. licenses
Hey @brumm if you wrote “PhoneManager.py” I am sorry for the mixup. It must be “GitHubGet.py” that I got from @JonB. These are the only two scripts from this forum that I modified and use occasionally. They are both very handy. As far as the “julia geeks” license is concerned I created it because it seems ridiculous that people want to officially license this little bit of code. This is more a proof of concept than a finished product that requires licensing. However, since someone wanted to take that step I had to counteract and “julia geeks” fits my intent perfectly: “look, learn and go do better”. There are no laws or even rules that forces a license to provide for commercial use. By the way, a copyright suffice and is well defined and protected by states and international laws.
@tileyon If you don't provide a license and only add a copyright statement, then you're not actually allowing anyone to use your code. To run your code, the user has to copy or download it, which would be against the copyright. That's why a proper license is important, otherwise nobody is legally allowed to use your code.
As far as I can tell, currently your license doesn't allow people to download your code. I'm not a lawyer though. If you really want to write your own license, you should get in contact with a lawyer to make sure that the license legally means what you expect it to mean.
In almost all cases it's safer, easier and more useful to use an existing well-known open-source license, especially if you want others to contribute to your code. The https://choosealicense.com/ site from GitHub (which I linked to before) gives a good short overview. This and this page on the GNU website are also interesting, but they are of course biased towards copyleft licenses like the GPL. Unlike what they sometimes suggest, it's fine to use a less restrictive license (MIT, Apache, BSD, etc.).
Relax @dgelessus this not a capital case. We do not need to bring layers in this, they will steal the fun. Licenses are just stipulations that broaden the scope of a copyright or patent. julia geeks allows you to improve and extend the code and to do so you must be able to download it. The fact is I never intended to put any kind of restriction on its use until licensing was mentioned by other parties under terms that were unacceptable to me. I thank you for your judicious suggestions and I will look into the matter. In the meantime anyone can download, experiment and improve UniPAGe regardless of their perception of julia geeks
Capital cases, lawyers be damned. You say this:
"In the unfortunate event that the material should be reproduced, transformed or otherwise manipulated in whole or in parts then current local, state and international copyright laws should apply."
What that means is that I can submit changes to your Git repository, but I can't actually any software with it built in, as that would be "reproduction". Thus any code I wrote with it would only be issuable in source form, for people who run a Python interpreter and who install your gui wrapper themselves.
That means that I can't develop a package in Pythonista using it and then package it in XCode.
It means I can't compile the package into an Android.apk or a Windows .exe .
There is little point in me learning your API if I'm never going to be able to use it beyond tinkering for my own interest -- and if I'm tinkering for my own interest, then cross-platform dev clearly isn't a big issue and in the end I'd be just as well sticking with Pythonista ui or Kivy.
The only wrinkle with your approach is that it would be too easy to work away on Pythonista and build up a working UI, only to forget which methods were available in the cross-platform version.
The way I would solve this is by renaming the package (eg
pythonistaui, and having two versions -- a Pythonista one and a Kivy one. The job of the Pythonista package would be to simply throw an error any time you tried to use something that isn't implemented in the Kivy version.
In its simplest version, it would just be a list of imports, but that wouldn't deal with classes that didn't have all methods fully implemented -- in that case you'd have to build subclasses and override unimplemented methods to throw exceptions.
If you are considering developing such a wrapper, I think the Pythonista version could probably be generated automatically with a bit of clever programming -- write a script that takes the
uispec from Pythonista, checks each function, class and method to see if it's available in your Kivy wrapper, and builds up the Pythonista wrapper from there.
In the Pythonista console, type:
import ui ; help(ui)for a starting point.