PicoLisp GUI on Smartphones

With slight modifications, the standard PicoLisp GUI can be used to develop web applications for smartphones.

This is not about native apps. Those would present a more device-typical look & feel, but require platform-dependent code and rather different development methods. Instead, we focus on simple extensions to the standard PicoLisp Web GUI, with its server-based forms, sessions and database connectivity.

With the help of HTML5 most features of mobile devices are accessible, or will be so in the future. The little demo program explained in the following, for example, uses Using the +OnClick Button to obtain the current location.

The result works directly in any smartphone browser (and, of course, any desktop browser). If you wish to provide a clickable app - to save the user from the trouble of starting the browser - you can do so easily with the help of services like AppsGeyser.


Main concern is the small screen size. To make PicoLisp applications usable on smartphones, take the following three steps:

  1. Include "@lib/phone.css" in the list of CSS files
  2. Use <bar> instead of <menu>
  3. Reduce the number of components per page
Points 1 and 3 should be obvious. Point 2 refers to the only significant change.

<bar> is a new function, which implements a reduced version of <menu>. The usual menu produced by <menu> is relatively large, a vertical list of items, on the left side of the screen, which expands to submenu(s) and subitem(s) of arbitrary depth.

<bar>, on the other hand, is a horizontal menu bar on the top of the screen, with only one level of pull down menus. A click opens a submenu, a second click will close it again.

Also, while <menu> allows you to open and close submenus independently, <bar> automatically closes a submenu whenever another submenu is opened.

Phone GUI Demo

I wrote a simple demo program, and installed it on our server. You find the complete source code at the end of this article.

To try it, you can either point your browser (smartphone or not) to http://phone.picolisp.com, or download an installable app (Android only) from http://www.appsgeyser.com/getwidget/Pil%20Demo or via this QR-Code. PilDemoQR.png

The "login" page accepts any user name and password ;-)

The menu bar has three entries. "App" let's you to navigate back to the start page, or logout directly.

"Text" shows some typical text components and scrollable charts. Some of the fields are filled upon button presses with data like local date and time. "Life" shows a simple text animation of Conway's Game of Life.

Two charts are provided, one of fixed length, and one unlimited, generating data while scrolling down. Note that you can scroll the charts not only with the standard buttons ("<", "<<", ">" and ">>"), but also with the usual swipe gestures.

The "Graphics" menu leads to a Canvas demo ("Forest Fire" simulation) and an interface to Google Maps (requesting your Geo location upon button press).

If you copy the source code to a local file (e.g. "misc/phone.l"), you can also run it locally as
   $ pil misc/phone.l -main -go +
For simplicity, this demo doesn't utilize a database. For a database application you would basically replace all occurrences of the +Var prefix class by +E/R.

Demo Source

   # 31dec13abu
   # (c) Software Lab. Alexander Burger

   (allowed ("!demo")
      "!work" "!stop" "@lib.css" "@lib/phone.css" setLocation )

      "@lib/http.l" "@lib/xhtml.l" "@lib/form.l"
      "@lib/adm.l" "@lib/simul.l" "@lib/canvas.l" )

      *Latitude   48490773
      *Longitude  10849270
      *CanvasDX   240
      *CanvasDY   180 )

   (de setLocation (Lat Lon)
      (setq *Latitude Lat)
      (setq *Longitude Lon) )

   (de bar (Ttl . Prg)
         (html 0 Ttl '("@lib.css" "@lib/phone.css") NIL
            (<ping> 2)
               ((0 36 'bar)
                  (<div> @
                     (when *Login
                              ("Home" "!work")
                              ("logout" (and *Login "!stop")) )
                              ("Fields" (and *Login "!demoFields"))
                              ("Charts" (and *Login "!demoCharts")) )
                              ("Canvas" (and *Login "!demoCanvas"))
                              ("Maps" (and *Login "!demoMaps")) ) ) ) ) )
               ((0 NIL 'main)
                  (<div> (bar? @)
                     (run Prg 1) ) ) ) ) ) )

   (de work ()
      (setq *Url "!work")
      (when (app)
         (seed (in "/dev/urandom" (rd 8))) )
      (bar "Pil Demo"
         (<h3> "fh" "PicoLisp Phone Demo")
         (<img> "@img/logo.png" "PicoLisp Logo")
         (loginForm) ) )

   (de demoFields ()
      (bar "Text Fields"
         (form NIL
            (<h3> "fh" "Text Fields")
                  (<grid> 2
                     "Text" (gui '(+Var +TextField) '*DemoText 20)
                     "E-Mail" (gui '(+Var +MailField) '*DemoMail 20)
                     "Checkbox" (gui '(+Var +Checkbox) '*DemoCheck)
                     "Radio A" (gui '(+Var +Radio) '*DemoRadio NIL "A")
                     "Radio B" (gui '(+Radio)  -1 "B")
                     "Radio C" (gui '(+Radio)  -2 "C") )
                  (<grid> 3
                     "Date" (gui '(+Var +DateField) '*DemoDate 10)
                     (gui '(+JS +Button) "Today" '(set> (field -1) (date)))
                     "Time" (gui '(+Var +TimeField) '*DemoTime 10)
                     (gui '(+JS +Button) "Now" '(set> (field -1) (time)))
                     "Number" (gui '(+Var +NumField) '*DemoNum 10)
                     (gui '(+JS +Button) "Increment" '(inc '*DemoNum))
                     "Fixnum" (gui '(+Var +FixField) '*DemoFix 2 10)
                     (gui '(+JS +Button) "Increment" '(inc '*DemoFix 1.0)) ) )
                  (gui '(+Var +TextField) '*DemoArea 40 20) )
                     (n0 *Menu)
                     (for Col (=: grid (grid 20 20))
                        (for This Col
                           (=: life (rand T)) ) ) )
                  (gui '(+Style +View +TextField) "mono"
                           (for Col (: home grid)
                              (for This Col
                                 (link (if (: life) "X " "  ")) )
                              (link "^J") ) ) )
                     40 20 )
                  (gui '(+Click +Auto +Button) 420 'This 1000 '(pop *Throbber)
                     '(with (: home)
                        (for Col (: grid)
                           (for This Col
                              (let N  # Count neighbors
                                    '((Dir) (get (Dir This) 'life))
                                       west east south north
                                       ((X) (south (west X)))
                                       ((X) (north (west X)))
                                       ((X) (south (east X)))
                                       ((X) (north (east X))) ) )
                                 (=: next  # Next generation
                                    (if (: life)
                                       (>= 3 N 2)
                                       (= N 3) ) ) ) ) )
                        (for Col (: grid)  # Update
                           (for This Col
                              (=: life (: next)) ) ) ) ) ) )
            (<hr>) ) ) )

   (de demoCharts ()
      (bar "Charts"
         (form NIL
            (<h3> "fh" "Charts")
                  (gui '(+Init +Chart) (mapcar mkChartData (range 1 12)) 5)
                  (scroll 7 T) )
                  (gui '(+QueryChart) 7
                     '(goal '((for @N T) (^ @@ (mkChartData (-> @N)))))
                     5 )
                  (scroll 7) ) )
            (<hr>) ) ) )

   (de demoCanvas ()
      (bar "Canvas"
         (form NIL
            (<h3> "fh" "Forest Fire")
            (or *PRG (n0 *Menu)
               (for Col (setq *FireGrid (grid *CanvasDX *CanvasDY))
                  (for This Col
                     (=: tree (rand T)) ) ) )
            (<canvas> "$*FireGrid" *CanvasDX *CanvasDY)
            (javascript NIL "onload=drawCanvas('$*FireGrid', 40)") ) ) )

   (de demoMaps ()
      (bar "Maps"
         (form NIL
            (<h3> "fh" "Google Maps")
                  (<grid> 3
                     "Latitude" (gui '(+Var +FixField) '*Latitude 6 8)
                     (gui '(+OnClick +Button)
                        "var Form = this.form;
                           function(pos) {
                              lisp(Form, 'setLocation',
                                 pos.coords.latitude * 1000000,
                                 pos.coords.longitude * 1000000 )
                           function(e) {alert(e.message)} );
                        return false;"
                        "Get my location" )
                     "Longitude" (gui '(+Var +FixField) '*Longitude 6 8) ) )
                     "<iframe width="100%" height="320" frameborder="3" 
                     scrolling="no" marginheight="0" marginwidth="0" 
                     (format *Latitude 6)
                     (format *Longitude 6)
                     "&amp;iwloc=near&amp;output=embed"></iframe>" ) ) )
            (<hr>) ) ) )

   (de stop ()
      (work) )

   (undef 'login)
   (de login (Nm Pw)
      (setq *Login (new '(+Dummy) 'nm Nm)) )

   (de main ())

   (de go (Port)
      (server (or Port 8080) "!work") )

   ### Utility Functions ###
   (de mkChartData (I)
         (bit? 1 I)
               (char (+ 64 I))
               (need 5 (char (+ 96 I))) ) ) ) )

   (de mkDemoTable (Rows)
      (<table> NIL "Chart" '((btn) (align "Number") (NIL "Text"))
         (do 7
            (<row> NIL
               (gui 1 '(+Checkbox))
               (gui 2 '(+Sgn +NumField) 12)
               (gui 3 '(+TextField) 12)
               (gui 4 '(+DelRowButton))
               (gui 5 '(+BubbleButton)) ) ) ) )

   (de drawCanvas (Id Dly)
      (for Col *FireGrid
         (for This Col
            (=: next
                  ((: burn) NIL)
                  ((: tree)
                           (find  # Neighbor burning?
                              '((Dir) (get (Dir This) 'burn))
                                 west east south north
                                 ((X) (south (west X)))
                                 ((X) (north (west X)))
                                 ((X) (south (east X)))
                                 ((X) (north (east X))) ) )
                           (=0 (rand 0 9999)) )
                        'tree ) )
                  (T (and (=0 (rand 0 99)) 'tree)) ) ) ) )
      (for Col *FireGrid
         (for This Col
            (if (: next)
               (put This @ T)
               (=: burn)
               (=: tree) ) ) )
         (csClearRect 0 0 *CanvasDX *CanvasDY)
         (csFillStyle "green")
         (csDrawDots 1 1
               (for (X . Col) *FireGrid
                  (for (Y . This) Col
                     (and (: tree) (link X Y)) ) ) ) )
         (csFillStyle "red")
         (csDrawDots 3 3
               (for (X . Col) *FireGrid
                  (for (Y . This) Col
                     (and (: burn) (link X Y)) ) ) ) ) ) )

   # vi:et:ts=3:sw=3


19mar14   abu