Traces Of

Code, Design and Random Thoughts

Clickable Widgets Experiment

Comments

If you have followed the developments of Übersicht, you will be aware of the 0.6 release which attempted to make Widgets clickable by default. In the new 0.7 release I’m reverting to using a shortcut key again and I thought I’d share what lead to this.

Clickable Widgets

I thought I found a great solution that would make widgets act more like Desktop icons, which means that they could be clicked on and interacted with at any time. I stumbled across the solution while working on something else, so it was not a planned feature, but the solution seemed very simple and even removed code. So it looked like a win win situation and a low hanging fruit to be released with other fixes.

It turns out that, not only did I overlook some obvious defects, but also the Übersicht widget community has been insanely creative, creating widgets and use cases that I had not imagined ever! After releasing v0.6 the bug reports start trickling in. Initially they all seemed like easy bugs to fix, but as more and more use cases started coming to light, it became apparent that not all would be supportable with this approach. What as even worse, some bugs started popping up that were very hard to reproduce and only surfaced after Übersicht was running for a while.

Reverting to Interaction Shortcuts

I learned a lot about the innards of OS X while working on this and many times I came very close to making clickable widgets work. It is one of these things that either has to work flawlessly or else it becomes very annoying very fast. In the end there was always those last 2% missing or one mysterious bug that would surface every once in a while (but often enough to be an issue) would remain.

Always clickable widgets was not necessarily something that people have asked for, nor did the previous solution of using a shortcut key seem to have big drawbacks for most people. Also, my spare time to work on Übersicht is limited and fixing this feature started to look like a massive time sink. Time which I could use to develop other great features, which actually have been requested but the community, instead.

What’s Next

Taken all into consideration, it was an easy decision to revert to using shortcut keys for now. Version 0.7, which has been released today, reverts to the old - pre v0.6 - behavior. I hope to be releasing other great features in the future and maybe I will pick this one up again at some point. In the meantime I’m curious to see what other great widgets you guys come up with. The enthusiasm and creativity that this simple little App inspires is awesome to see and a great motivator for me to keep working on it!

Thank you!

Background Filters for Übersicht

Comments

Today I want to reveal a ‘hidden’ feature of Übersicht that has been available since version 0.2. The reason for this secrecy is that it is in an early beta state and still somewhat cumbersome to use. However, the release of Mac OS X Yosemite is nigh and this will probably be a welcome addition to match the visual appearance of it. So I thought, better to get some people using it and get some feedback.

The hidden feature I am talking about is background filters. OS X Yosemite is full of them - most commonly blur filters. What they allow you to do is having beautiful translucent effects without sacrificing usability. Without filters, other content shining through translucent elements offers too many distractions. However, with the right filters you don’t so much see other content, but rather get the impression that the UI reacts to surrounding colors ever so slightly.

get your blur on

So how would one recreate Yosemite’s filters in Übersicht? Of course CSS3 filters have been available for use since the first release. The problem with CSS3 filters is that they can only be applied to a DOM element and not what’s behind a DOM element. To make matters worse, usually what’s behind Übersicht widgets is the desktop wallpaper, which is not even part of the same App, let alone the Übersicht DOM.

To work around this issue, I’ve created a way to choose a dedicated DOM element and make it display a slice of the desktop wallpaper. The slice is chosen so that it exactly matches what would normally behind said DOM element. To understand what I mean, lets dive straight into an example.

setting up

The image to the left shows a simple widget with a translucent background and no filters applied. It has only a single DOM element that shows the widget content. In order to get this widget ready for background filters we first need to create a more complex DOM structure:

1
2
3
4
render: (output) -> """
  <canvas class='bg-slice'></canvas>
  <div class='content'>#{output}</div>
"""

We’ve now created a canvas element which will later draw the wallpaper slice, and a content element which will hold the widget content and sits in front of the canvas element. Of course we need some CSS to make this work, which comes down to:

1
2
3
4
5
6
7
8
9
10
.content
  background: rgba(#fff, 0.5)

.bg-slice
  position: absolute
  top: 0
  left: 0
  width: 0
  height: 0
  -webkit-filter: blur(10px)

This applies the background color to the content element and makes the canvas span the entire widget, sitting behind the content. Note that using width and height is important or otherwise the canvas will have a default size 300xsomething pixels.

This code will make the widget look identical to the previous code: The canvas element is completely transparent so there is nothing to see and nothing to apply the blur filter to.

drawing the background slice

To make the canvas draw a slice of the wallpaper we can use the the new uebersicht.makeBgSlice method. It expects a canvas element as an argument and will measure the elements size, ask the App for a wallpaper slice of that size and finally draw the slice. For it to be able to measure the canvas size, it needs to be called after rendering has happened. For this we can use the new afterRender property, which - as the name suggests - gets invoked after render. This gives us following code:

1
2
afterRender: (domEl) ->
  uebersicht.makeBgSlice(el) for el in $(domEl).find '.bg-slice'

The result can be seen to the right. It looks pretty much what we are looking for, but keen observers will note that the background looks less blurred and more transparent towards the edges. This is a common problem with blur filters (for reasons I will not go into detail here) and corresponds to the blur radius we chose - 10px in this case.

the final 10px

To get this just right we need to compensate for the 10px blur radius by growing the canvas element 10px in every direction. Luckily we have Stylus to aid us here; we can make the blur radius a variable and add it to the canvas size. The code and result are shown below:

1
2
3
4
5
6
7
8
9
bg-blur = 10px

.bg-slice
  position: absolute
  top: -(bg-blur)
  left: -(bg-blur)
  width: 100% + 2*bg-blur
  height: 100% + 2*bg-blur
  -webkit-filter: blur(bg-blur)



This looks very much what we are looking for already and this approach lets us easily play with different blur radii. Of course there are many other CSS3 filters available, so feel free to experiment!

The final, complete widget is available here.

caveats

As you have probably noticed by now, getting this to work is a somewhat tedious process which could probably be streamlined. Some other issues remain, that might be harder to fix, though. You will notice a delay when switching to a desktop with a different wallpaper until all your filters display the new wallpaper image. Also you will notice a spike in CPU usage while the App processes the new wallpaper.

For these reasons, please be careful when using this feature and expect some changes in the future. In the meantime, happy styling and experimenting!

Übersicht 0.2

Comments

A new version of Übersicht has been released today, which brings a much requested feature: screen control. For those of you with multiple monitors, you can now select which monitor Übersicht runs on.

It should also remember which monitor(s) you used last and use it again whenever it is available. Note that it doesn’t remember the exact monitor, but rather which port the monitor was connected to. So if you connect a different monitor to the same port, it should use that one.

The screen-shot to the left also shows another new feature: manual refresh. You can now force your widgets to refresh manually, which is handy when you just woke your machine from sleep and don’t want to wait for your widgets to update.

Finally, there also have been improvements to error reporting to aid you in writing your own widgets. CoffeeSCript and Stylus compile errors were previously only logged to the system log, but now they will appear in the developer console as well.

This is the first release that has seen some community involvement and I’d like to thank every one who helped out! Special thanks go to Guillaume Boudreau and Siemen Sikkema.

Übersicht

Comments

Earlier today I released Übersicht, a little Mac app I originally wrote to scratch my own itch. It is essentially like GeekTool, which lets you run system commands and display their output on your desktop. The main difference that widgets are written in HTML5, which was originally motivated by these two main reasons:

  • I can plug in a different monitor without my widgets breaking
  • I can easily write my own widgets

So in essence the app is just a large WebView that is glued to your desktop and widgets are little snippets of HTML+CSS+JS. Of course you can’t run system commands from within a WebView, so the app comes with a NodeJs backend. For more details on that, you can checkout out the slides for my talk at the local Amsterdam JavaScript Meetup.

Of course, also checkout the app itself. It is released under the GPL license and the code can be found on GitHub!

Text Entry Woes for Mobile Web Apps

Comments

Sometimes building web apps feels like trying to build a house of cards while the browser might pull away the carpet from underneath you at any moment. Text entry on mobile is one of these issues were you, as a developer, have very little control over what you want to achieve versus what the browser thinks it is best for you.

Mobile browsers have a reason for this of course. When the mobile web came about, websites and web apps where not designed with a small screen and a software keyboard in mind. In order to still provide a decent experience when entering text, mobile browsers had to come up with some solutions. For Mobile Safari that solution was to overlay the soft keyboard on top of the page and scroll the page so that the focused text field is in center of the visible space above the keyboard.

dont push me, ‘cause …

While this works great on normal websites, in your carefully crafted web app you might run into something like this:

This might not seem like a big deal at the first glance, but when trying to create a seamless, near-native user experience, this can ruin your day. Submit buttons and other form elements that were carefully placed to be visible and easily reachable when entering text are now scrolled halfway off the screen. In combination with the unexpected movement it really makes the app feel broken.

window.scrollTo to the rescue

So what can we do about this. There is, to my knowledge, no clean work around. A lot of default behaviour of the browser can be tweaked using css, some kind of event.preventDefault() or by other means, but this isn’t one of them. What we can do, however, is to reset the scroll offset using javascript (or coffeescript in this case), so something like this:

1
$(textareaEl).on('focus', function () { window.scrollTo(0, 0) });

This is assuming that your web app takes the whole screen space and hides the address bar. However, it doesn’t quite work; the browser scrolls just slightly after the focus event fires, so the page scrolls anyway. Using a setTimeout works, but you rely on a magic number, plus it is hard to catch it exactly when it scrolls, so that there is a visible jump happening before the text field moves back in its correct position.

hard to catch

It turns out, that if you focus the text field programatically, there is no delay before the page scrolls, so the onfocus handler catches it just in time.

So the final solution boils down to:

  • stop the browser from focusing the textfield when the user taps it.
  • focus the text field programatically.
  • scroll window back to top when the focus event fires.

I need to emphasise that this is only a valid solution if your are dealing with a full-on web app, which is sized to fit the screen and is not user scalable. Double tap zoom, or pinch zoom will break with this approach (just on the text field, not the entire site)!

bolting the carpet to the floor

In order to stop the browser from focusing the text field we need to preventDefault on touchstart (touchend should work as well). This means we have to provide our own custom tap handler, since the default click behaviour won’t get triggered. The final solution could look something like this:

1
2
3
4
5
// onClick is our custom click/tap handler
onClick(myTextarea, function () { $(myTextarea).focus() });

// scroll back to top
$(myTextarea).on('focus', function () { window.scrollTo(0, 0) })

The custom click handler could look something like this:

onClick
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var onClick = function(domEl, clickHandler) {
    domEl = $(domEl); // make sure we have a query object

    var startTime, startPos, canceled;

    domEl.on('touchstart', function (event) {
         // this prevents the browser from firing a click event and
         // focusing the text field
        event.preventDefault();

        canceled  = false;
        startTime = event.timeStamp;
        startPos  = { x: event.pageX, y: event.pageY };
    });

    domEl.on('touchmove', function (event) {
        var dx = event.pageX - startPos.x,
            dy = event.pageY - startPos.y;

        // cancel click if finger moved more than 40px
        var distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
        canceled     = canceled || distance > 40;
    });

    domEl.on('touchend', function (event) {
        if(canceled) return;

        // don't trigger click if finger rested longer than 500ms
        if(event.timeStamp - startTime < 500)
            clickHandler();
    })
};

And there you have it; the text field stays in place, solid as a rock. We’ve bolted the carpet to the floor and can now happily continue to build our house of cards.

Mind you, this solution is for iOS only, so on Android or other mobile platforms/browsers it won’t work and even break things. There are other solutions for these platforms, which I won’t go into further detail here.

Finally, do remember that you are breaking default browser behaviour, which exists for a reason. So know what you are doing or else you might significantly reduce the usability of your app! Having a textfield stay hidden behind the soft keyboard, or preventing other necessary user interaction is hardly ever very user friendly ;).

New Blog

Comments

Here is to a fresh start!

Since posterous shut down I had to migrate my not-so-active blog, but wasn’t successful with importing all my old posts so far. Many of them are probably not worth the trouble. I do hope to move over the more interesting posts some time soon, though!

Stay tuned!