Cross-platform library for accessing windows, graphics and input in a consistent manner across Windows, Linux and OS X. Supports transparent windows, bgra8 bitmaps everywhere, drawing via [cairo] and [opengl], edge snapping, fullscreen mode, multiple displays, hi-dpi, key mappings, triple-click events, timers, cursors, native menus, notification icons, all text in utf8, and more.
See issues and milestones.
API Library Developed & Tested On Probably Runs On
WinAPI [winapi] Windows 7 x64 Windows XP/2000 Cocoa [objc] OSX 10.12 OSX 10.9 Xlib [xlib] Ubuntu/Unity 18.04 x64 Ubuntu/Unity 10.04
local nw = require'nw'
local app = nw:app() --get the app singleton
local win = app:window{ --create a new window
w = 400, h = 200, --specify window's frame size
title = 'hello', --specify window's title
visible = false, --don't show it yet
}
function win:click(button, count) --this is one way to bind events
if button == 'left' and count == 3 then --triple click
app:quit()
end
end
--this is another way to bind events which allows setting multiple
--handlers for the same event type.
win:on('keydown', function(self, key)
if key == 'F11' then
self:fullscreen(not self:fullscreen()) --toggle fullscreen state
end
end)
function win:repaint() --called when window needs repainting
local bmp = win:bitmap() --get the window's bitmap
local cr = bmp:cairo() --get a cairo drawing context
cr:rgb(0, 1, 0) --make it green
cr:paint()
end
win:show() --show it now that it was properly set up
app:run() --start the event loop
NOTE: In the table below, foo(t|f) /-> t|f
is a shortcut for saying
that foo(t|f)
sets the value of foo and foo() -> t|f
gets it.
t|f
means true|false
.
the app object
nw:app() -> app
the global application object
the app loop
app:run()
start the loop
app:stop()
stop the loop
app:running() -> t|f
check if the loop is running
app:poll([timeout]) -> t|f
process the next pending event (return true if there was one)
app:maxfps(fps) -> fps
cap the window repaint rate
quitting
app:quit()
quit the app, i.e. close all windows and stop the loop
app:autoquit(t|f) /-> t|f
quit the app when the last visible window is closed (true
)
app:quitting() -> [false]
event: quitting (return false
to refuse)
win:autoquit(t|f) /-> t|f
quit the app when the window is closed (false
)
timers
app:runevery(seconds, func)
run a function on a timer (return false
to stop it)
app:runafter(seconds, func)
run a function on a timer once
app:run(func)
run a function on a zero-second timer once
app:sleep(seconds)
sleep without blocking an app:run() function
window tracking
app:windows(['#',][filter]) -> {win1, ...}
all windows in creation order
app:window_created(win)
event: a window was created
app:window_closed(win)
event: a window was closed
window creation
app:window(t|cw,ch,[title],[vis]) -> win
create a window
window closing
win:close([reason|force])
close the window and hide it or destroy it
win:free([force])
close the window and destroy it
win:hideonclose(t|f) /-> t|f
hide on close or destroy on close
win:dead() -> t|f
check if the window was destroyed
win:closing(reason, [closing_win])
event: closing (return false
to refuse)
win:closed()
event: window is about to be destroyed
win:closeable() -> t|f
closeable flag
window & app activation
app/win:active() -> t|f
check if app/window is active
app:activate([mode])
activate the app
app:active_window() -> win
the active window, if any
win:activate()
activate the window
win:activable() -> t|f
activable flag
app/win:activated()
event: app/window was activated
app/win:deactivated()
event: app/window was deactivated
app instances
app:check_single_instance()
single app instance check
app.id
set an app ID
app:already_running() -> t|f
check if other app instances running
app:wakeup_other_instances()
send wakeup event to other app instances
app:wakeup()
event: wakeup from another instance
app visibility (OSX)
app:visible(t|f) /-> t|f
get/set app visibility
app:hide()
hide the app
app:unhide()
unhide the app
app:hidden()
event: app was hidden
app:unhidden()
event: app was unhidden
window state
win:visible(t|f) /-> t|f
get/set window visibility
win:show()
show window (in its previous state)
win:hide()
hide window
win:shown()
event: window was shown
win:hidden()
event: window was hidden
win:minimizable() -> t|f
minimizable flag
win:isminimized() -> t|f
check if the window is minimized
win:minimize()
minimize the window
win:minimized()
event: window was minimized
win:unminimized()
event: window was unminimized
win:maximizable() -> t|f
maximizable flag
win:ismaximized() -> t|f
check if the window is maximized
win:maximize()
maximize the window
win:maximized()
event: window was maximized
win:unmaximized()
event: window was unmaximized
win:fullscreenable() -> t|f
fullscreenable flag
win:fullscreen(t|f) /-> t|f
get/enter/exit fullscreen mode
win:entered_fullscreen()
event: entered fullscreen mode
win:exited_fullscreen()
event: exited fullscreen mode
win:restore()
restore from minimized or maximized state
win:shownormal()
show in normal state
win:showmodal()
show as modal window
win:changed(old_state, new_state)
event: window state changed
app:changed(old_state, new_state)
event: app state changed
win:enabled(t|f) /-> t|f
get/set window enabled flag
frame extents
app:frame_extents(...) -> ...
frame extents for a frame type
app:client_to_frame(...) -> ...
client rect -> window frame rect conversion
app:frame_to_client(...) -> ...
window frame rect -> client rect conversion
size and position
win:client_rect(x,y,w,h) /-> x,y,w,h
get/set client rect
win:frame_rect(x,y,w,h) /-> x,y,w,h
get/set frame rect
win:client_size(cw, ch) /-> cw, ch
get/set client rect size
win/view:to_screen(x, y) -> x, y
client space -> screen space conversion
win/view:to_client(x, y) -> x, y
screen space -> client space conversion
win:normal_frame_rect() -> x,y,w,h
get frame rect in normal state
win:sizing(when, how, rect)
event: window size/position is about to change
win:frame_rect_changed(x, y, w, h, ...)
event: window frame was moved and/or resized
win:frame_moved(x, y, oldx, oldy)
event: window frame was moved
win:frame_resized(w, h, oldw, oldh)
event: window frame was resized
win:client_rect_changed(cx,cy,cw,ch,...)
event: window client area was moved and/or resized
win:client_moved(cx, cy, oldcx, oldcy)
event: window client area was moved
win:client_resized(cw, ch, oldcw, oldch)
event: window client area was resized
win:hittest(x, y) -> where
event: hit test for frameless windows
size constraints
win:resizeable() -> t|f
resizeable flag
win:minsize(cw, ch) /-> cw, ch
get/set min client rect size
win:maxsize(cw, ch) /-> cw, ch
get/set max client rect size
window edge snapping
win:edgesnapping(mode) /-> mode
get/set edge snapping mode
win:magnets(which) -> {r1, ...}
event: get edge snapping rectangles
window z-order
win:topmost(t|f) /-> t|f
get/set the topmost flag
win:raise([rel_to_win])
raise above all windows/specific window
win:lower([rel_to_win])
lower below all windows/specific window
window title
win:title(title) /-> title
get/set title
displays
app:displays() -> {disp1, ...}
get displays (in no specific order)
app:main_display() -> disp
the display whose screen rect starts at (0,0)
app:active_display() -> disp
the display which contains the active window
disp:screen_rect() -> x, y, w, h
display's screen rectangle
disp.x, disp.y, disp.w, disp.h
disp:desktop_rect() -> cx, cy, cw, ch
display's screen rectangle minus the taskbar
disp.cx, disp.cy, disp.cw, disp.ch
app:displays_changed()
event: displays changed
win:display() -> disp|nil
the display the window is on
cursors
win:cursor(name|t|f) /-> name, t|f
get/set the mouse cursor and visibility
app:caret_blink_time() -> time | 1/0
caret blink time
frame flags
win:frame() -> frame
window's frame: 'normal', 'none', 'toolbox'
win:transparent() -> t|f
transparent flag
win:corner_radius() -> n
rounded corners (0)
child windows
win:parent() -> win|nil
window's parent
win:children() -> {win1, ...}
child windows
win:sticky() -> t|f
sticky flag
hi-dpi support
app:autoscaling(t|f) /-> t|f
get/enable/disable autoscaling
disp.scalingfactor
display's scaling factor
win:scalingfactor_changed()
event: a window's display scaling factor changed
views
win:views() -> {view1, ...}
list views
win:view(t) -> view
create a view
view:free()
destroy the view
view:dead() -> t|f
check if the view was freed
view:visible(t|f) /-> t|f
get/set view's visibility
view:show()
show the view
view:hide()
hide the view
view:rect(x, y, w, h) /-> x, y, w, h
get/set view's position (in window's client space) and size
view:size(w, h) /-> w, h
get/set view's size
view:anchors(anchors) /-> anchors
get/set anchors
view:rect_changed(x, y, w, h)
event: view's size and/or position changed
view:moved(x, y, oldx, oldy)
event: view was moved
view:resized(w, h, oldw, oldh)
event: view was resized
keyboard
app:key(query) -> t|f
get key pressed and toggle states
win:keydown(key)
event: a key was pressed
win:keyup(key)
event: a key was depressed
win:keypress(key)
event: sent after each keydown, including repeats
win:keychar(s)
event: input char pressed; s is utf-8
mouse
app:mouse(var) -> val
mouse state: x, y, pos
win/view:mouse(var) -> val
mouse state: x, y, pos, inside, left, right, middle, x1, x2
win/view:mouseenter(x, y)
event: mouse entered the client area of the window
win/view:mouseleave()
event: mouse left the client area of the window
win/view:mousemove(x, y)
event: mouse was moved
win/view:mousedown(button, x, y, count)
event: mouse button was pressed
win/view:mouseup(button, x, y, count)
event: mouse button was depressed
win/view:click(button, count, x, y)
event: mouse button was clicked
win/view:mousewheel(delta, x, y, pdelta)
event: mouse wheel was moved
win/view:hmousewheel(delta, x, y, pdelta)
event: mouse horizontal wheel was moved
app:double_click_time() -> time
double click time
app:double_click_target_area() -> w, h
double click target area
rendering
win/view:repaint()
event: needs repainting
win/view:sync()
event: needs sync'ing
win/view:invalidate([invalid_clock])
request sync'ing and repainting
win/view:validate([at_clock])
request sync'ing if invalid
win/view:invalid([at_clock]) -> t|f
check if invalidated
win/view:bitmap() -> bmp
get a bgra8 [bitmap] object to draw on
bmp:clear()
fill the bitmap with zero bytes
bmp:cairo() -> cr
get a cairo context on the bitmap
win/view:free_cairo(cr)
event: cairo context needs freeing
win/view:free_bitmap(bmp)
event: bitmap needs freeing
win/view:gl() -> gl
get an OpenGL context to draw with
menus
app:menu() -> menu
create a menu (or menu bar)
app:menubar() -> menu
get app's menu bar (OSX)
win:menubar(menu|nil) /-> menu|nil
get/set/remove window's menu bar (Windows, Linux)
win/view:popup(menu, cx, cy)
pop up a menu relative to a window or view
menu:popup(win/view, cx, cy)
pop up a menu relative to a window or view
menu:add(...)
menu:set(...)
menu:remove(index)
menu:get(index) -> item
get the menu item at index
menu:get(index, prop) -> val
get the value of a property of the menu item at index
menu:items([prop]) -> {item1, ...}
menu:checked(index, t|f) /-> t|f
get/set a menu item's checked state
icons (common API)
icon:free()
icon:bitmap() -> bmp
get a bgra8 [bitmap] object
icon:invalidate()
request repainting
icon:repaint()
event: bitmap needs redrawing
icon:free_bitmap(bmp)
event: bitmap needs freeing
notification icons
app:notifyicon(t) -> icon
app:notifyicons() -> {icon1, ...}
list notification icons
icon:tooltip(s) /-> s
get/set icon's tooltip
icon:menu(menu) /-> menu
get/set icon's menu
icon:text(s) /-> s
get/set text (OSX)
icon:length(n) /-> n
get/set length (OSX)
window icon (Windows)
win:icon([which]) -> icon
window's icon ('big'); which can be: 'big', 'small'
dock icon (OSX)
app:dockicon() -> icon
file choose dialogs
app:opendialog(t) -> path|{path1,...}|nil
open a standard "open file" dialog
app:savedialog(t) -> path|nil
open a standard "save file" dialog
clipboard
app:getclipboard(format) -> data|nil
get data in clipboard (format is 'text', 'files', 'bitmap')
app:getclipboard() -> formats
get data formats in clipboard
app:setclipboard(f|data[, format])
clear or set clipboard
drag & drop
win/view:dropfiles(x, y, files)
event: files are dropped
win/view:dragging('enter',t,x,y) -> s
event: mouse enter with payload
win/view:dragging('hover',t,x,y) -> s
event: mouse move with payload
win/view:dragging('drop',t,x,y)
event: dropped the payload
win/view:dragging('leave')
event: mouse left with payload
tooltips
win:tooltip(text|f) -> text|f
get/set/hide tooltip text
events
app/win/view:on(event, func)
call func when event happens
app/win/view:off(event)
remove event handlers
app/win/view:fire(event, args...) -> ret
fire an event
app/win/view:events(enabled) -> prev_state
enable/disable events
app/win/view:event(name, args...)
meta-event fired on every other event
version checks
app:ver(query) -> t|f
check OS minimum version (eg. 'OSX 10.8')
extending
nw.backends -> {os -> module_name}
default backend modules for each OS
nw:init([backend_name])
init with a specific backend (can be called only once)
The global app object is the API from which everything else gets created.
Get the global application object.
This calls nw:init()
which initializes the library with the default
backend for the current platform.
Start the application main loop.
Calling run() when the loop is already running does nothing.
Stop the loop.
Calling stop() when the loop is not running does nothing.
Check if the loop is running.
Process the next pending event from the event queue.
Returns true
if there was an event to process, false
if there wasn't.
Returns false, exit_code
if the application was asked to quit.
timeout
(default=0) specifies a maximum wait time for an event to appear.
Get/set the maximum window repaint rate (frames per second).
1/0
disables the throttling. The default is 60
. Note that you still need
to call invalidate()
in order to trigger a repaint.
Quit the app, i.e. close all windows and stop the loop.
Quitting is a multi-phase process:
app:quitting()
event is fired. If it returnsfalse
, quitting is aborted.win:closing('quit', closing_win)
event is fired on all non-child windows, with the initial window as arg#2. If any of them returnsfalse
, quitting is aborted.win:free'force'
is called on all windows (in reverse-creation order).- the app loop is stopped.
Calling quit()
when the loop is not running or while quitting
is in progress does nothing.
Get/set the app autoquit flag (default: true
).
When this flag is true
, the app loop exists when the last visible non-child
window is closed.
Event: the app wants to quit, but nothing was done to that effect.
Return false
from this event to cancel the process.
Get/set the window autoquit flag (default: false
).
When this flag is true
, the app loop exists when the window is closed.
This flag can be used on the app's main window if there is such a thing.
Run a function on a recurrent timer.
The timer can be stopped by returning false
from the function.
Run a function on a timer once.
Run a function on a zero-second timer, once, inside a coroutine.
This allows calling app:sleep()
inside the function (see below).
If the loop is not already started, it is started and then stopped after the function finishes.
Sleep without blocking from inside a function that was run via app:run(). While the function is sleeping, other timers and events continue to be processed.
This is poor man's multi-threading based on timers and coroutines. It can be used to create complex temporal sequences withoug having to chain timer callbacks.
Calling sleep() outside an app:run() function raises an error.
Get all windows in creation order. If '#' is given, get the number of windows
(dead or alive) instead. An optional filter(self, win) -> false
function
can be used to filter the results in both cases.
Event: a window was created.
Event: a window was closed.
Create a window (fields of t
below with default value in parenthesis):
- position
x
,y
- frame positionw
,h
- frame sizecx
,cy
- client area positioncw
,ch
- client area sizemin_cw
,min_ch
- min client rect size (1, 1
)max_cw
,max_ch
- max client rect size
- state
visible
- start visible (true
)minimized
- start minimized (false
)maximized
- start maximized (false
)enabled
- start enabled (true)
- frame
frame
- frame type:'normal'
,'none'
,'toolbox'
('normal'
)title
- title (''
)transparent
- transparent window (false
)corner_radius
- rounded corners (0
)
- behavior
parent
- parent windowsticky
- moves with parent (false
)topmost
- stays on top of other non-topmost windows (false
)minimizable
- allow minimization (true
)maximizable
- allow maximization (true
;false
ifresizeable
isfalse
)closeable
- allow closing (true
)resizeable
- allow resizing (true
)fullscreenable
- allow fullscreen mode (true
;false
ifresizeable
isfalse
)activable
- allow activation (true
)autoquit
- quit the app on closing (false
)hideonclose
- hide on close instead of destroying (true
)edgesnapping
- magnetized edges ('screen'
)
- rendering
opengl
- enable and configure OpenGL on the window
- menu
menu
- the menu bar
- tooltip
tooltip
- tooltip text (false
)
You can pass any combination of x
, y
, w
, h
, cx
, cy
, cw
, ch
as long as you pass both the width and the height in one way or another.
The position is optional and it defaults to OS-driven cascading.
Additionally, x
and/or y
can be 'center-main'
or 'center-active'
which will center the window on the main or active display respectively.
If the size is max-constrained by either max_cw
, max_ch
or resizeable = false
then maximizable = false
and
fullscreenable = false
must also be set.
Expect the OS to adjust the window size and/or position in unspecified ways for off-screen windows, windows too small to fit all titlebar buttons, windows with zero or negative client size or windows that are very large. Some adjustments are delayed to when the window is shown.
The window state is the combination of multiple flags (minimized
,
maximized
, fullscreen
, visible
, active
) plus its position, size
and frame in current state (client_rect
and frame_rect
), and in normal
state (normal_frame_rect
).
State flags are independent of each other, so they can be in almost
any combination at the same time. For example, a window which starts
with {visible = false, minimized = true, maximized = true}
is initially hidden. If later made visible with win:show()
,
it will show minimized. If the user then unminimizes it, it will restore
to maximized state. Throughout all these stages the maximized
flag
is true
.
- window-relative positions are relative to the top-left corner of the window's client area.
- screen-relative positions are relative to the top-left corner of the main screen.
Child windows (parent = win
) are top-level windows (so framed, not clipped)
that stay on top of their parent, minimize along with their parent,
and don't appear in the taskbar.
The following defaults are different for child windows:
minimizable
: false (must be false)maximizable
: falsefullscreenable
: falseedgesnapping
: 'parent siblings screen'sticky
: true
Child windows can't be minimizable because they don't appear in the taskbar (they minimize when their parent is minimized). Child windows remain visible if their parent is hidden (or is created hidden).
Get the window's parent (read-only).
Get the window's children (those whose parent() is this window).
Sticky windows (sticky = true
) follow their parent when their parent is moved.
NOTE: Sticky windows don't work on Linux.
Get the sticky flag (read-only).
Toolbox windows (frame = 'toolbox'
) show a thin title bar on Windows
(they show a normal frame on OSX and Linux). They must have a parent.
Transparent windows (transparent = true
) allow using the full alpha channel
when drawing on them. They also come with serious limitations (mostly from Windows):
- they can't be framed so you must pass
frame = 'none'
. - they can't have views.
- you can't draw on them using OpenGL.
Despite these limitations, transparent windows are the only way to create free-floating tooltips and custom-shaped notification windows.
Get the transparent flag (read-only).
Closing the window hides it or destroys it depending on the hideonclose
flag.
You can prevent closing by returning false
in the win:closing()
event.
Close the window. Children are closed first. The force
arg allows closing
the window without firing the win:closing()
event.
Calling close()
on a closed window does nothing.
Closing a window results in hiding it or freeing it, depending on the
hideonclose
flag.
Check if the window was destroyed. Calling any other method on a dead window raises an error.
Event: The window is about to close. Reason can be 'quit'
, 'close'
,
or the first argument passed to close()
.
When reason is 'close'
, closing_win
is the window initiating the process.
Return false
from the event handler to refuse closing.
Event: The window was closed and is about to be destroyed.
Fired after all children are closed, but before the window itself is destroyed.
This event does not fire when hideonclose
is true
and the window is
closed by the user or by calling close()
(check the hidden
event then).
Get the closeable flag (read-only).
What to do when a window is closed: hide it or destroying it.
Close and destroy the window (same as close()
when hideonclose
is set
to false
).
NOTE: Ensure that all the windows are freed before the process exits
(which is why the autoquit
option calls free()
on the windows instead
of close()
which might just hide them). Don't leave it to the gc to free
window objects because a window object contains other gc'ed objects that
need to be freed in a specific order but the order in which ffi.gc
destructors are called is undefined when the window object is gc'ed.
Activation is about app activation and window activation. Activating a window programatically has an immediate effect only while the app is active. If the app is inactive, the window is not activated until the app becomes active and the user is notified in some other less intrusive way.
If the user activates a different app in the interval between app launch and first window being shown, the app won't be activated back (this is a good thing usability-wise). This doesn't work on Linux (new windows always pop in your face because there's no concept of an "app" really in X).
Check if the app is active.
Activate the app, which activates the last window that was active before the app got deactivated.
The mode arg can be:
- 'alert' (default; Windows and OSX only; on Linux it does nothing)
- 'force' (OSX and Linux only; on Windows it's the same as 'alert')
- 'info' (OSX only; on Windows it's the same as 'alert'; on Linux it does nothing)
The 'alert' mode: on Windows, this flashes the window on the taskbar until the user activates the window. On OSX it bounces the dock icon until the user activates the app. On Linux it does nothing.
The 'force' mode: on Windows this is the same as the 'alert' mode. On OSX and Linux it pops up the window in the user's face (very rude, don't do it).
The 'info' mode: this special mode allows bouncing up the dock icon on OSX only once. On other platforms it's the same as the default 'alert' mode.
Event: the app was activated/deactivated.
Get the active window, if any (nil if the app is inactive).
Check if the window is active (false
for all windows if the app is inactive).
Activate the window. If the app is inactive, this does not activate the window.
Instead it only marks the window to be activated when the app becomes active.
If you want to alert the user that it should pay attention to the app/window,
call app:activate()
after calling this function.
Event: window was activated/deactivated.
Get the activable flag (read-only). This is useful for creating popup menus that can be clicked on without stealing keyboard focus away from the main window.
NOTE: Only works with frameless windows.
NOTE: This doesn't work in Linux.
If another instance of this app is already running, activate it and exit
this process. Calling this at the beginning of the app (after setting
nw.app_id
if that's necessasry) is enough to enable single-app instance
behavior.
Set the app ID for single-app-instance checks. All processes with the same app ID will be considered instances of the same app. If this is not set, the executable file which started the process is used as app ID.
NOTE: This must be set before calling nw:app()
for the first time.
Check if other instances of this app are running.
Send wakeup
event to other instances of this app.
Event: another instance of this app has called app:wakeup_other_instances()
.
Get/set app visibility.
app:hidden()
app:unhidden()
Event: app was hidden/unhidden.
Show the window in its previous state (which can include any combination of minimized, maximized, and fullscreen state flags).
When a hidden window is shown it is also activated, except if it was previously minimized, in which case it is shown in minimized state without being activated.
Calling show() on a visible (which includes minimized) window does nothing.
Hide the window from the screen and from the taskbar, preserving its full state.
Calling hide() on a hidden window does nothing.
Check if a window is visible (note: that includes minimized).
Calls show()
or hide()
to change the window's visibility.
win:shown()
win:hidden()
Event: window was shown/hidden.
Get the minimizable flag (read-only).
Get the minimized state. This flag remains true
when a minimized window is hidden.
Minimize the window and deactivate it. If the window is hidden, it is shown in minimized state (and the taskbar button is not activated).
Event: window was minimized/unminimized.
Get the maximizable flag (read-only).
Get the maximized state. This flag stays true
if a maximized window
is minimized, hidden or enters fullscreen mode.
Maximize the window and activate it. If the window was hidden, it is shown in maximized state and activated.
If the window is already maximized it is not activated.
Event: window was maximized/unmaximized.
Check if a window is allowed to go in fullscreen mode (read-only). This flag only affects OSX - the only platform which presents a fullscreen button on the title bar. Fullscreen mode can always be engaged programatically.
Get the fullscreen state.
Enter or exit fullscreen mode and activate the window. If the window is hidden or minimized, it is shown in fullscreen mode and activated.
If the window is already in the desired mode it is not activated.
Event: entered/exited fullscreen mode.
Restore from minimized, maximized or fullscreen state, i.e. unminimize if the window was minimized, exit fullscreen if it was in fullscreen mode, or unmaximize it if it was maximized (otherwise do nothing).
The window is always activated unless it was in normal mode.
Show the window in normal state.
The window is always activated even when it's already in normal mode.
State tracking is about getting and tracking the entire user-changeable state of a window (of or the app) as a whole.
Show as modal window to its parent. A modal window disables its parent while it is visible and enables it back when it gets hidden again. The window must be activable and must have a parent or an error is raised.
Event: window user-changeable state (i.e. any of the visible
, minimized
,
maximized
, fullscreen
or active
flags) has changed.
Event: app user-changeable state (i.e. the visible
or active
flag) has
changed.
Get/set the enabled flag (default: true). A disabled window cannot receive mouse or keyboard focus. Disabled windows are useful for implementing modal windows: make a child window and disable the parent while showing the child, and enable back the parent when closing the child.
NOTE: This doesn't work on Linux.
Get the frame extents for a certain frame type.
If has_menu
is true
, then the window also has a menu.
Given a client rectangle, return the frame rectangle for a certain
frame type. If has_menu
is true
, then the window also has a menu.
Given a frame rectangle, return the client rectangle for a certain
frame type. If has_menu
is true
, then the window also has a menu.
win:client_rect() -> cx, cy, cw, ch
win:client_rect(cx, cy, cw, ch)
win:frame_rect() -> x, y, w, h
win:frame_rect(x, y, w, h)
win:client_size() -> cw, ch
win:client_size(cw, ch)
Get/set the client/frame rect/size in screen coordinates.
When getting: returns nothing if the window is minimized.
When setting: if any of the arguments is nil or false
, it is replaced with
the current value of that argument to allow for partial changes. Does nothing
if the window is minimized, maximized, or in fullscreen mode.
Convert a point from client space to screen space and viceversa based on client_rect().
Get the frame rect in normal state (in screen coordinates). Unlinke client_rect() and frame_rect(), this always returns a rectangle. This is useful for recreating a window in its previous state which includes the normal frame rectangle, the maximized flag, and optionally the minimized flag. It doesn't include the fullscreen flag (you cannot create a window in fullscreen mode but you can enter fullscreen mode afterwards).
Event: window size/position is about to change. The rect
arg is a table
with the fields x, y, w, h. Change these values in the table to affect
the window's final size and position.
NOTE: This event does not fire in Linux.
win:client_rect_changed(cx, cy, cw, ch, oldcx, oldcy, oldcw, oldch)
win:client_moved(cx, cy, oldcx, oldcy)
win:client_resized(cw, ch, oldcw, oldch)
win:frame_rect_changed(x, y, w, h, oldx, oldy, oldw, oldh)
win:frame_moved(x, y, oldx, oldy)
win:frame_resized(w, h, oldw, oldh)
Event: window was moved/resized. These events also fire when a window is hidden or minimized in which case all args are nil, so make sure to test for that.
These events fire together every time in the same order:
client_rect_changed
client_moved
client_resized
frame_rect_changed
frame_moved
frame_resized
Hit test for moving and resizing frameless windows. Return 'left', 'top',
'right', 'bottom', 'topleft', 'bottomright', 'topright' or 'bottomleft'
to specify that the window should be resized, 'move' which means the window
should be moved, false
which means the coordinates are over the client area,
or nil which means that standard resizing should take place. The where
arg is the default response for the given coordinates.
Check if the window is resizeable.
Get/set/clear the minimum client rect size.
The constraint can be applied to one dimension only by passing false
or nil
for the other dimension. The window is resized if it was smaller than this size.
The size is clamped to maxsize if that is set. The size is finally clamped to
the minimum (1, 1) which is also the default.
Get/set/clear the maximum client rect size.
The constraint can be applied to one dimension only by passing false
or nil
for the other dimension. The window is resized if it was larger than this size.
The size is clamped to minsize if that is set. Trying to set this on a
maximizable or fullscreenable window raises an error.
Get/set edge snapping mode, which is a string containing any combination of the following words separated by spaces:
'app'
- snap to app's windows'other'
- snap to other apps' windows'parent'
- snap to parent window'siblings'
- snap to sibling windows'screen'
ortrue
- snap to screen edges'all'
- equivalent to 'app other screen'false
- disable snapping
NOTE: Edge snapping doesn't work on Linux because the win:sizing()
event doesn't fire there. It is however already (poorly) implemented
by some window managers (eg. Unity) so all is not lost.
Event: get edge snapping rectangles (rectangles are tables with fields x, y, w, h).
Get/set the topmost flag. A topmost window stays on top of all other non-topmost windows.
Raise above all windows/specific window.
Lower below all windows/specific window.
Get/set the window's title.
In multi-monitor setups, the non-mirroring displays are mapped on a virtual surface, with the main display's top-left corner at (0, 0).
Get displays (in no specific order). Mirroring displays are not included. If '#' is given, get the display count instead.
Get the display whose screen rect is at (0, 0).
Get the display which contains the active window, falling back to the main display if there is no active window.
Get the display's screen rectangle.
Get the display's desktop rectangle (screen minus any taskbars).
NOTE: This doesn't work in Linux for secondary monitors (it gives the screen rect).
Event: displays changed.
Get the display the window is currently on. Returns nil if the window is off-screen. Returns the correct display based on the window's coordinates even if the window is hidden.
Get/set the mouse cursor and/or visibility. The name can be:
- 'arrow' (default)
- 'text'
- 'hand'
- 'cross'
- 'forbidden'
- 'size_diag1' (i.e. NE-SW, forward-slash-looking)
- 'size_diag2' (i.e. NW-SE, backslash-looking)
- 'size_h'
- 'size_v'
- 'move'
- 'busy_arrow'
- 'top', 'left', 'right', 'bottom', 'topleft', 'topright', 'bottomleft', 'bottomright' (only different in Linux)
See [nw_keyboard] for the list of key names.
Get key pressed and toggle states. The query can be one or more
key names separated by spaces or by +
eg. 'alt+f3' or 'alt f3'.
The key name can start with ^
in which case the toggle state of that key
is queried instead eg. '^capslock' returns the toggle state of the caps lock
key while 'capslock' returns its pressed state. (only the capslock, numlock
and scrolllock keys have toggle states).
The key name can start with !
which checks that the key is not pressed.
Event: a key was pressed (not sent on repeat).
Event: a key was depressed.
Event: sent after keydown and on key repeat.
Event: sent after keypress for displayable characters; s
is a utf-8
string and can contain one or more code points.
By default, windows contents are scaled by the OS on Hi-DPI screens, so they look blurry but they are readable even if the app is unaware that it is showing on a dense screen. Making the app Hi-DPI-aware means telling the OS to disable this automatic raster scaling and allow the app to scale the UI itself (but this time in vector space) in order to make it readable again on a dense screen.
Check if autoscaling is enabled.
Enable/disable autoscaling.
NOTE: This function must be called before the OS stretcher kicks in, i.e. before creating any windows or calling any display APIs. It will silently fail otherwise.
The display's scaling factor is an attribute of display objects. This is 1 when autoscaling is enabled and > 1 when disabled and the display is hi-dpi.
If autoscaling is disabled, windows must check their display's scaling factor and scale the UI accordingly.
A window's display scaling factor changed or most likely the window was moved to a screen with a different scaling factor.
A view object defines a rectangular region within a window for drawing and receiving mouse events.
Views allow partitioning a window's client area into multiple non-overlapping regions that can be rendered using different technologies. In particular, you can use OpenGL on some views, while using bitmaps (and thus cairo) on others. This presents a simple solution to the problem of drawing an antialiased 2D UI around a 3D scene as an alternative to drawing on the textures of orto-projected quads. Views also allow placing native widgets alongside custom-painted areas on the same window.
NOTE: If you use views, bind all mouse events to the views. Do not mix window and view mouse events since the behavior of window mouse events in the presence of views is not consistent between platforms.
Get the window's views. If '#' is given, get the view count instead.
Create a view (fields of t
below):
x
,y
,w
,h
- view's position (in window's client space) and sizevisible
- start visible (default: true)anchors
- resizing anchors (default: 'lt'); can be 'ltrb'opengl
- enable and configure OpenGL on the view.
NOTE: The width and height are clamped to the minimum (1, 1).
Destroy the view.
Check if the view was destroyed.
Get/set the view's visibility.
The position and size of the view are preserved while hidden (anchors keep working).
Get/set the view's position (in window's client space) and size.
The view rect is valid and can be changed while the view is hidden.
Get/set the view's size.
Get/set the anchors: they can be any combination of 'ltrb' characters representing left, top, right and bottom anchors respectively.
Anchors are a simple but effective way of doing stitched layouting. This is how they work: there's four possible anchors which you can set, one for each side of the view. Setting an anchor on one side fixates the distance between that side and the same side of the window the view is on, so that when the window is moved/resized, the view is also moved/resized in order to preserve the initial distance to that side of the window.
Event: view's size and/or position changed.
Get the mouse state. The var
arg can be:
'x', 'y', 'pos', 'inside', 'left', 'right', 'middle', 'x1', 'x2'.
The mouse state is not queried: it is the state at the time of the last mouse event. Returns nothing if the window is hidden or minimized.
Mouse coordinates are relative to the window's client-area.
Event: mouse entered/left the client area of the window.
These events do not fire while the mouse is captured (see mousedown) but a mouseleave event will fire after mouseup if mouseup happens outside the client area of the window/view that captured the mouse.
Event: the mouse was moved.
Event: a mouse button was pressed; button can be 'left', 'right', 'middle', 'x1', 'x2'.
While a mouse button is down, the mouse is captured by the window/view which received the mousedown event, which means that the same window/view will continue to receive mousemove events even if the mouse leaves its client area.
Event: a mouse button was depressed.
Event: a mouse button was clicked (fires immediately after mousedown).
function win:click(button, count, x, y)
if count == 2 then --double click
...
elseif count == 3 then --triple click
...
return true --triple click is as high as we go in this app
end
end
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.
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 event with the highest count 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.
The double-click time interval is from the user's mouse settings and it is queried on every click.
Event: the mouse vertical or horizontal wheel was moved. The delta represents the number of lines to scroll.
The number of lines per scroll notch is from the user's mouse settings and it is queried on every wheel event (Windows, OSX).
The extra pixeldelta
arg is given on OSX on devices where analog scrolling
is available, in which case that value should be used instead.
Drawing on a window or view must be done inside the repaint()
event
by requesting the window/view's bitmap or OpenGL context and drawing on it.
The OS fires repaint
whenever it loses (part of) the contents
of the window. To force a repaint anytime, use win:invalidate()
.
NOTE: You can't request a bitmap on an OpenGL-enabled window/view
and you can't request an OpenGL context on a non-OpenGL-enabled window/view.
To enable OpenGL on a window/view you must pass an opengl
options table
to the window/view creation function (it can be an empty table or just true
).
Event: window needs repainting. To repaint the window, simply request the window's bitmap or OpenGL context and draw using that.
Request sync'ing and repainting. The optional invalid_clock
(which defaults
to -inf
) specifies the earliest time.clock()
when the window/view should
be repainted (this is useful for implementing delayed animations efficiently).
Check if the window/view is invalid at a specific time point (which defaults
to time.clock()
).
Fire the sync()
event if the window/view is invalid.
Event: window needs sync'ing. This event is fired before repaint()
,
but only as a result of calling invalidate()
.
The point of this function is to separate updating the logical representation
of a window or view (i.e. its layout) from updating its raster representation
(i.e. its pixels), so that in some parts of the code you can signal that the
layout was put in an inconsistent state and must be sync'ed on the next frame,
while in other parts of the code you can ask that the layout be sync'ed
immediately (eg. because you need to hit-test it on a mousemove
event),
and all this can happen between frames, independent of the repainting cycle.
Get a bgra8 [bitmap] object to draw on. The bitmap is freed and replaced when
the window's client area changes size. The bitmap must be requested inside
the repaint()
event for drawing purposes, but can also be requested outside
the repaint()
event for hit-testing purposes.
The alpha channel is not used unless this is a transparent window (note: views cannot be transparent).
Fill the bitmap with zeroes.
Get a [cairo] context on the bitmap. The context lasts as long as the bitmap lasts.
Event: cairo context needs to be freed.
Event: bitmap needs to be freed.
Get an OpenGL context/API to draw on the window or view. For this to work
OpenGL must be enabled on the window or view via the opengl
options table,
which can have the fields:
profile
- OpenGL profile to use: '1.0', '3.2' ('1.0')antialiasing
- enable antialiasing: 'supersample', 'multisample', true, false (false)samples
- number of samples for 'multisample' antialiasting (4)vsync
- vertical sync: true, false, swap-interval (true)
Create a menu.
Get the app's menu bar (OSX)
Get/set/remove the window's menu bar (Windows, Linux).
Pop up a menu at a point relative to a window or view.
menu:add([index, ]text, [action], [options])
menu:set(index, text, [action], [options])
menu:add{index =, text =, action =, <option> =}
menu:set{index =, text =, action =, <option> =}
Add/set a menu item. The options are:
action
- can be a function or another menu to be used as a submenutext
- the text to display:&
before a letter creates an access key\t
followed by a key combination creates a shortcut key- the empty string (the default) creates a separator
- eg.
'&Close\tAlt+F4'
shows as 'Close Alt+F4' and activates onAlt+C
and onAlt+F4
submenu
- a submenu (same as whenaction
is a submenu)enabled
- enabled state (true)checked
- checked state (false)
Remove menu item at index.
Get a menu item, or the value of one of its properties.
Get the menu items. If a property name is given, pluck the values of that property from the menu items instead. If '#' is given, get the item count instead.
Get/set the checked state of a menu item.
Free the icon.
Get the icon's bitmap.
Request icon redrawing.
Event: icon needs redrawing.
Event: the icon's bitmap needs to be freed.
Get the window's icon. The which
arg can be: 'big' (default), 'small'.
Get the app's dock icon.
Create a notification icon.
Get all the notification icons. If '#' is given, get the icon count instead.
Get/set the icon's tooltip.
Get/set a menu for the icon.
Get/set the status bar item's text (OSX only).
Get/set the status bar item's length (OSX only).
Open a standard "open file" dialog and wait for it to close. Fields of t
:
title
- dialog's titlefiletypes
- supported file extensions eg.{'txt', 'jpg', ...}
multiselect
- allow multiple selection (false)initial_dir
- initial dir
When multiselect = true
the dialog returns a list of paths,
otherwise it returns a path. If the user closes the dialog without
choosing a file, it returns ni.
Open a standard "save file" dialog and wait for it to close. Fields of t
:
title
dialog's titlefiletypes
- supported file extensions eg.{'txt', 'jpg', ...}
filename
- default filenameinitial_dir
- initial dir
If the user closes the dialog without choosing a file, it returns ni.
Get the clipboard contents in one of the available formats. The format can be:
- 'text' - returns a string.
- 'files' - returns
{path1, ...}
- 'bitmap' - returns a [bitmap]
Get the data formats ({format = true}
) currently in clipboard.
Clear or set the clipboard. Passing false
clears it, otherwise data
can be:
- a string (assuming 'text' format).
- a bitmap (assuming 'bitmap' format).
- a table
{format = ..., data = ...}
. - a list of strings (for format: 'files').
Event: files ({filename1, ...}
) are dropped over the window/view.
win/view:dragging('enter', t, x, y) -> s
win/view:dragging('hover', t, x, y) -> s
win/view:dragging('drop', t, x, y)
win/view:dragging('leave')
Event: something is being dragged over the window/view. The first arg corresponds to the following mouse events:
- 'enter' - mouse enter
- 'hover' - mouse move
- 'drop' - mouse button up
- 'leave' - mouse leave
The t
arg is a table cotaining the drag payload in one or more formats:
{format = data}
. The x
, y
args are the mouse coordinates in window/view
client space.
You can respond to the 'enter' and 'hover' stages by returning:
- 'copy' - show a cursor indicating that the data is being copied
- 'link' - show a cursor indicating that the data is being linked
- 'none' - show the normal arrow cursor
- 'abort' - show the forbidden icon
- true - means 'copy'
- false - means 'abort'
- nil/nothing - means 'abort'
Get/set/hide window's tooltip.
nw uses the [events] mixin to add events functionality to all app
, win
and view
objects. This means that:
- you can add methods to these objects named after the event and they will be called automatically when the event fires.
:on()
,:off()
and:fire()
methods are available on these objects and can be used for custom events too.
Enable/disable events.
Check that a certain backend API is at a specified version or beyond.
The query has the form '<API> <version>'
where API can be
'Windows', 'OSX' or 'X'.
Example: app:ver'OSX 10.8'
returns true
on OSX 10.8 and beyond.
For Windows you can use the following table to figure it out:
Release Version
Windows 10 10.0 (6.2) Windows Server 2016 TP 10.0 (6.2) Windows 8.1 6.3 (6.2) Windows Server 2012 R2 6.3 (6.2) Windows 8 6.2 Windows Server 2012 6.2 Windows 7 6.1 Windows Server 2008 R2 6.1 Windows Server 2008 6.0 Windows Vista 6.0 Windows Server 2003 R2 5.2 Windows Server 2003 5.2 Windows XP 64-Bit Edition 5.2 Windows XP 5.1 Windows 2000 5.0
NOTE: Apps not manifested for Windows 8.1 or Windows 10 will report platforms greater than 6.2 as 6.2 (the [luajit] package comes with proper manifest files).
The number one mistake you can make is to assume that all calls are blocking. It's very easy to make that mistake because some of them actually are blocking on some platforms (in order of sanity: Windows, OSX and Linux -- X11 is particularly bad because all calls are asynchronous there). In a perfect world they would all be blocking and non-failing which would make programming with them much more robust and intuitive. The real world is an unspecified mess. So never, ever mix queries with commands, i.e. never assume that after a state-changing function returns you can make any assumptions about the state of the objects involved.
Do not assume that events fire in a specific order. Even if they appear to do so on one platform, that may not hold true on another platform. For instance, do not assume that app the activation event fires before the window activation event or that one should cause the other to fire.
The visible
flag when creating windows defaults to true
, but you should
really create windows with visible = false
, set up all the event handlers
on them and then call win:show()
, otherwise you will not catch any events
that trigger before you set up the event handlers (sometimes that includes
the repaint()
event so you will be showing a non-painted window).
One of the goals of this library is to reduce undefined behavior, but there will always be corner cases that are not covered. If your app behaves differently when ported to another platform, please file up a bug report. Even for contradictory situations (like seetting a minimum size constraint that is larger than the maxium size constraint) there should be a single answer for all platforms, even if that answer is arbitrary.
This is one of the bigger bricks of luapower but it is one which lends itself well to community development. The frontend uses composition rather than inheritance to connect to the backend so the communication between the two is always explicit. Features are well separated functionally and visually in the code so they can be developed separately without much risk of regressions. The code is well commented and there's unit tests and interactive tests which cover most of the functionality. The code follows the luapower [coding-style] and [api-design] guidelines.
All the development planning, coordination and communication is done via github issues and milestones.
- level out platform differences for common functionality.
- do support platform idioms and platform-specific functionality.
- minimize the need for emulation of missing features by rethinking the API.
- take preventive measures to avoid platform behavior:
- raise errors for parameter combinations that are not universally supported.
- clamp values to universally supported ranges.
- make stable iterators with specified order or better yet, return arrays.
- seek orthogonality, but do add convenience methods where useful.