001 
002 /*
003  *  JScripter Standard 1.0 - To Script In Java
004  *  Copyright (C) 2008-2011  J.J.Liu<jianjunliu@126.com> <http://www.jscripter.org>
005  *  
006  *  This program is free software: you can redistribute it and/or modify
007  *  it under the terms of the GNU Affero General Public License as published by
008  *  the Free Software Foundation, either version 3 of the License, or
009  *  (at your option) any later version.
010  *  
011  *  This program is distributed in the hope that it will be useful,
012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014  *  GNU Affero General Public License for more details.
015  *  
016  *  You should have received a copy of the GNU Affero General Public License
017  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
018  */
019 
020 package jsx.ui.layout;
021 
022 import js.ArrayLike;
023 import js.Id;
024 import js.Initializer;
025 import js.Js;
026 import js.ObjectLike;
027 import js.user.JsHTMLElement;
028 import jsx.client.Browser;
029 import jsx.core.ArrayLikes;
030 import jsx.dom.Elements;
031 import jsx.dom.Styles;
032 import jsx.ui.Component;
033 import jsx.ui.Container;
034 import jsx.ui.Widget;
035 import jsx.ui.Container.Layout;
036 import jsx.ui.event.Style;
037 
038 /**
039  * <p>Lays out children of a container in a horizontal or vertical bar.</p>
040  * <p>Note, A {@link BarLayout} is a {@link FlowLayout} although a {@link FlowLayout} 
041  * is composed of {@link BarLayout} layouts.</p>
042  * 
043  * @author <a href="mailto:jianjunliu@126.com">J.J.Liu (Jianjun Liu)</a> at <a href="http://www.jscripter.org" target="_blank">http://www.jscripter.org</a>
044  */
045 public class BarLayout extends FlowLayout
046 {
047     /**
048      * <p>A global identifier for a configurable property of a {@link BarLayout} object.</p>
049      * <p>The identified configurable property of a {@link BarLayout} object refers to 
050      * a boolean value specifying whether the layout manager align the children of the 
051      * container for which it is doing layout.</p>
052      * @since 1.0
053      */
054     public final static Id<Boolean> ALIGN = new Id<Boolean>();
055     /**
056      * <p>A global identifier for a configurable property of a {@link BarLayout} object.</p>
057      * <p>The identified configurable property of a {@link BarLayout} object refers to 
058      * a boolean value specifying whether the layout manager fit the last children of the 
059      * container for which it is doing layout.</p>
060      * @since 1.0
061      */
062     public final static Id<Boolean> FIT = new Id<Boolean>();
063 
064     /**
065      * <p>The default constructor that constructs a layout manager of this type.</p>
066      * <p>This constructor invokes the default constructor of the superclass.</p>
067      * @since 1.0
068      * @see #BarLayout(ObjectLike)
069      */
070     public BarLayout() {}
071 
072     /**
073      * <p>A typical constructor that constructs a layout manager of this type and forces 
074      * constructors of subclasses to pass initializing data.</p>
075      * <p>This constructor invokes the typical constructor of the superclass passing 
076      * the specified initializing object as the argument.</p>
077      * @param ini The initializing object.
078      * @since 1.0
079      * @see #BarLayout()
080      */
081     public BarLayout(ObjectLike ini) {
082         super(ini);
083     }
084 
085     /**
086      * <p>Layouts a container.</p>
087      * <p>This method layouts children of a container in a horizontal or vertical bar.</p>
088      * @param c The container to layout.
089      * @since 1.0
090      */
091     @Override
092     protected void doLayout(Container c) {
093         layout(c);
094         updateContentWidth (c);
095         updateContentHeight(c);
096         align(c, 0);
097         fit(c);
098         if (Js.be(ini(this).var(ALIGN))) {
099             align(c, 2);
100         } else {
101             boolean v = Js.be(ini(this).var(VERTICAL));
102             int r = ini(this).var(ORIGIN);
103             r = Js.cond(v, r & RIGHTTOP, r & LEFTBOTTOM);
104             if (Js.be(r)) {
105                 align(c, 1);
106             }
107         }
108     }
109 
110     private static final double pad(boolean v, Component c) {
111         return v ? Component.padding(c, Component.PADDINGTOP ) : 
112                    Component.padding(c, Component.PADDINGLEFT);
113     }
114 
115     private static final double margins(boolean v, Component c) {
116         return v ? Component.margin(c, Component.MARGINTOP   ) +
117                    Component.margin(c, Component.MARGINBOTTOM) :
118                    Component.margin(c, Component.MARGINLEFT ) +
119                    Component.margin(c, Component.MARGINRIGHT);
120     }
121 
122     private static final double wi(Component c) {
123         return Component.offsetWidth(c) + margins(false, c);
124     }
125 
126     private static final double hi(Component c) {
127         return Component.offsetHeight(c) + margins(true, c);
128     }
129 
130     private static final void align(Container ct, int d) {
131         Component c = ct.unwrap();
132         Layout u = Container.getLayout(ct);
133         boolean v = Js.be(ini(u).var(VERTICAL));
134         int r = ini(u).var(ORIGIN);
135         double x = Js.cond(v ^ Js.be(d), ini(ct).var(CONTENT_HEIGHT), ini(ct).var(CONTENT_WIDTH));
136         if (Js.not(d)) {
137             r = Js.cond(v, (r & LEFTBOTTOM) / LEFTBOTTOM, (r & RIGHTTOP) / RIGHTTOP);
138             x = pad(v, c) + r * x;
139         }
140         ArrayLike<Widget> children = ini(ct).var(Container.CHILDREN);
141         for (int i = 0, len = ArrayLikes.length(children); i < len; i++) {
142             Component we = children.get(i).unwrap();
143             if (Js.be(we)) {
144                 JsHTMLElement e = Component.getHTMLElement(we);
145                 switch (d) {
146                     case 0:
147                         double w = v ? hi(we) : wi(we);
148                         Elements.applyStyle(
149                                 e,
150                                 Js.apply(
151                                         position(Js.not(v), pad(Js.not(v), c)),
152                                         position(v, x - w * r)
153                                 )
154                         );
155                         x += w * (1 - 2 * r);
156                         break;
157                     case 1:
158                         double h = v ? wi(we) : hi(we);
159                         Elements.applyStyle(
160                                 e,
161                                 position(Js.not(v), x - h)
162                         );  
163                         break;
164                     case 2:
165                         Elements.applyStyle(
166                                 e,
167                                 size(Js.not(v), we, x)
168                         );
169                         we.exec(new Style());
170                 }
171             }
172         }
173     }
174 
175     private static final void fit(Container ct) {
176         Layout u = Container.getLayout(ct);
177         ArrayLike<Widget> children = ini(ct).var(Container.CHILDREN);
178         int len = ArrayLikes.length(children);
179         if (Js.be(ini(u).var(FIT)) && len > 0) {
180             Component c = children.get(len - 1).unwrap();
181             if (Js.be(c)) {
182                 boolean v = Js.be(ini(u).var(VERTICAL));
183                 int r = ini(u).var(ORIGIN);
184                 r = Js.cond(v, (r & LEFTBOTTOM) / LEFTBOTTOM, (r & RIGHTTOP) / RIGHTTOP);
185                 double cw = Js.cond(v, ini(ct).var(CONTENT_HEIGHT), ini(ct).var(CONTENT_WIDTH));
186                 double x = v ? Component.top (c) : Component.left(c);
187                 double w = v ? hi(c) : wi(c);
188                 w = r * (w + x) + (1 - r) * (cw - x);
189                 x = x * (1 - r) + r * pad(v, ct.unwrap());
190                 JsHTMLElement e = Component.getHTMLElement(c);
191                 Elements.applyStyle(
192                         e,
193                         position(v, x)
194                 );
195                 Elements.applyStyle(
196                         e,
197                         size(v, c, w)
198                 );
199                 c.exec(new Style());
200             }
201         }
202     }
203 
204     private static final ObjectLike position(boolean v, double x) {
205         String s = Styles.px(x);
206         ObjectLike p = new Initializer().var();
207         if (v) {
208             Styles.top (p, s);
209         } else {
210             Styles.left(p, s);
211         }
212         return p;
213     }
214 
215     private static final ObjectLike size(boolean v, Component c, double w) {
216         w -= margins(v, c);
217         if (!Browser.isIE && (Component.div(c) || Component.span(c))) {
218             w = v ? Component.contentHeight(c, w) :  Component.contentWidth(c, w);
219         }
220         String s = Styles.px(w);
221         ObjectLike p = new Initializer().var();
222         if (v) {
223             Styles.height(p, s);
224         } else {
225             Styles.width (p, s);
226         }
227         return p;
228     }
229 
230     /**
231      * <p>Layouts a container when notified it has been resized.</p>
232      * <p>This method layouts children of a container in a horizontal or vertical bar.</p>
233      * @param c The container to layout on resizing.
234      * @since 1.0
235      */
236     @Override
237     protected void onResize(Container c) {
238         Layout u = Container.getLayout(c);
239         boolean align = Js.be(ini(u).var(ALIGN));
240         boolean v = Js.be(ini(u).var(VERTICAL));
241         int r = ini(u).var(ORIGIN);
242         boolean vw = Js.be(Js.cond(v, r & LEFTBOTTOM, r & RIGHTTOP  ));
243         boolean vh = Js.be(Js.cond(v, r & RIGHTTOP  , r & LEFTBOTTOM));
244         boolean cw = updateContentWidth (c);
245         boolean ch = updateContentHeight(c);
246         boolean w = Js.cond(v, ch, cw);
247         boolean h = Js.cond(v, cw, ch);
248         if (w && vw) {
249             align(c, 0);
250         }
251         if (h) {
252             if (align) {
253                 align(c, 2);
254             } else if (vh) {
255                 align(c, 1);
256             }
257         }
258         if (w) {
259             fit(c);
260         }
261     }
262 }