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[0], self.screen_size[1])) def unibutton(self, params): xratio = self.screen_size[0] / 800.0 yratio = self.screen_size[1] / 600.0 self.unibuttons.append([]) if len(params) == 6: function = params[5] else: function = nofunction if self.kivy: from kivy.uix.button import Button self.unibuttons[len(self.unibuttons) - 1] = Button( text = params[4], size_hint_y = None, size_hint_x = None, height = params[3] * yratio, width = params[2] * xratio, pos = (params[0] * xratio, params[1] * 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[0] * xratio, (600 - params[1] - params[3]) * yratio, \ params[2] * xratio, params[3] * yratio), title = params[4]) 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[3] * xratio self.unibuttons[len(self.unibuttons) - 1].width = params[2] * 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[0] / 800.0 yratio = self.screen_size[1] / 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[3] * yratio, width = params[2] * xratio, text = params[4], multiline = True, pos = (params[0] * xratio, params[1] * yratio)) self.root.add_widget(self.unitexts[len(self.unitexts) - 1]) else: import ui self.unitexts[len(self.unitexts) - 1] = ui.TextField(frame= (params[0] * xratio, (600 - params[1] - params[3]) * \ yratio, params[2] * xratio, params[3] * 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[4] self.root.add_subview(self.unitexts[len(self.unitexts) - 1]) def unilabel(self, params): xratio = self.screen_size[0] / 800.0 yratio = self.screen_size[1] / 600.0 self.unilabels.append([]) if self.kivy: from kivy.uix.label import Label self.unilabels[len(self.unilabels) - 1] = Label(pos = \ (params[0] * xratio, params[1] * yratio), \ size_hint=(1.0,1.0), halign="left", \ valign="bottom", text = params[4]) 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[0] * xratio, (600 - params[1] - params[3]) * yratio, \ params[2] * xratio, params[3] * yratio)) self.unilabels[len(self.unilabels) - 1].text = params[4] 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[0] / 800.0 yratio = self.screen_size[1] / 600.0 self.unimages.append([]) if self.kivy: from kivy.uix.image import Image self.unimages[len(self.unimages) - 1] = Image( source= params[4], allow_stretch = True, size_hint = (None, None), size=(params[2] * xratio, params[3] * yratio), pos=(params[0] * xratio, params[1] * 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[0] * xratio, \ (600 - params[1] - params[3]) * yratio, \ params[2] * xratio, params[3] * yratio))) self.root.add_subview (self.unimages[len(self.unimages) - 1]) self.unimages[len(self.unitexts) - 1].image = ui.Image.named(params[4]) def uniframe(self, params): xratio = self.screen_size[0] / 800.0 yratio = self.screen_size[1] / 600.0 if self.kivy: from kivy.graphics import Color from kivy.graphics import Rectangle self.root.canvas.add(Color (params[4][0],params[4][1], params[4][2])) self.root.canvas.add(Rectangle(pos = (params[0] * xratio, \ params[1] * yratio), size = (params[2] * xratio, \ params[3] * yratio))) else: import ui xratio = self.screen_size[0] / 800.0 yratio = self.screen_size[1] / 600.0 self.uniframes.append([]) self.uniframes[len(self.uniframes) - 1] = ui.View(frame=(params[0] * xratio, \ (600 - params[1] - params[3]) * yratio, \ params[2] * xratio, params[3] * yratio)) self.uniframes[len(self.uniframes) - 1].background_color = (params[4][0],params[4][1], params[4][2],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[0]) self.setscreen() self.screendef = screendef def setpage(self): for k in range(1, len(self.screendef)): self.screendef[k][0](self, self.screendef[k][1]) 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[0].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.
-
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?
-
@brumm https://choosealicense.com/ is a guide from GitHub to help you choose a license.
-
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
-
@Ti-Leyon
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.
-
@abcabc
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
ui
spec 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.
-
@Niall here is an update to UniPAGE that may address your concerns. julia geeks was an expression of my frustration with people who wanted to license the code under their own terms without even mentioning the original source. I followed @dgelessus advice and investigated the common open source licensure, albeit superficially I must admit. I settled on the Creative Commons Attribution-ShareAlike 4.0 International License. It seems to reflect the general idea I had in mind. As far as I understand it you may use it in your own work and modify it whichever you want and even compile and sell the resulting app as long as you mention my original copyright at the beginning of the source code. If you want to publish the modified source you must do so under the same license. I am not a lawyer thus you may refer to the Creative Commons web site (www.creativecommons.org) for more information. Furthermore, I turned the "xratio" and "yratio" variables into class level entities as it was suggested by @JonB. I also added a "standard" view that will automatically set the main window at 800x600 under MS Windows, OSX and Linux (the Kivy default). It will fit the sheet in Pythonista according to the screen orientation. Under Android the window will maximize to occupy the entire screen. To use that "standardized structure" set the first tuple representing the view size to (0, 0). Thank you to @JonB, @ccc and @dgelessus for their suggestions.
# coding: utf-8 #----------------------------------------------------------------------------- # Name: UniPaGe # Purpose: Adaptable Graphic User Interface for Python # # Author: Ti Leyon # # Created: 04/04/2017 # Copyright: (c) Ti Leyon 2017 # Licence: This work is licensed under the # Creative Commons Attribution-ShareAlike 4.0 International License. # To view a copy of this license, visit: # http://creativecommons.org/licenses/by-sa/4.0/ # or send a letter to: # Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. #----------------------------------------------------------------------------- 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 = [] self.xratio = self.screen_size[0] / 800.0 self.yratio = self.screen_size[1] / 600.0 def setscreen(self): if self.kivy: from kivy.uix.floatlayout import FloatLayout from kivy.core.window import Window self.root = FloatLayout() if (self.xratio == 0) or (self.yratio == 0): from kivy.utils import platform as core_platform if core_platform == 'android': self.screen_size = Window.size else: self.screen_size = (800, 600) self.xratio = self.screen_size[0] / 800.0 self.yratio = self.screen_size[1] / 600.0 Window.size = self.screen_size else: import ui if (self.xratio == 0) or (self.yratio == 0): ss1 = ui.get_screen_size()[0] ss3 = ui.get_screen_size()[1] notoptimal = True while notoptimal: if ss1 % 8 == 0: notoptimal = False else: ss1 -= 1 ss1 = ss1 - 124 ss2 = (ss1 / 4) * 3 if ss2 > ss3: print('yes') ss2 = ss3 - ss2 - ((ss3 - ss2) % 3) ss1 = (ss2 / 3) * 4 self.screen_size = (ss1, ss2) self.xratio = self.screen_size[0] / 800 self.yratio = self.screen_size[1] / 600 self.root = ui.View(frame=(0,0,self.screen_size[0], \ self.screen_size[1])) def unibutton(self, params): self.unibuttons.append([]) if len(params) == 6: function = params[5] else: function = nofunction if self.kivy: from kivy.uix.button import Button self.unibuttons[len(self.unibuttons) - 1] = Button( text = params[4], size_hint_y = None, size_hint_x = None, height = params[3] * self.yratio, width = params[2] * self.xratio, pos = (params[0] * self.xratio, params[1] * self.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[0] * self.xratio, (600 - params[1] - params[3]) * self.yratio, \ params[2] * self.xratio, params[3] * self.yratio), title = params[4]) 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[3] * self.xratio self.unibuttons[len(self.unibuttons) - 1].width = params[2] * self.yratio self.unibuttons[len(self.unibuttons) - 1].tint_color = 'white' self.root.add_subview(self.unibuttons[len(self.unibuttons) - 1]) def unitext(self, params): 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[3] * self.yratio, width = params[2] * self.xratio, text = params[4], multiline = True, pos = (params[0] * self.xratio, params[1] * self.yratio)) self.root.add_widget(self.unitexts[len(self.unitexts) - 1]) else: import ui self.unitexts[len(self.unitexts) - 1] = ui.TextField(frame= (params[0] * self.xratio, (600 - params[1] - params[3]) * \ self.yratio, params[2] * self.xratio, params[3] * self.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 * self.xratio) self.unitexts[len(self.unitexts) - 1].text = params[4] self.root.add_subview(self.unitexts[len(self.unitexts) - 1]) def unilabel(self, params): self.unilabels.append([]) if self.kivy: from kivy.uix.label import Label self.unilabels[len(self.unilabels) - 1] = Label(pos = \ (params[0] * self.xratio, params[1] * self.yratio), \ size_hint=(1.0,1.0), halign="left", \ valign="bottom", text = params[4]) 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[0] * self.xratio, (600 - params[1] - params[3]) * self.yratio, \ params[2] * self.xratio, params[3] * self.yratio)) self.unilabels[len(self.unilabels) - 1].text = params[4] 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 * self.xratio) self.root.add_subview(self.unilabels[len(self.unilabels) - 1]) def unimage(self, params): self.unimages.append([]) if self.kivy: from kivy.uix.image import Image self.unimages[len(self.unimages) - 1] = Image( source= params[4], allow_stretch = True, size_hint = (None, None), size=(params[2] * self.xratio, params[3] * self.yratio), pos=(params[0] * self.xratio, params[1] * self.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[0] * self.xratio, \ (600 - params[1] - params[3]) * self.yratio, \ params[2] * self.xratio, params[3] * self.yratio))) self.root.add_subview (self.unimages[len(self.unimages) - 1]) self.unimages[len(self.unitexts) - 1].image = ui.Image.named(params[4]) def uniframe(self, params): if self.kivy: from kivy.graphics import Color from kivy.graphics import Rectangle self.root.canvas.add(Color (params[4][0],params[4][1], params[4][2])) self.root.canvas.add(Rectangle(pos = (params[0] * self.xratio, \ params[1] * self.yratio), size = (params[2] * self.xratio, \ params[3] * self.yratio))) else: import ui self.uniframes.append([]) self.uniframes[len(self.uniframes) - 1] = \ ui.View(frame=(params[0] * self.xratio, \ (600 - params[1] - params[3]) * self.yratio, \ params[2] * self.xratio, params[3] * self.yratio)) self.uniframes[len(self.uniframes) - 1].background_color = \ (params[4][0],params[4][1], params[4][2],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[0]) self.setscreen() self.screendef = screendef def setpage(self): for k in range(1, len(self.screendef)): self.screendef[k][0](self, self.screendef[k][1]) 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[0].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 = [(0, 0), (uniframe,(0, 0, 600, 450,(.6,.6,.6))), (unilabel,(80, 300, 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()
-
Thanks a lot. You rock!
-
A visual designer that generates a UniPAGe based script? About a year ago I wrote a simple Python IDE for the Android platform. It was mainly an experiment to find out how for far I could push the Android version of RFO BASIC and how fast I could create a ready for market app on that platform. It was a rewarding experience and recently I decided to adapt it to produce UniPAGe scripts. Before I started it occurred to me that I should restart from scratch and write the visual interface designer using UniPAGe. That way the designer could run unmodified in all the environments covered by UniPAGe. That is exactly what I did and in doing so I discovered about a dozen bugs, inconsistencies and omissions in the original implementation of UniPAGe. They were promptly addressed and corrected. Below are a list of the corrections as well as a full listing of the latest implementation of UniPAGE. BTW @komanguy thank you for your kind comments and enjoy the this finely tuned release.
Improvements in this release:
- Set the font size for the
Kivy
environment to adapt to the current window resolution - Created Android windows within the full screen resolution in all orientations because the
Kivy
environment responded erratically when the window is set directly. - Fixed font ratio for the textfields in
Pythonista UI
- Fixed the automated mode (0, 0) in the
Pthonista UI
for all iOS devices to present an "optimal" sheet (Tested on many iPhone and iPad models). - Note: I could not locate a reference for the height of the title bar in a sheet presentation in
Pythonista
. A factor of 90 times the vertical ratios works on all tested devices although not quite optimal on higher resolution gears. - Made the "closepage()" function independent of the global "mypage" variable so it could be used along with the UniPAGe classes in an external module if so desired.
#----------------------------------------------------------------------------- # Name: UniPaGe # Purpose: Adaptable Graphic User Interface for Python # # Author: Ti Leyon # # Created: 04/07/2017 # Copyright: (c) Ti Leyon 2017 # Licence: This work is licensed under the # Creative Commons Attribution-ShareAlike 4.0 # International License. # To view a copy of this license, visit: # http://creativecommons.org/licenses/by-sa/4.0/ # or send a letter to: # Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. #----------------------------------------------------------------------------- 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 = [] self.xratio = self.screen_size[0] / 800.0 self.yratio = self.screen_size[1] / 600.0 def setscreen(self): if self.kivy: from kivy.config import Config Config.set('input', 'mouse', 'mouse,multitouch_on_demand') from kivy.uix.floatlayout import FloatLayout from kivy.core.window import Window from kivy.utils import platform as core_platform self.root = FloatLayout() if (self.xratio == 0) or (self.yratio == 0): if core_platform == 'android': self.screen_size = Window.size if self.screen_size[0] < self.screen_size[1]: x = self.screen_size[0] y = self.screen_size[0] / 4 * 3 self.screen_size = (x, y) else: self.screen_size = (800, 600) self.xratio = self.screen_size[0] / 800.0 self.yratio = self.screen_size[1] / 600.0 if core_platform == 'android': Window.softinput_mode = 'pan' else: Window.size = self.screen_size else: import ui if (self.xratio == 0) or (self.yratio == 0): ss1 = ui.get_screen_size()[0] ss3 = ui.get_screen_size()[1] notoptimal = True while notoptimal: if ss1 % 8 == 0: notoptimal = False else: ss1 -= 1 ss2 = (ss1 / 4) * 3 title_bar_height = int(ss3 / 600 * 50) if ss2 > ss3 - title_bar_height: ss2 = ss3 - title_bar_height notoptimal = True while notoptimal: if ss2 % 6 == 0: notoptimal = False else: ss2 -= 1 ss1 = (ss2 / 3) * 4 self.screen_size = (ss1, ss2) self.xratio = ss1 / 800 self.yratio = ss2 / 600 self.root = ui.View(frame=(0,0,self.screen_size[0], \ self.screen_size[1])) def unibutton(self, params): self.unibuttons.append([]) if len(params) == 6: function = params[5] else: function = nofunction if self.kivy: from kivy.uix.button import Button self.unibuttons[len(self.unibuttons) - 1] = Button( text = params[4], size_hint_y = None, size_hint_x = None, height = params[3] * self.yratio, width = params[2] * self.xratio, font_size = 17.5 * self.yratio, pos = (params[0] * self.xratio, params[1] * self.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[0] * self.xratio, (600 - params[1] - \ params[3]) * self.yratio, \ params[2] * self.xratio, params[3] * self.yratio), \ title = params[4]) 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[3] * \ self.xratio self.unibuttons[len(self.unibuttons) - 1].width = params[2] * \ self.yratio self.unibuttons[len(self.unibuttons) - 1].tint_color = 'white' self.unibuttons[len(self.unibuttons) - 1].font = ('<system>', \ 17.5 * self.yratio) self.root.add_subview(self.unibuttons[len(self.unibuttons) - 1]) def unitext(self, params): 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[3] * self.yratio, width = params[2] * self.xratio, text = params[4], multiline = True, font_size = 17.5 * self.yratio, pos = (params[0] * self.xratio, params[1] * self.yratio)) self.root.add_widget(self.unitexts[len(self.unitexts) - 1]) else: import ui self.unitexts[len(self.unitexts) - 1] = ui.TextField(frame= (params[0] * self.xratio, (600 - params[1] - params[3]) * \ self.yratio, params[2] * self.xratio, params[3] * self.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>', 17.5 * \ self.yratio) self.unitexts[len(self.unitexts) - 1].text = params[4] self.root.add_subview(self.unitexts[len(self.unitexts) - 1]) def unilabel(self, params): self.unilabels.append([]) if self.kivy: from kivy.uix.label import Label self.unilabels[len(self.unilabels) - 1] = Label(pos = \ (params[0] * self.xratio, params[1] * self.yratio), \ size_hint=(1.0,1.0), halign="left", \ valign="bottom", text = params[4]) self.unilabels[len(self.unilabels) - 1].font_size = 17.5 * \ self.yratio 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[0] * self.xratio, (600 - params[1] - params[3]) * \ self.yratio, params[2] * self.xratio, params[3] * self.yratio)) self.unilabels[len(self.unilabels) - 1].text = params[4] self.unilabels[len(self.unilabels) - 1].text_color = 'white' self.unilabels[len(self.unilabels) - 1].alignment = \ ui.ALIGN_LEFT self.unilabels[len(self.unilabels) - 1].font = ('<system>', 18 * \ self.yratio) self.root.add_subview(self.unilabels[len(self.unilabels) - 1]) def unimage(self, params): self.unimages.append([]) if self.kivy: from kivy.uix.image import Image self.unimages[len(self.unimages) - 1] = Image( source= params[4], allow_stretch = True, size_hint = (None, None), size=(params[2] * self.xratio, params[3] * self.yratio), pos=(params[0] * self.xratio, params[1] * self.yratio)) self.root.add_widget(self.unimages[len(self.unimages) - 1]) else: import ui self.unimages[len(self.unimages) - 1] = (ui.ImageView (name = 'Image', frame = (params[0] * self.xratio, \ (600 - params[1] - params[3]) * self.yratio, \ params[2] * self.xratio, params[3] * self.yratio))) self.root.add_subview (self.unimages[len(self.unimages) - 1]) self.unimages[len(self.unimages) - 1].image = \ ui.Image.named(params[4]) def uniframe(self, params): if self.kivy: from kivy.graphics import Color from kivy.graphics import Rectangle self.root.canvas.add(Color (params[4][0],params[4][1], \ params[4][2])) self.root.canvas.add(Rectangle(pos = (params[0] * self.xratio, \ params[1] * self.yratio), size = (params[2] * self.xratio, \ params[3] * self.yratio))) else: import ui self.uniframes.append([]) self.uniframes[len(self.uniframes) - 1] = \ ui.View(frame=(params[0] * self.xratio, \ (600 - params[1] - params[3]) * self.yratio, \ params[2] * self.xratio, params[3] * self.yratio)) self.uniframes[len(self.uniframes) - 1].background_color = \ (params[4][0],params[4][1], params[4][2],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): def __init__(self, screendef): try: from kivy.uix.floatlayout import FloatLayout kivy = True except: import ui kivy = False unipage.__init__(self, kivy, screendef[0]) self.setscreen() self.screendef = screendef def setpage(self): for k in range(1, len(self.screendef)): self.screendef[k][0](self, self.screendef[k][1]) def closepage(sender): try: 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() except: sender.superview.close() def nofunction(sender): pass def function_1(sender): mypage.unitexts[0].text = 'Oh! You clicked my button.' if __name__ == '__main__': unilabel = unipage.unilabel uniframe = unipage.uniframe unitext = unipage.unitext unibutton = unipage.unibutton unimage = unipage.unimage widgets = [(0, 0), (uniframe,(0, 0, 600, 450,(.6,.6,.6))), (unilabel,(40, 300, 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() if mypage.kivy: mypage.unimages[len(mypage.unimages) - 1].source = \ 'data/logo/kivy-icon-512.png' else: mypage.unimages[len(mypage.unimages) - 1].source = \ 'test:Pythonista' mypage.showpage()
- Set the font size for the
-
If you are using a lower resolution device such as most iPhones the optimal mode (0, 0) may not fit in landscape orientation. It will work fine in portrait though. This is because I cannot yet locate a reliable documentation for the height of title bar in a sheet presentation at any resolution. If you do want to use that orientation change the title_bar_height factor from 50 to 90.
-
@abcabc. What ever happened to your version? Because despite what @TiLeyon thinks you can not copyright ideas or conecpts thats what patents are for. Your code was comletely differnt that @TiLeyon and used a completely different approach. IMHO yous was also a lot cleaner approach.