From 48f33fe78f58afd43c0756d48c330c3d4591658f Mon Sep 17 00:00:00 2001 From: Thomas Geymayer Date: Tue, 10 Jun 2014 18:46:52 +0200 Subject: [PATCH] canvas.gui: Checkable buttons, layout for ScrollArea and images for Lables. --- Nasal/canvas/gui.nas | 39 +++------------------ Nasal/canvas/gui/Widget.nas | 36 +++++++++++++------ Nasal/canvas/gui/styles/DefaultStyle.nas | 23 ++++++++++--- Nasal/canvas/gui/widgets/Button.nas | 44 ++++++++++++++++-------- Nasal/canvas/gui/widgets/Label.nas | 5 +++ Nasal/canvas/gui/widgets/ScrollArea.nas | 38 +++++++++++++++----- 6 files changed, 112 insertions(+), 73 deletions(-) diff --git a/Nasal/canvas/gui.nas b/Nasal/canvas/gui.nas index f43d8391c..88e09d5bc 100644 --- a/Nasal/canvas/gui.nas +++ b/Nasal/canvas/gui.nas @@ -35,7 +35,7 @@ var WindowButton = { var window_focus = me._windowFocus(); file ~= window_focus ? "_focused" : "_unfocused"; - if( me._active ) + if( me._down ) file ~= "_pressed"; else if( me._hover ) file ~= "_prelight"; @@ -405,7 +405,7 @@ var Window = { var button_close = WindowButton.new(title_bar, "close") .move(x, y); - button_close.onClick = func me.del(); + button_close.listen("clicked", func me.del()); # title me._title = title_bar.createChild("text", "title") @@ -452,32 +452,6 @@ var Dialog = { } }; -var createLayoutTest = func -{ - var dlg = canvas.Window.new([350,250], "dialog") - .set("resize", 1); - dlg.getCanvas(1) - .set("background", style.getColor("bg_color")); - var root = dlg.getCanvas().createGroup(); - - var vbox = VBoxLayout.new(); - dlg.setLayout(vbox); - - var b = gui.widgets.Button.new(root, style, {}).setText("Stretch"); - b.setMaximumSize([9999, 9999]); - vbox.addItem(b, 1); - - var button_box = HBoxLayout.new(); - vbox.addItem(button_box); - - var b1 = gui.widgets.Button.new(root, style, {}).setText("Ok"); - button_box.addItem(b1); - b1.setFocus(); - - var b2 = gui.widgets.Button.new(root, style, {}).setText("Abort"); - button_box.addItem(b2); -} - # Canvas GUI demo # # Shows an icon in the top-right corner which upon click opens a simple window @@ -498,9 +472,6 @@ var initDemo = func }); my_canvas.addEventListener("click", func(e) { - if( e.button == 1 ) - return createLayoutTest(); - var dlg = canvas.Window.new([400,300], "dialog") .set("resize", 1); var my_canvas = dlg.createCanvas() @@ -511,9 +482,8 @@ var initDemo = func my_canvas.addEventListener("drag", func(e) { printf("drag: screen(%.1f|%.1f) client(%.1f|%.1f) local(%.1f|%.1f) delta(%.1f|%.1f)", e.screenX, e.screenY, e.clientX, e.clientY, e.localX, e.localY, e.deltaX, e.deltaY); }); my_canvas.addEventListener("wheel", func(e) { printf("wheel: screen(%.1f|%.1f) client(%.1f|%.1f) %.1f", e.screenX, e.screenY, e.clientX, e.clientY, e.deltaY); }); var root = my_canvas.createGroup(); - root.addEventListener("test", func(e) { printf("test: %s", e.detail.test); }); -root.createChild("image") - .set("src", "http://wiki.flightgear.org/skins/common/images/icons-fg-135.png"); + root.createChild("image") + .set("src", "http://wiki.flightgear.org/skins/common/images/icons-fg-135.png"); var text = root.createChild("text") .setText("This could be used for building an 'Aircraft Help' dialog.\nYou can also #use it to play around with the new Canvas system :). β") @@ -533,7 +503,6 @@ root.createChild("image") .set("fill", "#ff0000") .hide(); var visible_count = 0; - text.addEventListener("click", func root.dispatchEvent(canvas.CustomEvent.new("test", {detail: {"test": "some important data.."}}))); text.addEventListener("mouseover", func text_move.show()); text.addEventListener("mouseout", func text_move.hide()); text.addEventListener("mousemove", func(e) { printf("move: screen(%.1f|%.1f) client(%.1f|%.1f) local(%.1f|%.1f) delta(%.1f|%.1f)", e.screenX, e.screenY, e.clientX, e.clientY, e.localX, e.localY, e.deltaX, e.deltaY); }); diff --git a/Nasal/canvas/gui/Widget.nas b/Nasal/canvas/gui/Widget.nas index b6cbf7434..81051b5da 100644 --- a/Nasal/canvas/gui/Widget.nas +++ b/Nasal/canvas/gui/Widget.nas @@ -22,10 +22,16 @@ gui.Widget = { m.setSizeHint([32, 32]); m.setMaximumSize([m._MAX_SIZE, m._MAX_SIZE]); - m.setSetGeometryFunc(m.setGeometry); + m.setSetGeometryFunc(m._impl.setGeometry); return m; }, + setFixedSize: func(x, y) + { + me.setMinimumSize([x, y]); + me.setSizeHint([x, y]); + me.setMaximumSize([x, y]); + }, # Move the widget to the given position (relative to its parent) move: func(x, y) { @@ -48,11 +54,11 @@ gui.Widget = { }, # Set geometry of widget (usually used by layouting system) # - # @param geom [, , , ] + # @param geom [, , , ] setGeometry: func(geom) { me.move(geom[0], geom[1]); - me.setSize(geom[2] - geom[0], geom[3] - geom[1]); + me.setSize(geom[2], geom[3]); me._onStateChange(); return me; }, @@ -69,7 +75,7 @@ gui.Widget = { me._focused = 1; canvas._focused_widget = me; - me.onFocusIn(); + me._trigger("focus-in"); me._onStateChange(); return me; @@ -83,15 +89,16 @@ gui.Widget = { me._focused = 0; me.getCanvas()._focused_widget = nil; - me.onFocusOut(); + me._trigger("focus-out"); me._onStateChange(); return me; }, - onFocusIn: func {}, - onFocusOut: func {}, - onMouseEnter: func {}, - onMouseLeave: func {}, + listen: func(type, cb) + { + me._view._root.addEventListener("cb." ~ type, cb); + return me; + }, # protected: _MAX_SIZE: 32768, # size for "no size-limit" _onStateChange: func {}, @@ -112,7 +119,7 @@ gui.Widget = { root.addEventListener("mouseenter", func { me._hover = 1; - me.onMouseEnter(); + me._trigger("mouse-enter"); me._onStateChange(); }); root.addEventListener("mousedown", func { @@ -121,10 +128,17 @@ gui.Widget = { }); root.addEventListener("mouseleave", func { me._hover = 0; - me.onMouseLeave(); + me._trigger("mouse-leave"); me._onStateChange(); }); }, + _trigger: func(type, data = nil) + { + me._view._root.dispatchEvent( + canvas.CustomEvent.new("cb." ~ type, {detail: data}) + ); + return me; + }, _windowFocus: func { var canvas = me.getCanvas(); diff --git a/Nasal/canvas/gui/styles/DefaultStyle.nas b/Nasal/canvas/gui/styles/DefaultStyle.nas index 04c31ce92..0e20a714e 100644 --- a/Nasal/canvas/gui/styles/DefaultStyle.nas +++ b/Nasal/canvas/gui/styles/DefaultStyle.nas @@ -70,7 +70,7 @@ DefaultStyle.widgets.button = { me._label.set("fill", me._style.getColor("fg_color")); file ~= "button"; - if( model._active ) + if( model._down ) { file ~= "-active"; me._label.setTranslation(w / 2 + 1, h / 2 + 6); @@ -82,7 +82,7 @@ DefaultStyle.widgets.button = { if( model._focused and !backdrop ) file ~= "-focused"; - if( model._hover and !model._active ) + if( model._hover and !model._down ) { file ~= "-hover"; me._bg.set("fill", me._style.getColor("button_bg_color_hover")); @@ -104,6 +104,9 @@ DefaultStyle.widgets.label = { { if( me['_bg'] != nil ) me._bg.reset().rect(0, 0, w, h); + if( me['_img'] != nil ) + me._img.set("size[0]", w) + .set("size[1]", h); if( me['_text'] != nil ) # TODO different alignment me._text.setTranslation(2, h / 2); @@ -115,11 +118,23 @@ DefaultStyle.widgets.label = { return me._deleteElement('text'); me._createElement("text", "text") - .set("text", text); + .set("text", text) + .set("fill", "black"); # TODO get real font metrics model.setMinimumSize([size(text) * 5 + 4, 14]); - model.setSizeHint([size(text) * 8 + 8, 24]); + model.setSizeHint([size(text) * 5 + 14, 24]); + + return me; + }, + setImage: func(model, img) + { + if( img == nil or size(img) == 0 ) + return me._deleteElement('img'); + + me._createElement("img", "image") + .set("src", img) + .set("preserveAspectRatio", "xMidYMid slice"); return me; }, diff --git a/Nasal/canvas/gui/widgets/Button.nas b/Nasal/canvas/gui/widgets/Button.nas index bbcc8a337..5da879a95 100644 --- a/Nasal/canvas/gui/widgets/Button.nas +++ b/Nasal/canvas/gui/widgets/Button.nas @@ -4,7 +4,8 @@ gui.widgets.Button = { var cfg = Config.new(cfg); var m = gui.Widget.new(gui.widgets.Button); m._focus_policy = m.StrongFocus; - m._active = 0; + m._down = 0; + m._checkable = 0; m._flat = cfg.get("flat", 0); if( style != nil and !m._flat ) @@ -17,25 +18,41 @@ gui.widgets.Button = { me._view.setText(me, text); return me; }, - setActive: func + setCheckable: func(checkable) { - if( me._active ) + me._checkable = checkable; + return me; + }, + setChecked: func(checked = 1) + { + if( !me._checkable or me._down == checked ) return me; - me._active = 1; + me._trigger("clicked", {checked: checked}); + me._trigger("toggled", {checked: checked}); + + me._down = checked; me._onStateChange(); return me; }, - clearActive: func + setDown: func(down = 1) { - if( !me._active ) + if( me._checkable or me._down == down ) return me; - me._active = 0; + me._down = down; me._onStateChange(); return me; }, - onClick: func {}, + toggle: func + { + if( !me._checkable ) + me._trigger("clicked", {checked: 0}); + else + me.setChecked(!me._down); + + return me; + }, # protected: _onStateChange: func { @@ -45,14 +62,11 @@ gui.widgets.Button = { _setView: func(view) { var el = view._root; - el.addEventListener("mousedown", func me.setActive()); - el.addEventListener("mouseup", func me.clearActive()); + el.addEventListener("mousedown", func me.setDown(1)); + el.addEventListener("mouseup", func me.setDown(0)); + el.addEventListener("click", func me.toggle()); - # 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("mouseleave",func me.setDown(0)); el.addEventListener("drag", func(e) e.stopPropagation()); call(gui.Widget._setView, [view], me); diff --git a/Nasal/canvas/gui/widgets/Label.nas b/Nasal/canvas/gui/widgets/Label.nas index c5c4798c9..47654d158 100644 --- a/Nasal/canvas/gui/widgets/Label.nas +++ b/Nasal/canvas/gui/widgets/Label.nas @@ -13,6 +13,11 @@ gui.widgets.Label = { me._view.setText(me, text); return me; }, + setImage: func(img) + { + me._view.setImage(me, img); + return me; + }, setBackground: func(bg) { me._view.setBackground(me, bg); diff --git a/Nasal/canvas/gui/widgets/ScrollArea.nas b/Nasal/canvas/gui/widgets/ScrollArea.nas index 0c889bcf1..cc434f6ba 100644 --- a/Nasal/canvas/gui/widgets/ScrollArea.nas +++ b/Nasal/canvas/gui/widgets/ScrollArea.nas @@ -7,6 +7,7 @@ gui.widgets.ScrollArea = { m._scroll_pos = [0,0]; m._max_scroll = [0, 0]; m._content_size = [0, 0]; + m._layout = nil; if( style != nil ) m._setView( style.createWidget(parent, "scroll-area", cfg) ); @@ -15,6 +16,12 @@ gui.widgets.ScrollArea = { return m; }, + setLayout: func(l) + { + me._layout = l; + l.setParent(me); + return me.update(); + }, getContent: func() { return me._view.content; @@ -143,12 +150,30 @@ gui.widgets.ScrollArea = { _updateBB: func() { # TODO only update on content resize - var bb = me.getContent().getTightBoundingBox(); + if( me._layout == nil ) + { + var bb = me.getContent().getTightBoundingBox(); - if( bb[2] < bb[0] or bb[3] < bb[1] ) - return nil; - var w = bb[2] - bb[0]; - var h = bb[3] - bb[1]; + if( bb[2] < bb[0] or bb[3] < bb[1] ) + return nil; + var w = bb[2] - bb[0]; + var h = bb[3] - bb[1]; + + var cur_offset = me.getContent().getTranslation(); + me._content_offset = [cur_offset[0] - bb[0], cur_offset[1] - bb[1]]; + } + else + { + var min_size = me._layout.minimumSize(); + var max_size = me._layout.maximumSize(); + var size_hint = me._layout.sizeHint(); + var w = math.min(max_size[0], math.max(math.max(min_size[0], size_hint[0]), me._size[0])); + var h = math.min(max_size[1], math.max(math.max(min_size[1], size_hint[1]), me._size[1])); + me._layout.setGeometry([0, 0, w, h]); + + # Layout always has the origin at (0, 0) + me._content_offset = [0, 0]; + } if( w > me._size[0] ) { @@ -168,8 +193,5 @@ gui.widgets.ScrollArea = { me._content_size[0] = w; me._content_size[1] = h; - - var cur_offset = me.getContent().getTranslation(); - me._content_offset = [cur_offset[0] - bb[0], cur_offset[1] - bb[1]]; } };