native widgets

View on GitHub
Download as .tar.gz
Download as .zip

NOTE: work-in-progress (to-be-released soon)

local nw = require'nw'

Cross-platform library for displaying and manipulating native windows, drawing in their client area using cairo or opengl, and accessing keyboard, mouse and touchpad events in a consistent and well-specified manner across Windows, Linux and OS X.

Jump to: Quick Example | Features | Behavior

application loop
nw:app() -> app return the application object (singleton)
app:run() run the main loop until the last window is closed
app:quit() close all windows; abandon on the first window that refuses to close.
app:displays() -> iter() -> display get displays in no specific order
app:main_display() -> display get the display whose screen rect starts at (0,0)
app:screen_rect([display]) -> x, y, w, h display's screen rectangle
app:desktop_rect([display]) -> x, y, w, h display's screen rectangle excluding the taskbar
app:time() -> time get an opaque time object representing a hi-resolution timestamp
app:timediff(time1, time2) -> ms get the difference between two time objects
app:windows() -> iter() -> win iterate app's windows in creation order
app:active_window() -> win get the active window, if any
app:window(t) -> win create a window (fields of t below)
state options
t.x, t.y, t.w, t.h (required) window's frame rectangle
t.visible (true) window is visible
t.title (empty) window title
t.state ('normal') window state: 'normal', 'maximized', 'minimized'
t.fullscreen (false) fullscreen mode (orthogonal to state)
t.topmost (false) always stay on top of all other windows
frame options
t.frame (true) window has a frame, border and title bar
t.transparent (false) window is transparent, has no frame and is not directly resizeable
t.minimizable (true) enable the minimize button
t.maximizable (true) enable the maximize button
t.closeable (true) enable the close button / allow closing the window
t.resizeable (true) window is user-resizeable
window lifetime
win:free() close the window and destroy it
win:dead() -> true|false check if the window was destroyed
win:closing() event: closing; return false to prevent it
win:closed() event: closed (but not dead yet)
window focus
win:activate() activate the window
win:active() -> true|false check if the window is active
win:activated() event: window was activated
win:deactivated() event: window was deactivated
window state
win:show([state]) show it, in its current state or in a new state
win:hide() hide it (orthogonal to state)
win:visible([visible]) -> true|false get/set visibility
win:state([state]) -> state get/set state: 'normal', 'maximized', 'minimized'
win:topmost([true]) -> topmost get/set the topmost flag
win:fullscreen([on]) -> true|false get/set fullscreen mode (orthogonal to state)
win:frame_rect([x, y, w, h]) -> x, y, w, h get/set the frame rect of the 'normal' state
win:display() -> display the display the window is on
win:frame_changing(how, x, y, w, h) event: moving (how = 'move'), or resizing (how = 'left', 'right', 'top', 'bottom', 'topleft', 'topright', 'bottomleft', 'bottomright'); return different (x, y, w, h) to constrain
win:frame_changed() event: window was moved, resized or state changed
win:title([title]) -> title get/set the window's title
win:save() -> t save user-changeable state
win:load(t) load user-changeable state
window frame
win:frame(flag) -> value get frame flags: 'frame', 'transparent', 'minimizable', 'maximizable', 'closeable', 'resizeable'
keyboard events
win:key(keyname) -> down[, toggled] get key and toggle state (see source for key names, or print keys on keydown)
win:keydown(key) event: a key was pressed
win:keyup(key) event: a key was depressed
win:keypress(key) event: sent on each key down, including repeats
win:keychar(char) event: sent after key_press for displayable characters; char is utf-8
mouse events
win:hover() event: mouse entered the client area of the window
win:leave() event: mouse left the client area of the window
win:mousemove(x, y) event: mouse move
win:mousedown(button) event: a mouse button was pressed: 'left', 'right', 'middle', 'ex1', 'ex2'
win:mouseup(button) event: a mouse button was depressed
win:click(button, count) event: a mouse button was pressed (see notes for double-click)
win:wheel(delta) event: mouse wheel was moved
win:hwheel(delta) event: mouse horizontal wheel was moved
mouse state
win.mouse a table with fields: x, y, left, right, middle, xbutton1, xbutton2, inside
win:render(cr) event: redraw the window client area using the given cairo context
win:invalidate() request window redrawing
win:client_rect() -> x, y, w, h get the client area rect (relative to itself)
win:event(event, ...) post an event
win:observe(event, func(...) end) observe an event i.e. call func when event happens
app:<event>(win, ...) window events are forwarded to the app object
app.window_class the table that windows inherit from
app.window_class.defaults default values for window creation arguments
nw.impl nw implementation class

Quick Example

local nw = require'nw'

local app = nw:app()

local win = app:window{x = 100, y = 100, w = 400, h = 200, title = 'hello'}

function win:click(button, count)
   if button == 'left' and count == 3 then --triple click

function win:keydown(key)
   if key == 'F11' then
      self:fullscreen(not self:fullscreen())

app:run() --start the main loop


  • frameless transparent windows
  • edge snapping
  • full screen mode
  • multi-monitor support
  • complete access to the US keyboard
  • triple-click events
  • multi-touch gestures
  • unicode


  • consistent and fully-specified behavior accross all supported platforms
  • no platform-specific features except for supporting platform idioms
  • unspecified behavior is a bug
  • unsupported parameter combinations are errors
  • properties for state are orthogonal to each other
  • iterators are stable and with specified order


  • cocoa: OSX 10.7+
  • winapi: Windows XP/2000+


State variables

State variables are independent of each other, so a window can be maximized, in full screen mode and hidden all at the same time. Changing the state to 'minimized' won't affect the fact that the window is still hidden, nor that it is in full screen mode. If the window is shown, it will be in full screen mode. Out of full screen mode it will be minimized. Likewise, moving or resizing the window affects the frame rectangle of the 'normal' mode. If the window is maximized, resizing it won't have an immediate effect, but changing the state to 'normal' will show the window in its new size.

Maximizing or restoring a window while visible has the side effect of activating the window, if it's not active already.

Closing windows

Closing a window destroys it by default. You can prevent that by returning false on the closing event.

function win:closing()
   return false --prevent destruction

Closing the app

The app:run() call returns after the last window is destroyed. Because of that, app:quit() only has to close all windows, and it tries to do that in reverse-creation order.


When the user clicks the mouse repeatedly, with a small enough interval between clicks and over the same target, a counter is incremented. When the interval between two clicks is larger than the threshold or the mouse is moved too far away from the initial target, the counter is reset (i.e. the click-chain is interrupted). Returning true on the click event also resets the counter (i.e. interrupts the click chain).

This allows processing of double-clicks, triple-clicks, or multi-clicks by checking the count argument on the click event. If your app doesn't need to process double-clicks or multi-clicks, you can just ignore the count argument. If it does, you must return true after processing the multi-click event so that the counter is reset.

For instance, if your app supports double-click over some target, you must return true when count is 2, otherwise you might get a count of 3 on the next click sometimes, instead of 1 as expected. If your app supports both double-click and triple-click over a target, you must return true when the count is 3 to break the click chain, but you must not return anything when the count is 2, or you'll never get a count of 3.

Corner cases

  • calling any method on a closed window results in error, except for win:free() which does nothing.
  • calling app:run() while running is a no op.
  • app:windows() can return dead windows (but not new windows).
  • calling display functions on an invalid display object results in error (monitors can come and go too you know).