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.dd;
021 
022 import js.ArrayLike;
023 import js.Id;
024 import js.Initializer;
025 import js.Js;
026 import js.ObjectLike;
027 import js.Vars;
028 import jsx.Configurable;
029 import jsx.client.Browser;
030 import jsx.core.ObjectLikes;
031 import jsx.core.Variables;
032 import jsx.dom.Elements;
033 import jsx.dom.Styles;
034 import jsx.ui.Component;
035 import jsx.ui.Widget;
036 import jsx.ui.dd.event.DragMove;
037 import jsx.ui.dd.event.DragStart;
038 import jsx.ui.dd.event.DragStop;
039 import jsx.ui.dd.event.OnDragMove;
040 import jsx.ui.event.Position;
041 import jsx.ui.event.Style;
042 import jsx.ui.fx.event.Animation;
043 
044 /**
045  * <p>A base class for widgets that can be resized by touch handles.</p>
046  * <p>A {@link Resizable} widget is a draggable wrapper that makes its underlying HTML 
047  * element resizable in accordance with a {@link Mouse} wrapper widget to which it 
048  * listens mouse events.</p>
049  * <p>A {@link Resizable} widget is {@link Configurable} and is also an event source 
050  * which fires {@link jsx.ui.Widget.Event} events.</p>
051  * 
052  * @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>
053  */
054 public class Resizable extends Draggable implements OnDragMove
055 {
056     private final static Id<Integer> DIR = new Id<Integer>();
057 
058     /**
059      * <p>This constant is a legal value for the second argument of the constructor 
060      * {@link #Resizable(Component, int)} and the method {@link Resizer#getResizer(Component, int)}}, 
061      * and the only argument of the method {@link Resizer#mask(int)} meaning the 
062      * resizer of the north border of a component.</p>
063      * @since 1.0
064      */
065     public final static int N  = 0;
066     /**
067      * <p>This constant is a legal value for the second argument of the constructor 
068      * {@link #Resizable(Component, int)} and the method {@link Resizer#getResizer(Component, int)}}, 
069      * and the only argument of the method {@link Resizer#mask(int)} meaning the 
070      * resizer of the south border of a component.</p>
071      * @since 1.0
072      */
073     public final static int S  = 1;
074     /**
075      * <p>This constant is a legal value for the second argument of the constructor 
076      * {@link #Resizable(Component, int)} and the method {@link Resizer#getResizer(Component, int)}}, 
077      * and the only argument of the method {@link Resizer#mask(int)} meaning the 
078      * resizer of the east border of a component.</p>
079      * @since 1.0
080      */
081     public final static int E  = 2;
082     /**
083      * <p>This constant is a legal value for the second argument of the constructor 
084      * {@link #Resizable(Component, int)} and the method {@link Resizer#getResizer(Component, int)}}, 
085      * and the only argument of the method {@link Resizer#mask(int)} meaning the 
086      * resizer of the west border of a component.</p>
087      * @since 1.0
088      */
089     public final static int W  = 3;
090     /**
091      * <p>This constant is a legal value for the second argument of the constructor 
092      * {@link #Resizable(Component, int)} and the method {@link Resizer#getResizer(Component, int)}}, 
093      * and the only argument of the method {@link Resizer#mask(int)} meaning the 
094      * resizer of the south-east corner of a component.</p>
095      * @since 1.0
096      */
097     public final static int SE = 4;
098     /**
099      * <p>This constant is a legal value for the second argument of the constructor 
100      * {@link #Resizable(Component, int)} and the method {@link Resizer#getResizer(Component, int)}}, 
101      * and the only argument of the method {@link Resizer#mask(int)} meaning the 
102      * resizer of the south-west corner of a component.</p>
103      * @since 1.0
104      */
105     public final static int SW = 5;
106     /**
107      * <p>This constant is a legal value for the second argument of the constructor 
108      * {@link #Resizable(Component, int)} and the method {@link Resizer#getResizer(Component, int)}}, 
109      * and the only argument of the method {@link Resizer#mask(int)} meaning the 
110      * resizer of the north-east corner of a component.</p>
111      * @since 1.0
112      */
113     public final static int NE = 6;
114     /**
115      * <p>This constant is a legal value for the second argument of the constructor 
116      * {@link #Resizable(Component, int)} and the method {@link Resizer#getResizer(Component, int)}}, 
117      * and the only argument of the method {@link Resizer#mask(int)} meaning the 
118      * resizer of the north-west corner of a component.</p>
119      * @since 1.0
120      */
121     public final static int NW = 7;
122 
123     /**
124      * <p>A typical constructor that constructs a wrapper widget of this type and forces 
125      * constructors of subclasses to pass initializing data.</p>
126      * <p>This constructor invokes the typical constructor of the superclass passing 
127      * the specified initializing object as the argument. The typical constructor of the 
128      * superclass attaches the resizable widget being created to the mouse widget if 
129      * specified.</p>
130      * @param ini The initializing object.
131      * @since 1.0
132      */
133     protected Resizable(ObjectLike ini) {
134         super(ini);
135         Integer dir = ini(this).var(DIR);
136         dir = Variables.undefined(dir) ? SE : dir & 7;
137         ini(this).var(DIR, dir);
138     }
139 
140     /**
141      * <p>Constructs a draggable widget that wraps a specified component and makes it 
142      * resizable in the specified direction by the configured touch handle to which 
143      * the newly constructed widget are listening mouse events.</p>
144      * <p>This constructor invokes the typical constructor of this class passing 
145      * an new initializing object as the argument and setting the configurable property 
146      * {@link Widget#COMPONENT} to the argument component, {@link #DIR} to the 
147      * specified resizing direction.</p>
148      * @param e The component to be wrapped by the wrapper widget being created.
149      * @param dir Specifies in which direction this resizable widget is allowed to 
150      * resize its underlying component. Possible values for this argument are:
151      * <ul>
152      * <li>{@link #N}: Only the north border is allowed to move for resizing.</li>
153      * <li>{@link #S}: Only the south border is allowed to move for resizing.</li>
154      * <li>{@link #E}: Only the east border is allowed to move for resizing.</li>
155      * <li>{@link #W}: Only the west border is allowed to move for resizing.</li>
156      * <li>{@link #SE}: Only the south-east corner is allowed to move for resizing.</li>
157      * <li>{@link #SW}: Only the south-west corner is allowed to move for resizing.</li>
158      * <li>{@link #NE}: Only the north-east corner is allowed to move for resizing.</li>
159      * <li>{@link #NW}: Only the north-west corner is allowed to move for resizing.</li>
160      * </ul>
161      * @since 1.0
162      */
163     public Resizable(Component e, int dir) {
164         this(new Initializer().set(COMPONENT, e).set(DIR, dir).var());
165     }
166 
167     private final static Id<Double> WI = new Id<Double>();
168     private final static Id<Double> HI = new Id<Double>();
169 
170     private static final void press(Widget w) {
171         Component e = w.unwrap();
172         ObjectLike ini = ini(e);
173         if (Browser.isIE) {
174             ini.var(WI, (double)Component.offsetWidth (e));
175             ini.var(HI, (double)Component.offsetHeight(e));
176         } else {
177             ini.var(WI, Component.contentWidth (e));
178             ini.var(HI, Component.contentHeight(e));
179         }
180     }
181 
182     /**
183      * <p>Performs an action on the dispatched event.</p>
184      * <p>This method calls the method {@link Draggable#press(Draggable, DragStart)} to 
185      * prepare for resizing and then enters the dragging mode if it is not started.</p>
186      * @param evt The event dispatched to this listener.
187      * @since 1.0
188      */
189     @Override
190     public void onEvent(DragStart evt) {
191         ObjectLike ini = ini(this);
192         if (Js.not(ini.var(START))) {
193             press(this, evt);
194             press(this);
195             if (delegable(this)) {
196                 press(ini.var(GHOST));
197             }
198             ini.var(START, true);
199         }
200     }
201 
202     private final static ArrayLike<Integer> XCOEFFS = new Vars<Integer>()
203         .add( 0).add( 0).add( 1).add(-1)
204         .add( 1).add(-1).add( 1).add(-1).var();
205 
206     private final static ArrayLike<Integer> YCOEFFS = new Vars<Integer>()
207         .add(-1).add( 1).add( 0).add( 0)
208         .add( 1).add( 1).add(-1).add(-1).var();
209 
210     /**
211      * <p>Performs an action on the dispatched event.</p>
212      * <p>This method resizes the resizable widget or its ghost component accordingly 
213      * and fires a default {@link Style} event from underlying component to notify 
214      * a necessary layout.</p>
215      * @param evt The event dispatched to this listener.
216      * @since 1.0
217      */
218     @Override
219     public void onEvent(DragMove evt) {
220         ObjectLike ini = ini(this);
221         if (Js.be(ini.var(START))) {
222             Component e = delegable(this) ? ini.var(GHOST) : unwrap();
223             Js.apply(
224                     Elements.style(Component.getHTMLElement(e)),
225                     resize(ini.var(DIR), e, evt)
226             );
227             e.exec(new Style());
228         }
229     }
230 
231     /**
232      * <p>Performs an action on the dispatched event.</p>
233      * <p>This method ends resizing if it is in a dragging mode and resizes the 
234      * underlying component by applying a style object created with the cached position 
235      * data and the return result of a call to the method {@link #resize(double, double, int)}. 
236      * It executes an {@link Animation} event, with the style object that has just been 
237      * created, or a default {@link Style} event from the underlying component when 
238      * execution of an animation event fails.</p>
239      * @param evt The event dispatched to this listener.
240      * @since 1.0
241      */
242     @Override
243     public void onEvent(DragStop evt) {
244         ObjectLike ini = ini(this);
245         if (Js.be(ini.var(START))) {
246             ObjectLikes.delete(ini, START);
247             Component e = unwrap();
248             if (!Component.fixed(e)) {
249                 ObjectLike p = resize(ini.var(DIR), e, evt);
250                 int anim = 0;
251                 if (delegable(this)) {
252                     anim = e.exec(new Animation(p));
253                 }
254                 if (Js.not(anim)) {
255                     Js.apply(
256                             Elements.style(Component.getHTMLElement(e)),
257                             p
258                     );
259                     e.exec(new Style());
260                 }
261             }
262             if (delegable(this)) {
263                 Component.detach(ini.var(GHOST));
264             }
265         }
266     }
267 
268     private static final ObjectLike resize(int dir, Component e, Position<?> evt) {
269         ObjectLike ini = ini(e);
270         double x = abs(ini(evt).var(Position.X));
271         double y = abs(ini(evt).var(Position.Y));
272         double dx = x - ini.var(Position.X).doubleValue();
273         double dy = y - ini.var(Position.Y).doubleValue();
274         int cx = XCOEFFS.get(dir);
275         int cy = YCOEFFS.get(dir);
276         double w = ini.var(WI) + cx * dx;
277         double h = ini.var(HI) + cy * dy;
278         ObjectLike p = new Initializer().var();
279         if (true) {
280             if (cx < 0 && cy < 0) {
281                 Js.apply(
282                         p,
283                         move(
284                                 ini.var(X) + dx,
285                                 ini.var(Y) + dy,
286                                 0
287                         )
288                 );
289             } else if (cx < 0) {
290                 Js.apply(
291                         p,
292                         move(
293                                 ini.var(X) + dx,
294                                 0,
295                                 LIMIT_Y
296                         )
297                 );
298             } else if (cy < 0) {
299                 Js.apply(
300                         p,
301                         move(
302                                 0,
303                                 ini.var(Y) + dy,
304                                 LIMIT_X
305                         )
306                 );
307             }
308             if (Js.be(cx) && Js.be(cy)) {
309                 Js.apply(
310                         p,
311                         resize(w, h, 0)
312                 );
313             } else if (Js.be(cx)) {
314                 Js.apply(
315                         p,
316                         resize(w, 0, LIMIT_Y)
317                 );
318             } else if (Js.be(cy)) {
319                 Js.apply(
320                         p,
321                         resize(0, h, LIMIT_X)
322                 );
323             }
324         }
325         return p;
326     }
327 
328     /**
329      * <p>Creates and returns a style object of resizing data with the specified 
330      * dimensions and limitation.</p>
331      * <p>A subclass may also call this method to meet its particular needs.</p>
332      * @param w The X dimension.
333      * @param h The Y dimension.
334      * @param limit Limits a dimension in an axis direction. See {@link Draggable#LIMIT} 
335      * for possible values.
336      * @return The created style object.
337      * @since 1.0
338      */
339     protected static final ObjectLike resize(double w, double h, int limit) {
340         ObjectLike p = new Initializer().var();
341         if (Js.not(limit & LIMIT_X)) {
342             Styles.width (p, Styles.px(w));
343         }
344         if (Js.not(limit & LIMIT_Y)) {
345             Styles.height(p, Styles.px(h));
346         }
347         return p;
348     }
349 }