6 # Window/dialog stacking order
14 var gui_dir = getprop("/sim/fg-root") ~ "/Nasal/canvas/gui/";
15 var loadGUIFile = func(file) io.load_nasal(gui_dir ~ file, "canvas");
16 var loadWidget = func(name) loadGUIFile("widgets/" ~ name ~ ".nas");
17 var loadDialog = func(name) loadGUIFile("dialogs/" ~ name ~ ".nas");
19 loadGUIFile("Config.nas");
20 loadGUIFile("Style.nas");
21 loadGUIFile("Widget.nas");
22 loadGUIFile("styles/DefaultStyle.nas");
24 loadWidget("CheckBox");
26 loadWidget("LineEdit");
27 loadWidget("ScrollArea");
28 loadDialog("InputDialog");
29 loadDialog("MessageBox");
31 var style = DefaultStyle.new("AmbianceClassic", "Humanity");
33 new: func(parent, name)
36 parents: [WindowButton, gui.widgets.Button.new(parent, nil, {"flat": 1})],
39 m._focus_policy = m.NoFocus;
40 m._setView({_root: parent.createChild("image", "WindowButton-" ~ name)});
46 var file = style._dir_decoration ~ "/" ~ me._name;
47 var window_focus = me._windowFocus();
48 file ~= window_focus ? "_focused" : "_unfocused";
54 else if( window_focus )
57 me._view._root.set("src", file ~ ".png");
64 # @param size ([width, height])
65 new: func(size, type = nil, id = nil)
67 var ghost = _newWindowGhost(id);
69 parents: [Window, PropertyElement, ghost],
71 _node: props.wrapNode(ghost._node_ghost),
76 m.setInt("content-size[0]", size[0]);
77 m.setInt("content-size[1]", size[1]);
79 # TODO better default position
83 # arg = [child, listener_node, mode, is_child_event]
84 setlistener(m._node, func m._propCallback(arg[0], arg[2]), 0, 2);
95 if( me["_canvas"] != nil )
97 var placements = me._canvas._node.getChildren("placement");
98 # Do not remove canvas if other placements exist
99 if( size(placements) > 1 )
100 foreach(var p; placements)
102 if( p.getValue("type") == "window"
103 and p.getValue("id") == me.get("id") )
114 setTitle: func(title)
116 return me.set("title", title);
118 # Create the canvas to be used for this Window
120 # @return The new canvas
124 me.get("content-size[0]"),
125 me.get("content-size[1]")
129 size: [size[0], size[1]],
136 # Standard alpha blending
137 "blend-source-rgb": "src-alpha",
138 "blend-destination-rgb": "one-minus-src-alpha",
140 # Just keep current alpha (TODO allow using rgb textures instead of rgba?)
141 "blend-source-alpha": "zero",
142 "blend-destination-alpha": "one"
145 me._canvas._focused_widget = nil;
146 me._canvas.data("focused", me._focused);
147 me._canvas.addEventListener("mousedown", func me.raise());
151 # Set an existing canvas to be used for this Window
152 setCanvas: func(canvas_)
154 if( ghosttype(canvas_) != "Canvas" )
155 return debug.warn("Not a Canvas");
157 canvas_.addPlacement({type: "window", "id": me.get("id")});
158 me['_canvas'] = canvas_;
160 canvas_._focused_widget = nil;
161 canvas_.data("focused", me._focused);
163 # prevent resizing if canvas is placed from somewhere else
166 # Get the displayed canvas
167 getCanvas: func(create = 0)
169 if( me['_canvas'] == nil and create )
172 return me['_canvas'];
174 getCanvasDecoration: func()
176 return wrapCanvas(me._getCanvasDecoration());
180 if( me['_canvas'] == nil )
183 me._canvas.update(); # Ensure placement is applied
184 me._ghost.setLayout(l);
193 if( gui.focused_window != nil )
194 gui.focused_window.clearFocus();
199 gui.focused_window = me;
212 gui.focused_window = nil;
222 me.setInt("tf/t[0]", x);
223 me.setInt("tf/t[1]", y);
231 me.set("content-size[0]", w);
232 me.set("content-size[1]", h);
234 if( me.onResize != nil )
245 me.setInt("tf/t[0]", me.get("tf/t[0]", 10) + x);
246 me.setInt("tf/t[1]", me.get("tf/t[1]", 30) + y);
248 # Raise to top of window stack
251 # on writing the z-index the window always is moved to the top of all other
252 # windows with the same z-index.
253 me.setInt("z-index", me.get("z-index", gui.STACK_INDEX["default"]));
259 if( me['_canvas'] == nil )
262 for(var i = 0; i < 2; i += 1)
264 var size = me.get("content-size[" ~ i ~ "]");
265 me._canvas.set("size[" ~ i ~ "]", size);
266 me._canvas.set("view[" ~ i ~ "]", size);
272 var event = canvas.CustomEvent.new("wm.focus-" ~ (me._focused ? "in" : "out"));
274 if( me._getCanvasDecoration() != nil )
276 # Stronger shadow for focused windows
277 me.getCanvasDecoration()
278 .set("image[1]/fill", me._focused ? "#000000" : "rgba(0,0,0,0.5)");
280 var suffix = me._focused ? "" : "-unfocused";
281 me._title_bar_bg.set("fill", style.getColor("title" ~ suffix));
282 me._title.set( "fill", style.getColor("title-text" ~ suffix));
283 me._top_line.set( "stroke", style.getColor("title-highlight" ~ suffix));
285 me.getCanvasDecoration()
286 .data("focused", me._focused)
287 .dispatchEvent(event);
290 if( me.getCanvas() != nil )
292 .data("focused", me._focused)
293 .dispatchEvent(event);
296 _propCallback: func(child, mode)
298 if( !me._node.equals(child.getParent()) )
300 var name = child.getName();
302 # support for CSS like position: absolute; with right and/or bottom margin
303 if( name == "right" )
304 me._handlePositionAbsolute(child, mode, name, 0);
305 else if( name == "bottom" )
306 me._handlePositionAbsolute(child, mode, name, 1);
308 # update decoration on type change
309 else if( name == "type" )
312 settimer(func me._updateDecoration(), 0, 1);
315 else if( name.starts_with("resize-") )
318 me._handleResize(child, name);
320 else if( name == "size" )
323 me._resizeDecoration();
326 _handlePositionAbsolute: func(child, mode, name, index)
334 me._updatePos(index, name);
336 me["_listener_" ~ name] = [
339 "/sim/gui/canvas/size[" ~ index ~ "]",
340 func me._updatePos(index, name)
344 me._node.getNode("content-size[" ~ index ~ "]"),
345 func me._updatePos(index, name)
348 else if( mode == -1 )
349 for(var i = 0; i < 2; i += 1)
350 removelistener(me["_listener_" ~ name][i]);
352 _updatePos: func(index, name)
356 "tf/t[" ~ index ~ "]",
357 getprop("/sim/gui/canvas/size[" ~ index ~ "]")
359 - me.get("content-size[" ~ index ~ "]")
362 _handleResize: func(child, name)
364 var is_status = name == "resize-status";
365 if( !is_status and !me["_resize"] )
368 var min_size = [75, 100];
370 var x = me.get("tf/t[0]");
371 var y = me.get("tf/t[1]");
372 var old_size = [me.get("size[0]"), me.get("size[1]")];
374 var l = x + math.min(me.get("resize-left"), old_size[0] - min_size[0]);
375 var t = y + math.min(me.get("resize-top"), old_size[1] - min_size[1]);
376 var r = x + math.max(me.get("resize-right"), min_size[0]);
377 var b = y + math.max(me.get("resize-bottom"), min_size[1]);
381 me._resize = child.getValue();
383 if( me._resize and gui.region_highlight == nil )
384 gui.region_highlight =
385 getDesktop().createChild("path", "highlight")
386 .set("stroke", "#ffa500")
387 .set("stroke-width", 2)
388 .set("fill", "rgba(255, 165, 0, 0.15)")
389 .set("z-index", 100);
390 else if( !me._resize and gui.region_highlight != nil )
392 gui.region_highlight.hide();
393 me.setPosition(l, t);
396 me.get("content-size[0]") + (r - l) - old_size[0],
397 me.get("content-size[1]") + (b - t) - old_size[1],
399 if( me.onResize != nil )
404 else if( !me["_resize"] )
407 gui.region_highlight.reset()
416 _updateDecoration: func()
418 var border_radius = 9;
419 me.set("decoration-border", "25 1 1");
420 me.set("shadow-inset", int((1 - math.cos(45 * D2R)) * border_radius + 0.5));
421 me.set("shadow-radius", 5);
422 me.setBool("update", 1);
424 var canvas_deco = me.getCanvasDecoration();
425 canvas_deco.addEventListener("mousedown", func me.raise());
426 canvas_deco.set("blend-source-rgb", "src-alpha");
427 canvas_deco.set("blend-destination-rgb", "one-minus-src-alpha");
428 canvas_deco.set("blend-source-alpha", "one");
429 canvas_deco.set("blend-destination-alpha", "one");
431 var group_deco = canvas_deco.getGroup("decoration");
432 var title_bar = group_deco.createChild("group", "title_bar");
433 me._title_bar_bg = title_bar.createChild("path");
434 me._top_line = title_bar.createChild("path", "top-line");
442 var button_close = WindowButton.new(title_bar, "close")
444 button_close.listen("clicked", func me.del());
447 me._title = title_bar.createChild("text", "title")
448 .set("alignment", "left-center")
449 .set("character-size", 14)
450 .set("font", "LiberationFonts/LiberationSans-Bold.ttf")
451 .setTranslation( int(x + 1.5 * w + 0.5),
452 int(y + 0.5 * h + 0.5) );
454 var title = me.get("title", "Canvas Dialog");
455 me._node.getNode("title", 1).alias(me._title._node.getPath() ~ "/text");
456 me.set("title", title);
458 title_bar.addEventListener("drag", func(e) me.move(e.deltaX, e.deltaY));
460 me._resizeDecoration();
463 _resizeDecoration: func()
465 if( me["_title_bar_bg"] == nil )
468 var border_radius = 9;
472 me.get("size[0]"), me.get("size[1]"),
473 {"border-top-radius": border_radius} );
477 .moveTo(border_radius - 2, 2)
478 .lineTo(me.get("size[0]") - border_radius + 2, 2);
482 # Clear focus on click outside any window
483 getDesktop().addEventListener("mousedown", func {
484 if( gui.focused_window != nil )
485 gui.focused_window.clearFocus();
488 # Provide old 'Dialog' for backwards compatiblity (should be removed for 3.0)
490 new: func(size, type = nil, id = nil)
492 debug.warn("'canvas.Dialog' is deprectated! (use canvas.Window instead)");
493 return Window.new(size, type, id);