~extremepopcorn/dhlib/dhlib_ep

« back to all changes in this revision

Viewing changes to lib/ui/StaticText.mhx

  • Committer: edA-qa mort-ora-y
  • Date: 2010-02-16 05:36:32 UTC
  • Revision ID: eda-qa@disemia.com-20100216053632-60lt7fndfi3fgblw
first

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* <license>
 
2
 * This file is part of the dis-Emi-A HaXe Library. Copyright (c) edA-qa mort-ora-y
 
3
 * For full copyright and license information please refer to doc/license.txt.
 
4
 * </license> 
 
5
 */
 
6
package ui;
 
7
 
 
8
import flash.display.Sprite;
 
9
import flash.events.Event;
 
10
 
 
11
import flash.text.TextField;
 
12
import flash.text.TextFormat;
 
13
import flash.text.TextFormatAlign;
 
14
 
 
15
import mathx.MatPoint;
 
16
 
 
17
import draw.Color;
 
18
 
 
19
/**
 
20
 * This is to try and provide a better interface to static text fields in Flash.
 
21
 * Flash doesn't allow creation of StaticText fields, the ones with which transformations
 
22
 * can be done, and the TextField class sucks.
 
23
 *
 
24
 * For now this is a simple wrapper to the stupid TextField class.
 
25
 *
 
26
 * Supported:
 
27
 * -different fonts
 
28
 * -different colors
 
29
 * -clipping to target box
 
30
 * -alignment
 
31
 * -autosize to height of box (TODO: Add guaranteed fit mode)
 
32
 * -rotation (though an imported font must be used in flash)
 
33
 * 
 
34
 * Not supported:
 
35
 * -proper scaling (only font-size is supported, no arbitrary scaling)
 
36
 * -cannot removed gutter( fixed at 2 in flash, stupid)
 
37
 * -maximum size (flash seems to max out at format.size == 127)
 
38
 * -minimum size (judging from appearance)
 
39
 *
 
40
 * NOTE: The text field will fill up the height of the given space based on the font
 
41
 * height.  For mot texts this may appear to be misaligned ascenders and descenders
 
42
 * are not likely being used, if they were, everything would look more correct.
 
43
 */
 
44
class StaticText extends Sprite, implements Widget
 
45
{
 
46
        var tf : TextField;
 
47
        public var align(default, setTextAlign) : StaticTextAlign;
 
48
        var clipMask : Sprite;
 
49
        var suppressLayout : Bool;
 
50
        var prefLineHeight : Float;
 
51
        
 
52
        var prefNumRowsCols : MatPoint; //for multiline controls
 
53
        
 
54
        //for autoSizing text
 
55
        var minNumRowsCols : MatPoint;
 
56
        var maxNumRowsCols : MatPoint;
 
57
        
 
58
        var scrollY : Scrollbar;
 
59
        var noScrollY: Bool;
 
60
                
 
61
        public var fillColor : Color;
 
62
        public var constrainWidth : Bool;
 
63
        
 
64
        function new( initText : String, ?align : StaticTextAlign, ?clip : Null<Bool> )
 
65
        {
 
66
                super();
 
67
                MixinWidget();
 
68
                
 
69
                suppressLayout =true;
 
70
                prefLineHeight = 1;
 
71
                noScrollY = false;
 
72
                constrainWidth = true;
 
73
                
 
74
                //in general I suspect the defaults for multilines are less than useful...
 
75
                prefNumRowsCols = MatPoint.at( Math.floor(FontManager.cols / 2), Math.floor(FontManager.rows / 3) );
 
76
                                
 
77
                fillColor = null;       //by default not background
 
78
                        
 
79
                //setup Flash native field
 
80
                tf = new TextField();
 
81
                tf.selectable = false;
 
82
                tf.text = initText;
 
83
                tf.textColor = Theme.current.textColor.asInt();
 
84
                addChild( tf );
 
85
                
 
86
                font = Theme.current.textFont;
 
87
                tf.y = -font.getGutter();       //to remove wacky gutter
 
88
                
 
89
                //setup alignment
 
90
                if( align != null )
 
91
                        this.align = align;
 
92
                else
 
93
                        this.align = StaticTextAlign.Center;
 
94
                
 
95
                /**
 
96
                 *NOTE: Somethign about how this was done before caused Flash to always crash.
 
97
                 * I was creating a new object each time, but perhaps it was because I was removing
 
98
                 * the clipMaks without clearing it from the textField?
 
99
                 * I reduced the situation by not recreated clipping masks, but it is still there, using
 
100
                 * them causes the player to crash (during GC'ing it seems, based on timing with
 
101
                 * text in Fortress)
 
102
                 */
 
103
                //setup clipping (is this even needed?, text-test seems to work without)
 
104
                if( clip == null )
 
105
                        clip = false;
 
106
                if( clip )
 
107
                {
 
108
                        clipMask = new Sprite();
 
109
                        addChild( clipMask );
 
110
                        tf.mask = clipMask;
 
111
                }
 
112
                                
 
113
                addEventListener( Event.ADDED, onAdded );
 
114
                tf.addEventListener( Event.SCROLL, onScrolled );
 
115
                tf.addEventListener( Event.CHANGE, onChanged );
 
116
                suppressLayout = false;
 
117
        }
 
118
        
 
119
        /**
 
120
         * Creates a single-line static text control.
 
121
         *
 
122
         * @param initText [in] the initial text to display
 
123
         * @param align [in] the alignment of the text
 
124
         * @param clip [in] to restrict text to the size of the control, if false text flows out of bounding box
 
125
         * @param lineHeight [in] used in calculating preferred sizes to determine how large the text should be
 
126
         *              (NOTE: Use only for preferred size, actual size depends on final box size)
 
127
         */
 
128
        static public function singleLine( initText : String, ?align : StaticTextAlign, ?clip : Null<Bool>, ?lineHeight : Null<Float> ) : StaticText
 
129
        {
 
130
                var st = new StaticText( initText, align, clip );
 
131
                if( lineHeight != null )
 
132
                        st.prefLineHeight = lineHeight;
 
133
                st.noScrollY = true;    //never have a scroll bar for single lines
 
134
                return st;
 
135
        }
 
136
        
 
137
        /**
 
138
         * Creates a text field suitable for displaying large amounts of text,
 
139
         * appropriate sizes and scrolling will be added as needed.
 
140
         */
 
141
        static public function scrollText( initText : String, ?align : StaticTextAlign, ?prefRowsCols : MatPoint ) : StaticText
 
142
        {
 
143
                var st = new StaticText( initText, align );
 
144
                st.tf.multiline = true;
 
145
                st.tf.wordWrap = true;
 
146
                if( prefRowsCols != null )
 
147
                        st.prefNumRowsCols = prefRowsCols.clone();
 
148
                st.autoSize();
 
149
                return st;
 
150
        }
 
151
        
 
152
        //caller must update() since normally associated with some other change
 
153
        public function setPrefRowsCols( prc : MatPoint )
 
154
        {
 
155
                prefNumRowsCols = prc.clone();
 
156
        }
 
157
        
 
158
        static public function autoSizing( initText : String, prefLineHeight : Float, minRowsCols : MatPoint, maxRowsCols : MatPoint, ?align : StaticTextAlign ) : StaticText
 
159
        {
 
160
                var st = new StaticText( initText, align );
 
161
                st.tf.multiline = true;
 
162
                st.tf.wordWrap = true;
 
163
                st.minNumRowsCols = minRowsCols;
 
164
                st.maxNumRowsCols = maxRowsCols;                
 
165
                st.prefLineHeight = prefLineHeight;
 
166
                st.autoSize();
 
167
                return st;
 
168
        }
 
169
 
 
170
        function autoSize()
 
171
        {
 
172
                if( minNumRowsCols != null )
 
173
                {
 
174
                        //this is very hard to precalculate, I'm just not sure how entirely
 
175
                        //thus we'll simply step through the allotted lines and take the one where the text first fits
 
176
                        for( numLines in minNumRowsCols.y...(maxNumRowsCols.y+1) )
 
177
                        {
 
178
                                var factor = (numLines - minNumRowsCols.y) / (maxNumRowsCols.y - minNumRowsCols.y);
 
179
                                var width = Math.round( factor * (maxNumRowsCols.x - minNumRowsCols.x)  + minNumRowsCols.x );
 
180
                                                                
 
181
                                autoSizeTo( width, numLines );
 
182
                                
 
183
                                //if it fits, then we're almost done
 
184
                                if( tf.numLines <= numLines )
 
185
                                {
 
186
                                        //resize to the number lines actually needed (with may have added a blank)
 
187
                                        if( tf.numLines < numLines )
 
188
                                                autoSizeTo( width, tf.numLines );
 
189
                                        break;
 
190
                                }
 
191
                        }
 
192
                }
 
193
                
 
194
                checkScroll( true );
 
195
        }
 
196
        
 
197
        function autoSizeTo( width : Int, lines : Int )
 
198
        {
 
199
                prefNumRowsCols = MatPoint.at( width, lines );
 
200
                //this may leave a small gap at the bottom since font sizes are somehow rounding to int
 
201
                //but since it isn't the line height, this is very difficult to get exactly correct
 
202
                resize( font.byXWidth( width, prefLineHeight ), lines * prefLineHeight );
 
203
        }
 
204
        
 
205
        /**
 
206
         * By default the text in a StaticText field cannot be selected, though
 
207
         * in an Edit field it can be.
 
208
         * This functions turns on the selection for static text fields
 
209
         */
 
210
        /*final*/ public function enableSelectable()
 
211
        {
 
212
                tf.selectable = true;
 
213
        }
 
214
        
 
215
        /*final*/ public function disableScrollY()
 
216
        {
 
217
                noScrollY = true;
 
218
                autoSize();
 
219
        }
 
220
        
 
221
        /*final*/ public function selectAll()
 
222
        {
 
223
                tf.setSelection( 0, tf.text.length );
 
224
        }
 
225
        
 
226
        /*final*/ public function getScrollYEnd()
 
227
        {
 
228
                if( !tf.multiline )
 
229
                        return 0;
 
230
                        
 
231
                return tf.maxScrollV;
 
232
        }
 
233
        
 
234
        /*final*/ public function setScrollY( at : Int )
 
235
        {
 
236
                tf.scrollV = at;
 
237
        }
 
238
        
 
239
        /*final*/ function checkScroll( withUpdate: Bool )
 
240
        {
 
241
                //only multiline has a scrollbar (vertical only for now...)
 
242
                if( !tf.multiline )
 
243
                        return false;
 
244
                        
 
245
                var oldScrollY = scrollY != null;
 
246
                var hasScrollY = tf.maxScrollV > 1 && !noScrollY; //flash starts at 1 for some reason
 
247
                var changed = false;
 
248
                
 
249
                if( hasScrollY )
 
250
                {
 
251
                        if( scrollY == null )
 
252
                        {
 
253
                                scrollY = Scrollbar.vertical();
 
254
                                var me = this;
 
255
                                scrollY.addEventListener( UIEvent.USER_CHANGE,
 
256
                                        function( evt : Event ) { me.setScrollY( Math.floor(me.scrollY.at) ); } );
 
257
                                addChild( scrollY );
 
258
                                changed = true;
 
259
                        }
 
260
                                
 
261
                        scrollY.setRange( 1, tf.maxScrollV, 1 );        
 
262
                        scrollY.at = tf.scrollV;
 
263
                }
 
264
                else if( scrollY != null )
 
265
                {
 
266
                        changed = true;
 
267
                        removeChild( scrollY );
 
268
                        scrollY = null;
 
269
                }
 
270
                
 
271
                if( changed && withUpdate )
 
272
                        update();
 
273
                return changed;
 
274
        }
 
275
        
 
276
        /*protected*/ function onChanged( evt : Event )
 
277
        {
 
278
                autoSize();
 
279
        }
 
280
        
 
281
        /*final*/ function onScrolled( evt : Event )
 
282
        {
 
283
                if( scrollY != null )
 
284
                        scrollY.at = tf.scrollV;
 
285
        }
 
286
        
 
287
        /*final*/ function onAdded( evt : Event )
 
288
        {
 
289
                if( evt.target != this )
 
290
                        return;
 
291
                        
 
292
                update();
 
293
        }
 
294
        
 
295
        /*final*/ public function setTextAlign( a : StaticTextAlign ) : StaticTextAlign
 
296
        {
 
297
                align = a;
 
298
                update( );
 
299
                return align;
 
300
        }
 
301
        
 
302
        public var text( getText, setText ) : String;   //FAKE
 
303
        public function setText( t : String ) : String
 
304
        {
 
305
                //do nothing if its the same (save some cycles)
 
306
                if( tf.text == t )
 
307
                        return t;
 
308
                        
 
309
                tf.text = t;
 
310
                update();
 
311
                autoSize();
 
312
                return t;
 
313
        }
 
314
        
 
315
        public function getText( ) : String
 
316
        {
 
317
                return tf.text;
 
318
        }
 
319
        
 
320
#if flash
 
321
        public var htmlText( getHTMLText, setHTMLText ) : String;//FAKE
 
322
        public function setHTMLText( t : String ) : String
 
323
        {
 
324
                if( tf.htmlText == t )
 
325
                        return t;
 
326
                        
 
327
                tf.htmlText = t;
 
328
                update();
 
329
                autoSize();
 
330
                return t;
 
331
        }
 
332
        
 
333
        public function getHTMLText() : String
 
334
        {
 
335
                return tf.htmlText;
 
336
        }
 
337
#end
 
338
 
 
339
        public var bold( getBold, setBold ) : Bool;     //FAKE
 
340
        
 
341
        public function setBold( b : Bool) : Bool
 
342
        {
 
343
                var fmt = new TextFormat();
 
344
                fmt.bold = b;
 
345
                tf.setTextFormat( fmt );
 
346
                update();
 
347
                return b;
 
348
        }
 
349
        
 
350
        public function getBold( ) : Bool
 
351
        {
 
352
                return tf.getTextFormat().bold;
 
353
        }
 
354
        
 
355
        public var textColor( getTextColor, setTextColor ) : Color;     //FAKE
 
356
        
 
357
        public function setTextColor( c : Color ) : Color
 
358
        {
 
359
                tf.textColor = c.asInt();
 
360
                return c;
 
361
        }
 
362
        
 
363
        public function getTextColor( ) : Color
 
364
        {
 
365
                return Color.int( tf.textColor );
 
366
        }
 
367
 
 
368
        public var font( getFont, setFont ) : FontManager;
 
369
        
 
370
        public function getFont() : FontManager
 
371
        {
 
372
                return font;
 
373
        }
 
374
        
 
375
        public function setFont( font : FontManager ) : FontManager
 
376
        {
 
377
                this.font = font;
 
378
                
 
379
                var fmt = new TextFormat();
 
380
                fmt.font = font.fontName;
 
381
                tf.setTextFormat( fmt );
 
382
                tf.embedFonts = font.isEmbedded;
 
383
                update();
 
384
                
 
385
                return font;
 
386
        }
 
387
        
 
388
        function draw( w :  Float, h : Float )
 
389
        {
 
390
                graphics.clear();
 
391
                //graphics.lineStyle( 0, 0 );
 
392
                if( fillColor != null )
 
393
                        graphics.beginFill( fillColor.asInt() );
 
394
                graphics.drawRect( 0, 0, w, h );
 
395
        }
 
396
        
 
397
        /**
 
398
         * The core of the class which determines how the TextField should
 
399
         * actually be formatted.
 
400
         */
 
401
        function _resize( w : Float, h : Float )
 
402
        {
 
403
                if( tf==null || suppressLayout )
 
404
                        return;
 
405
                        
 
406
                var scrollYX = 0.0;
 
407
                if( scrollY != null )
 
408
                {
 
409
                        scrollYX = font.xWidth * 1.5;   //always 1.5 x wide
 
410
                        scrollY.move( w - scrollYX, 0 );
 
411
                        scrollY.resize( scrollYX, h );
 
412
                }
 
413
                
 
414
                tf.width = w  - scrollYX;               
 
415
                tf.height = h + 2 *font.getGutter();    //accomodate gutters in height (top/bottom)
 
416
                draw( w, h );
 
417
                
 
418
                var fmt = new TextFormat();
 
419
                if( tf.multiline )
 
420
                        fmt.size = font.fontSizeForHeight( h / prefNumRowsCols.y );
 
421
                else
 
422
                        fmt.size = font.fontSizeForHeight( h );
 
423
                        
 
424
                //FLASH: max size for Flash, it will ignore higher sizes, but it messages up our calculations later
 
425
                //TODO: is this perhaps only for device fonts?
 
426
                if( fmt.size > 127 )
 
427
                        fmt.size = 127;                 
 
428
                
 
429
                //patchup for some fonts (TODO: with scrolling what happens?)
 
430
                tf.y = fmt.size * font.padAscent;
 
431
                
 
432
                /* START: Flash nonsense workaround */
 
433
                switch( align )
 
434
                {
 
435
                        case Left:
 
436
                                fmt.align = TextFormatAlign.LEFT;
 
437
                        case Right:
 
438
                                fmt.align = TextFormatAlign.RIGHT;
 
439
                        case Center:
 
440
                                fmt.align = TextFormatAlign.CENTER;
 
441
                }
 
442
                /* END */
 
443
                
 
444
                //without this it doesn't work, *very* odd... (only if font.isEmbedded!)
 
445
                //TODO: perhaps this is why <font> tags don't work though in htmlText
 
446
                fmt.font = font.fontName;       
 
447
                
 
448
                tf.setTextFormat( fmt );
 
449
                
 
450
                //constrain width to available space (NOTE: see Flash note below, this never works correctly!)
 
451
                //var tw = tf.getLineMetrics(0).width + 2 * font.getGutter();
 
452
                var tw = tf.textWidth + 2 * font.getGutter();
 
453
                if( constrainWidth && tw > tf.width && !tf.multiline )
 
454
                {
 
455
                        //NOTE: we are sometimes still 1 size too high :(  ...
 
456
                        var oldSize : Float = fmt.size * 1.0;
 
457
                        fmt.size =  Math.floor( oldSize / (tw / tf.width) - 0.5 );              //... thus - 0.5, seems to work
 
458
                        //trace( oldSize + " / ( " + tw + " / " + tf.width + ") = " + fmt.size );
 
459
                        tf.setTextFormat( fmt );
 
460
                }
 
461
                
 
462
/*              NOTE: This can't be used since Flash is junk when it comes to handling
 
463
                dynamic text and scaling.  There is no way to get the *REAL* width of
 
464
                the text at this point, based on the new fmt.  We are forced to rely on
 
465
                the broken TextAlign mechanism, done above...
 
466
                (width will be taken from initial rescale function)
 
467
                //Crap, this is of the old font, before setTextFormat is called!!! (or something odd, the width is just wrong!)
 
468
                //Though RIGHT Align doesn't work in Flash if width is greater than box... :(
 
469
                var tw = tf.getLineMetrics(0).width + 2 * gutter;
 
470
                tf.width = tw;
 
471
                //trace( tf.text + ":" +  tw );
 
472
                                
 
473
                switch( align )
 
474
                {
 
475
                        case Left:
 
476
                                tf.x = 0;
 
477
                        case Right:
 
478
                                tf.x = w - tw;
 
479
                        case Center:
 
480
                                tf.x = (w/2) - (tw/2);
 
481
                }
 
482
*/
 
483
 
 
484
                //clip text to our borders
 
485
                if( clipMask != null )
 
486
                {
 
487
                        clipMask.graphics.clear();
 
488
                        clipMask.graphics.beginFill( 0xFFFFFF );
 
489
                        clipMask.graphics.drawRect( 0, 0, w, h );
 
490
                        clipMask.graphics.endFill();
 
491
                }
 
492
                
 
493
                //we need to also check in the case of a resize, no just textual changes,
 
494
                //the trick is however that until we size the TextField we won't know if we
 
495
                //need a scrollbar, so thus this recusrion here -- ARGH!! Flash strikes again,
 
496
                //somehow the scrolling parts of a text-field aren't set synchronously!!!  This only
 
497
                //seems to happen when the control is changing visibility...
 
498
                //TODO:we're assuming it will never flip-flop, but that may be a false assumption
 
499
                if( checkScroll( false ) )
 
500
                        update();
 
501
        }
 
502
        
 
503
        public function getPrefWidth() : SizeType
 
504
        {
 
505
                if( tf.multiline )
 
506
                        return SizeType.XWidth( prefNumRowsCols.x );
 
507
                return SizeType.XWidth( 
 
508
                        font.xWidthOf( tf.text, prefLineHeight, getBold() ) 
 
509
                        + 0.5   //to account for any gutters
 
510
                        );
 
511
        }
 
512
        
 
513
        public function getPrefHeight() : SizeType
 
514
        {
 
515
                if( tf.multiline )
 
516
                        return SizeType.Lines( prefNumRowsCols.y * prefLineHeight );
 
517
                return SizeType.Lines( prefLineHeight );
 
518
        }
 
519
 
 
520
        define(`NoSizing',`')
 
521
        include(`MixinWidget.ihx')
 
522
}