diff --git a/Nasal/canvas/gui.nas b/Nasal/canvas/gui.nas
index bab83e092..e1847d993 100644
--- a/Nasal/canvas/gui.nas
+++ b/Nasal/canvas/gui.nas
@@ -1,3 +1,46 @@
+var gui = {
+  widgets: {},
+  focused_window: nil
+};
+
+var gui_dir = getprop("/sim/fg-root") ~ "/Nasal/canvas/gui/";
+var loadGUIFile = func(file) io.load_nasal(gui_dir ~ file, "canvas");
+var loadWidget = func(name) loadGUIFile("widgets/" ~ name ~ ".nas");
+
+loadGUIFile("Style.nas");
+loadGUIFile("Widget.nas");
+loadGUIFile("styles/DefaultStyle.nas");
+loadWidget("Button");
+
+var style = DefaultStyle.new("AmbianceClassic");
+var WindowButton = {
+  new: func(parent, name)
+  {
+    var m = {
+      parents: [WindowButton, gui.widgets.Button.new(parent, nil, {"flat": 1})],
+      _name: name
+    };
+    m._focus_policy = m.NoFocus;
+    m._setRoot( parent.createChild("image", "WindowButton-" ~ name) );
+    return m;
+  },
+# protected:
+  _onStateChange: func
+  {
+    var file = style._dir_decoration ~ "/" ~ me._name;
+    file ~= me._window._focused ? "_focused" : "_unfocused";
+
+    if( me._active )
+      file ~= "_pressed";
+    else if( me._hover )
+      file ~= "_prelight";
+    else if( me._window._focused )
+      file ~= "_normal";
+
+    me._root.set("file", file ~ ".png");
+  }
+};
+
 var Window = {
   # Constructor
   #
@@ -7,7 +50,10 @@ var Window = {
     var ghost = _newWindowGhost(id);
     var m = {
       parents: [Window, PropertyElement, ghost],
-      _node: props.wrapNode(ghost._node_ghost)
+      _node: props.wrapNode(ghost._node_ghost),
+      _focused: 0,
+      _focused_widget: nil,
+      _widgets: []
     };
 
     m.setInt("content-size[0]", size[0]);
@@ -15,6 +61,7 @@ var Window = {
 
     # TODO better default position
     m.move(0,0);
+    m.setFocus();
 
     # arg = [child, listener_node, mode, is_child_event]
     setlistener(m._node, func m._propCallback(arg[0], arg[2]), 0, 2);
@@ -26,6 +73,8 @@ var Window = {
   # Destructor
   del: func
   {
+    me.clearFocus();
+
     if( me["_canvas"] != nil )
     {
       var placements = me._canvas.texture.getChildren("placement");
@@ -93,6 +142,42 @@ var Window = {
   {
     return wrapCanvas(me._getCanvasDecoration());
   },
+  addWidget: func(w)
+  {
+    append(me._widgets, w);
+    w._window = me;
+    if( size(me._widgets) == 2 )
+      w.setFocus();
+    w._onStateChange();
+    return me;
+  },
+  #
+  setFocus: func
+  {
+    if( me._focused )
+      return me;
+
+    if( gui.focused_window != nil )
+      gui.focused_window.clearFocus();
+
+    me._focused = 1;
+#    me.onFocusIn();
+    me._onStateChange();
+    gui.focused_window = me;
+    return me;
+  },
+  #
+  clearFocus: func
+  {
+    if( !me._focused )
+      return me;
+
+    me._focused = 0;
+#    me.onFocusOut();
+    me._onStateChange();
+    gui.focused_window = nil;
+    return me;
+  },
   setPosition: func(x, y)
   {
     me.setInt("tf/t[0]", x);
@@ -114,6 +199,26 @@ var Window = {
     # on writing the z-index the window always is moved to the top of all other
     # windows with the same z-index.
     me.setInt("z-index", me.get("z-index", 0));
+
+    me.setFocus();
+  },
+# protected:
+  _onStateChange: func
+  {
+    if( me._getCanvasDecoration() != nil )
+    {
+      # Stronger shadow for focused windows
+      me.getCanvasDecoration()
+        .set("image[1]/fill", me._focused ? "#000000" : "rgba(0,0,0,0.5)");
+
+      var suffix = me._focused ? "" : "-unfocused";
+      me._title_bar_bg.set("fill", style.getColor("title" ~ suffix));
+      me._title.set(       "fill", style.getColor("title-text" ~ suffix));
+      me._top_line.set(  "stroke", style.getColor("title-highlight" ~ suffix));
+    }
+
+    foreach(var w; me._widgets)
+      w._onStateChange();
   },
 # private:
   _propCallback: func(child, mode)
@@ -188,28 +293,25 @@ var Window = {
 
     var group_deco = canvas_deco.getGroup("decoration");
     var title_bar = group_deco.createChild("group", "title_bar");
-    title_bar
-      .rect( 0, 0,
-             me.get("size[0]"),
-             me.get("size[1]"), #25,
-             {"border-top-radius": border_radius} )
-      .setColorFill(0.25,0.24,0.22)
-      .setStrokeLineWidth(0);
-
-    var style_dir = "gui/styles/AmbianceClassic/";
+    me._title_bar_bg =
+      title_bar.rect( 0, 0,
+                      me.get("size[0]"),
+                      me.get("size[1]"),
+                      {"border-top-radius": border_radius} );
+    me._top_line = title_bar.createChild("path", "top-line")
+                            .moveTo(border_radius - 2, 2)
+                            .lineTo(me.get("size[0]") - border_radius + 2, 2);
 
     # close icon
     var x = 10;
     var y = 3;
     var w = 19;
     var h = 19;
-    var ico = title_bar.createChild("image", "icon-close")
-                       .set("file", style_dir ~ "close_focused_normal.png")
-                       .setTranslation(x,y);
-    ico.addEventListener("click", func me.del());
-    ico.addEventListener("mouseover", func ico.set("file", style_dir ~ "close_focused_prelight.png"));
-    ico.addEventListener("mousedown", func ico.set("file", style_dir ~ "close_focused_pressed.png"));
-    ico.addEventListener("mouseout",  func ico.set("file", style_dir ~ "close_focused_normal.png"));
+
+    var button_close = WindowButton.new(title_bar, "close")
+                                   .move(x, y);
+    button_close.onClick = func me.del();
+    me.addWidget(button_close);
 
     # title
     me._title = title_bar.createChild("text", "title")
@@ -223,10 +325,8 @@ var Window = {
     me._node.getNode("title", 1).alias(me._title._node.getPath() ~ "/text");
     me.set("title", title);
 
-    title_bar.addEventListener("drag", func(e) {
-      if( !ico.equals(e.target) )
-        me.move(e.deltaX, e.deltaY);
-    });
+    title_bar.addEventListener("drag", func(e) me.move(e.deltaX, e.deltaY));
+    me._onStateChange();
   }
 };
 
diff --git a/Nasal/canvas/gui/Style.nas b/Nasal/canvas/gui/Style.nas
new file mode 100644
index 000000000..244177c26
--- /dev/null
+++ b/Nasal/canvas/gui/Style.nas
@@ -0,0 +1,50 @@
+gui.Style = {
+  new: func(name)
+  {
+    var root_node = props.globals.getNode("/sim/gui/canvas", 1)
+                                 .addChild("style");
+    var path = getprop("/sim/fg-root") ~ "/gui/styles/" ~ name;
+
+    var m = {
+      parents: [gui.Style],
+      _path: path,
+      _node: io.read_properties(path ~ "/style.xml", root_node),
+      _colors: {}
+    };
+
+    # parse theme colors
+    var comp_names = ["red", "green", "blue", "alpha"];
+    var colors = m._node.getChild("colors");
+    if( colors != nil )
+    {
+      foreach(var color; colors.getChildren())
+      {
+        var str = "rgba(";
+        for(var i = 0; i < size(comp_names); i += 1)
+        {
+          if( i > 0 )
+            str ~= ",";
+          var val = color.getValue(comp_names[i]);
+          if( val == nil )
+            val = 1;
+          if( i < 3 )
+            str ~= int(val * 255 + 0.5);
+          else
+            str ~= int(val * 100) / 100;
+        }
+        m._colors[ color.getName() ] = str ~ ")";
+      }
+    }
+
+    m._dir_decoration =
+      m._path ~ "/" ~ (m._node.getValue("folders/decoration") or "decoration");
+    m._dir_widgets =
+      m._path ~ "/" ~ (m._node.getValue("folders/widgets") or "widgets");
+
+    return m;
+  },
+  getColor: func(name, def = "#00ffff")
+  {
+    return me._colors[name] or def;
+  }
+};
diff --git a/Nasal/canvas/gui/Widget.nas b/Nasal/canvas/gui/Widget.nas
new file mode 100644
index 000000000..a6b37e707
--- /dev/null
+++ b/Nasal/canvas/gui/Widget.nas
@@ -0,0 +1,83 @@
+gui.Widget = {
+# enum FocusPolicy:
+  NoFocus: 0,
+  TabFocus: 1,
+  ClickFocus: 2,
+  StrongFocus: 1 + 2,
+
+  #
+  new: func(derived)
+  {
+    return {
+      parents: [derived, gui.Widget],
+      _focused: 0,
+      _focus_policy: gui.Widget.NoFocus,
+      _hover: 0,
+      _root: nil
+    };
+  },
+  # Move the widget to the given position (relative to its parent)
+  move: func(x, y)
+  {
+    me._root.setTranslation(x, y);
+    return me;
+  },
+  #
+  setFocus: func
+  {
+    if( me._focused )
+      return me;
+
+    if( me._window._focused_widget != nil )
+      me._window._focused_widget.clearFocus();
+
+    me._focused = 1;
+    me._window._focused_widget = me;
+
+    me.onFocusIn();
+    me._onStateChange();
+
+    return me;
+  },
+  #
+  clearFocus: func
+  {
+    if( !me._focused )
+      return me;
+
+    me._focused = 0;
+    me._window._focused_widget = nil;
+
+    me.onFocusOut();
+    me._onStateChange();
+
+    return me;
+  },
+  onFocusIn: func {},
+  onFocusOut: func {},
+  onMouseEnter: func {},
+  onMouseLeave: func {},
+# protected:
+  _onStateChange: func {},
+  _setRoot: func(el)
+  {
+    me._root = el;
+    el.addEventListener("mouseenter", func {
+      me._hover = 1;
+      me.onMouseEnter();
+      me._onStateChange();
+    });
+    el.addEventListener("mousedown", func {
+      if( bits.test(me._focus_policy, me.ClickFocus / 2) )
+      {
+        me.setFocus();
+        me._window.setFocus();
+      }
+    });
+    el.addEventListener("mouseleave", func {
+      me._hover = 0;
+      me.onMouseLeave();
+      me._onStateChange();
+    });
+  }
+};
diff --git a/Nasal/canvas/gui/styles/DefaultStyle.nas b/Nasal/canvas/gui/styles/DefaultStyle.nas
new file mode 100644
index 000000000..01799d147
--- /dev/null
+++ b/Nasal/canvas/gui/styles/DefaultStyle.nas
@@ -0,0 +1,87 @@
+var DefaultStyle = {
+  new: func(name)
+  {
+    return { parents: [gui.Style.new(name), DefaultStyle] };
+  },
+  createWidget: func(parent, type, cfg)
+  {
+    var factory = me.widgets[type];
+    if( factory == nil )
+    {
+      debug.warn("DefaultStyle: unknown widget type (" ~ type ~ ")");
+      return nil;
+    }
+
+    return factory.new(parent, me, cfg);
+  },
+  widgets: {}
+};
+
+# A button
+DefaultStyle.widgets.button = {
+  padding: [6, 8, 6, 8],
+  new: func(parent, style, cfg)
+  {
+    var button = {
+      parents: [DefaultStyle.widgets.button],
+      element: parent.createChild("group", "button"),
+      size: cfg.get("size", [26, 26]),
+      _style: style
+    };
+
+    button._bg =
+      button.element.rect( 3,
+                           3,
+                           button.size[0] - 6,
+                           button.size[1] - 6,
+                           {"border-radius": 5} );
+    button._border =
+      button.element.createChild("image", "button")
+                    .set("slice", "10 12") #"7")
+                    .setSize(button.size);
+    button._label =
+      button.element.createChild("text")
+                    .setFont("LiberationFonts/LiberationSans-Regular.ttf")
+                    .set("character-size", 14)
+                    .set("alignment", "center-baseline");
+    return button;
+  },
+  setText: func(text)
+  {
+    me._label.set("text", text);
+  },
+  update: func(active, focused, hover, backdrop)
+  {
+    var file = me._style._dir_widgets ~ "/";
+    if( backdrop )
+    {
+      file ~= "backdrop-";
+      me._label.set("fill", me._style.getColor("backdrop_fg_color"));
+    }
+    else
+      me._label.set("fill", me._style.getColor("fg_color"));
+    file ~= "button";
+
+    if( active )
+    {
+      file ~= "-active";
+      me._label.setTranslation(me.size[0] / 2 + 1, me.size[1] / 2 + 6);
+    }
+    else
+      me._label.setTranslation(me.size[0] / 2, me.size[1] / 2 + 5);
+
+
+    if( focused and !backdrop )
+      file ~= "-focused";
+
+    if( hover and !active )
+    {
+      file ~= "-hover";
+      me._bg.set("fill", me._style.getColor("button_bg_color_hover"));
+    }
+    else
+      me._bg.set("fill", me._style.getColor("button_bg_color"));
+
+    me._border.set("file", file ~ ".png");
+  }
+};
diff --git a/Nasal/canvas/gui/widgets/Button.nas b/Nasal/canvas/gui/widgets/Button.nas
new file mode 100644
index 000000000..08e3ccb51
--- /dev/null
+++ b/Nasal/canvas/gui/widgets/Button.nas
@@ -0,0 +1,86 @@
+var Config = {
+  new: func(cfg)
+  {
+    var m = {
+      parents: [Config],
+      _cfg: cfg
+    };
+    if( typeof(m._cfg) != "hash" )
+      m._cfg = {};
+
+    return m;
+  },
+  get: func(key, default = nil)
+  {
+    var val = me._cfg[key];
+    if( val != nil )
+      return val;
+
+    return default;
+  }
+};
+
+gui.widgets.Button = {
+  new: func(parent, style, cfg)
+  {
+    var cfg = Config.new(cfg);
+    var m = gui.Widget.new(gui.widgets.Button);
+    m._focus_policy = m.StrongFocus;
+    m._active = 0;
+    m._flat = cfg.get("flat", 0);
+
+    if( style != nil and !m._flat )
+    {
+      m._button = style.createWidget(parent, "button", cfg);
+      m._setRoot(m._button.element);
+    }
+
+    return m;
+  },
+  setText: func(text)
+  {
+    me._button.setText(text);
+    return me;
+  },
+  setActive: func
+  {
+    if( me._active )
+      return me;
+
+    me._active = 1;
+    me._onStateChange();
+    return me;
+  },
+  clearActive: func
+  {
+    if( !me._active )
+      return me;
+
+    me._active = 0;
+    me._onStateChange();
+    return me;
+  },
+  onClick: func {},
+# protected:
+  _onStateChange: func
+  {
+    if( me._button != nil )
+      me._button.update(me._active, me._focused, me._hover, !me._window._focused);
+  },
+  _setRoot: func(el)
+  {
+    el.addEventListener("mousedown", func me.setActive());
+    el.addEventListener("mouseup",   func me.clearActive());
+
+    # Use 'call' to ensure 'me' is not set and can be used in the closure of
+    # custom callbacks. TODO pass 'me' as argument?
+    el.addEventListener("click", func call(me.onClick));
+
+    el.addEventListener("mouseleave",func me.clearActive());
+    el.addEventListener("drag", func(e) e.stopPropagation());
+
+    call(gui.Widget._setRoot, [el], me);
+  }
+};
+
+return;
diff --git a/gui/styles/AmbianceClassic/close_focused_normal.png b/gui/styles/AmbianceClassic/decoration/close_focused_normal.png
similarity index 100%
rename from gui/styles/AmbianceClassic/close_focused_normal.png
rename to gui/styles/AmbianceClassic/decoration/close_focused_normal.png
diff --git a/gui/styles/AmbianceClassic/close_focused_prelight.png b/gui/styles/AmbianceClassic/decoration/close_focused_prelight.png
similarity index 100%
rename from gui/styles/AmbianceClassic/close_focused_prelight.png
rename to gui/styles/AmbianceClassic/decoration/close_focused_prelight.png
diff --git a/gui/styles/AmbianceClassic/close_focused_pressed.png b/gui/styles/AmbianceClassic/decoration/close_focused_pressed.png
similarity index 100%
rename from gui/styles/AmbianceClassic/close_focused_pressed.png
rename to gui/styles/AmbianceClassic/decoration/close_focused_pressed.png
diff --git a/gui/styles/AmbianceClassic/decoration/close_unfocused.png b/gui/styles/AmbianceClassic/decoration/close_unfocused.png
new file mode 100644
index 000000000..03eb5a695
Binary files /dev/null and b/gui/styles/AmbianceClassic/decoration/close_unfocused.png differ
diff --git a/gui/styles/AmbianceClassic/decoration/close_unfocused_prelight.png b/gui/styles/AmbianceClassic/decoration/close_unfocused_prelight.png
new file mode 100644
index 000000000..6e5ec3d9e
Binary files /dev/null and b/gui/styles/AmbianceClassic/decoration/close_unfocused_prelight.png differ
diff --git a/gui/styles/AmbianceClassic/style.xml b/gui/styles/AmbianceClassic/style.xml
new file mode 100644
index 000000000..6ec09432d
--- /dev/null
+++ b/gui/styles/AmbianceClassic/style.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+
+<PropertyList>
+<!--
+	<folders>
+		<decoration type="string">decoration</decoration>
+		<widgets    type="string">widgets</widgets>
+	</folders>
+-->
+	<colors>
+
+		<!-- Window decoration colors -->
+		<title>
+			<red   type="float">0.275</red>
+			<green type="float">0.271</green>
+			<blue  type="float">0.251</blue>
+		</title>
+		<title-unfocused>
+			<red   type="float">0.235</red>
+			<green type="float">0.231</green>
+			<blue  type="float">0.216</blue>
+		</title-unfocused>
+		<title-text>
+			<red   type="float">0.875</red>
+			<green type="float">0.859</green>
+			<blue  type="float">0.824</blue>
+		</title-text>
+		<title-text-unfocused>
+			<red   type="float">0.502</red>
+			<green type="float">0.490</green>
+			<blue  type="float">0.471</blue>
+		</title-text-unfocused>
+		<title-highlight>
+			<red   type="float">0.365</red>
+			<green type="float">0.361</green>
+			<blue  type="float">0.341</blue>
+		</title-highlight>
+		<title-highlight-unfocused>
+			<red   type="float">0.278</red>
+			<green type="float">0.275</green>
+			<blue  type="float">0.259</blue>
+		</title-highlight-unfocused>
+
+		<!-- default colors for all GUI objects -->
+		<bg_color>
+			<red   type="float">0.949</red>
+			<green type="float">0.945</green>
+			<blue  type="float">0.941</blue>
+		</bg_color>
+
+		<fg_color>
+			<red   type="float">0.298</red>
+			<green type="float">0.298</green>
+			<blue  type="float">0.298</blue>
+		</fg_color>
+
+		<text_color>
+			<red   type="float">0.235</red>
+			<green type="float">0.235</green>
+			<blue  type="float">0.235</blue>
+		</text_color>
+
+		<backdrop_fg_color>
+			<red   type="float">0.428</red>
+			<green type="float">0.427</green>
+			<blue  type="float">0.427</blue>
+		</backdrop_fg_color>
+
+		<button_bg_color>
+			<red   type="float">0.949</red>
+			<green type="float">0.945</green>
+			<blue  type="float">0.941</blue>
+		</button_bg_color>
+
+		<button_bg_color_hover>
+			<red   type="float">0.996</red>
+			<green type="float">0.992</green>
+			<blue  type="float">0.988</blue>
+		</button_bg_color_hover>
+
+	</colors>
+
+</PropertyList>
diff --git a/gui/styles/AmbianceClassic/widgets/backdrop-button-active-hover.png b/gui/styles/AmbianceClassic/widgets/backdrop-button-active-hover.png
new file mode 100644
index 000000000..7ff22c80b
Binary files /dev/null and b/gui/styles/AmbianceClassic/widgets/backdrop-button-active-hover.png differ
diff --git a/gui/styles/AmbianceClassic/widgets/backdrop-button-active.png b/gui/styles/AmbianceClassic/widgets/backdrop-button-active.png
new file mode 100644
index 000000000..c71f92074
Binary files /dev/null and b/gui/styles/AmbianceClassic/widgets/backdrop-button-active.png differ
diff --git a/gui/styles/AmbianceClassic/widgets/backdrop-button-hover.png b/gui/styles/AmbianceClassic/widgets/backdrop-button-hover.png
new file mode 100644
index 000000000..7ff22c80b
Binary files /dev/null and b/gui/styles/AmbianceClassic/widgets/backdrop-button-hover.png differ
diff --git a/gui/styles/AmbianceClassic/widgets/backdrop-button.png b/gui/styles/AmbianceClassic/widgets/backdrop-button.png
new file mode 100644
index 000000000..d357ed68c
Binary files /dev/null and b/gui/styles/AmbianceClassic/widgets/backdrop-button.png differ
diff --git a/gui/styles/AmbianceClassic/widgets/button-active-focused.png b/gui/styles/AmbianceClassic/widgets/button-active-focused.png
new file mode 100644
index 000000000..71e24eb5b
Binary files /dev/null and b/gui/styles/AmbianceClassic/widgets/button-active-focused.png differ
diff --git a/gui/styles/AmbianceClassic/widgets/button-active.png b/gui/styles/AmbianceClassic/widgets/button-active.png
new file mode 100644
index 000000000..d2d5a7f4f
Binary files /dev/null and b/gui/styles/AmbianceClassic/widgets/button-active.png differ
diff --git a/gui/styles/AmbianceClassic/widgets/button-focused-hover.png b/gui/styles/AmbianceClassic/widgets/button-focused-hover.png
new file mode 100644
index 000000000..5d6e27690
Binary files /dev/null and b/gui/styles/AmbianceClassic/widgets/button-focused-hover.png differ
diff --git a/gui/styles/AmbianceClassic/widgets/button-focused.png b/gui/styles/AmbianceClassic/widgets/button-focused.png
new file mode 100644
index 000000000..f4e976f37
Binary files /dev/null and b/gui/styles/AmbianceClassic/widgets/button-focused.png differ
diff --git a/gui/styles/AmbianceClassic/widgets/button-hover.png b/gui/styles/AmbianceClassic/widgets/button-hover.png
new file mode 100644
index 000000000..abf23dc55
Binary files /dev/null and b/gui/styles/AmbianceClassic/widgets/button-hover.png differ
diff --git a/gui/styles/AmbianceClassic/widgets/button.png b/gui/styles/AmbianceClassic/widgets/button.png
new file mode 100644
index 000000000..c9090ae76
Binary files /dev/null and b/gui/styles/AmbianceClassic/widgets/button.png differ