PDA

View Full Version : I'm not sure I 'get' Openlaszlo


rsilvergun
05-11-2009, 06:57 PM
Hi all,
I've written a few classes for a game I'm working on. One extends the <text> object so that the text inside can be made to print out one character at a time (like the old Dragon Warrior games). I call it TextScroller:

<!--

This class implements a simple animation that displays a line of text 1 character at a time,
simular to console RPGs (think Dragon Warrior or Final Fantasy).

How it works:
1. Store the text we want to print in the fullText attribute.
2. Constrain the text attribute to be a substring of the fullText attribute (from 0 to the limit attribute).
3. Create an animator that increase the limit attribute. As the limit attribute increates the test attribute is updated and the text 'prints'.
4. calling setAttribute('fullText', 'My Text') changes the text, calling the animator's doStart() method causes the text to start printing.

BUG: The TextScroller can't resize larger than the first value of fullText.
Since I can't figure out why, I'm cheating and setting fullText's value to a long sequence of x's

-->
<class name='TextScroller' multiline='true' extends='text' >
<attribute name='limit' value='0' type='number'/>
<attribute name="width" value="600"/>
<attribute name='text' value='$always{this.fullText.substr(0,this.limit)} '/>
<attribute name='fullText' type='string' setter='setFullText(fullText)'
value="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx"
/>

<animator name="animateText" attribute="limit" to='$always{classroot.fullText.length}' duration="1500" start="false"/>

<method name='setFullText' args='myText'>

this.limit = 0;
this.fullText = myText;

</method>

</class>

The other is a box to contain the TextScroller. I used a StableBorderLayout to add borders. It's called DialogBox

<!-- The dialogBox class takes care of displaying the dialog box -->
<class name="dialogBox" width="${immediateparent.width}" height='${immediateparent.height/2 - 100}' x='0' y='400' extends="view">

<attribute name="fullscene" value="false" type="boolean"/>

<view width="${immediateparent.width}" height="${immediateparent.height}">

<stableborderlayout axis='y'/>
<view bgcolor="red" height="5" width="${immediateparent.width}" />
<view width="${immediateparent.width}">

<stableborderlayout axis='x'/>
<view bgcolor="blue" width="5" height="${immediateparent.width}"/>
<view resource='resources/semi_transparent1.png' stretches='both' onclick='dialogText.animateText.doStart()'>
<TextScroller name="dialogText" fullText='This is an excellent start'/>
</view>

<view bgcolor="blue" width="5" height="${immediateparent.width}"/>

</view>
<view bgcolor="red" height="5" width="${immediateparent.width}"/>

</view>

</class>
<!-- End class dialogBox-->

What I don't get (aside from the weird bug in my TextScroller I'm working around) is how I'm suppose to send a message to the DialogBox object to update the TextScroller's text. e.g. how do I call my TextScroller's setAttribute method for fullText (and then start the animator when I'm ready)?

I guess I could dig down into the class structure, but the code quickly gets hideous. What I'm wondering is, what is the 'right' way to do this. OL made it so easy to do the animation, that I figure I must be thinking in the wrong direction if it's this hard to call a method on an object just because it's buried in another object.

Thanks all!

madtux666
05-12-2009, 12:22 AM
Hi rsilvergun,

I can not run your application, please provide working (or not working ;-) app. I tried create instance of class TextScroller, I can not see anything, but error in Debugger:


ERROR: Invalid event sender: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxx' (for event onlength)



PS. Post your binary later here (id it is OS), I grow with such a games and I would like to see how it works, hehe ;-)



Regards,
Madtux

mjessup
05-12-2009, 05:25 AM
I think the answer to your question is events and handlers. I'm not entirely clear on which way the communication is going but perhaps what you need is something along the lines of:

<class name="dialogBox" ...>
<event name="someEvent" />
...
[some view/method/event that causes a need to update text]
someEvent.sendEvent();
...
<TextScroller name="dialogText" ...>
<handler name="someEvent" reference="classroot">
this.setFullText(...);
</handler>
</TextScroller>
...
</class>


A couple of notes in regards to "digging into the class structure". If you are not already familiar the use of classroot can be helpful when you need to move through different branches of a class' view structure. As far as the "need" to do it. I would say the answer is yes and no. I have come into situations where there was no reasonable solution other than to make some references to fairly deeper/shallower/parallel objects just to have the application wired properly. It is sometimes necessary because some components must necessarily communicate with one another. That said there are times where having to do it may be a symptom of a less than optimal code architecture, but that is difficult to say without knowing the complete structure of your application.

Long story short in my experience if there is a question of having one component send a "message" to another the answer is usually events and handlers/delegates.

kmeixner
05-12-2009, 03:45 PM
BUG: The TextScroller can't resize larger than the first value of fullText.
Since I can't figure out why, I'm cheating and setting fullText's value to a long sequence of x's

Remember to set the 'resize' option to 'true' for text items to resize when their text changes.

Also instead of setting 'value' to the text, try setting 'text' to the text.

Kevin

rsilvergun
05-12-2009, 04:40 PM
Here is the (semi working) code for madtux666. I removed the reference to the resource I didn't upload :)


<canvas height="600" width="800" debug="true">

<!--

This class implements a simple animation that displays a line of text 1 character at a time,
simular to console RPGs (think Dragon Warrior or Final Fantasy).

How it works:
1. Store the text we want to print in the fullText attribute.
2. Constrain the text attribute to be a substring of the fullText attribute (from 0 to the limit attribute).
3. Create an animator that increase the limit attribute. As the limit attribute increates the test attribute is updated and the text 'prints'.

BUG: The TextScroller can't resize larger than the first value of fullText.
Since I can't figure out why, I'm cheating and setting fullText's value to a long sequence of x's

-->
<class name='TextScroller' multiline='true' extends='text' >
<attribute name='limit' value='0' type='number'/>
<attribute name="width" value="600"/>
<attribute name='text' value='$always{this.fullText.substr(0,this.limit)} '/>
<attribute name='fullText' type='string' setter='setFullText(fullText)'
value="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx"
/>

<animator name="animateText" attribute="limit" to='$always{classroot.fullText.length}' duration="1500" start="false"/>

<method name='setFullText' args='myText'>

this.limit = 0;
this.fullText = myText;

</method>

</class>


<!-- The dialogBox class takes care of displaying the dialog box -->
<class name="dialogBox" width="${immediateparent.width}" height='${immediateparent.height/2 - 100}' x='0' y='400' extends="view">

<attribute name="fullscene" value="false" type="boolean"/>

<view width="${immediateparent.width}" height="${immediateparent.height}">

<stableborderlayout axis='y'/>
<view bgcolor="red" height="5" width="${immediateparent.width}" />
<view width="${immediateparent.width}">

<stableborderlayout axis='x'/>
<view bgcolor="blue" width="5" height="${immediateparent.width}"/>
<view height='${classroot.height}' width='${classroot.width}' onclick='dialogText.animateText.doStart()'>
<TextScroller name="dialogText" fullText='First Text works, cheating here and calling the animator when the container view is called.'/>
</view>

<view bgcolor="blue" width="5" height="${immediateparent.width}"/>

</view>
<view bgcolor="red" height="5" width="${immediateparent.width}"/>

</view>

</class>
<!-- End class dialogBox-->

<view name='containerView' bgcolor="yellow" height="${parent.height}" width="${parent.width}">

<dialogBox id="myDialogBox"/>
<handler name='onclick'>

myDialogBox.dialogText.setAttribute('fullText', "Trying to change the text here when the container view is clicked.");
Debug.write('hello world');

</handler>

</view>



</canvas>


I think mjessup's right, event handler's are the way to go. I'll read up and play around with them some tonight.

As for the bug, I tried setting resize='true', but it didn't change it. What's weird is the documents say resize defaults to true. I can't set 'text' directly because it needs to be a piece of the full text so that only that piece displays....

madtux666
05-12-2009, 11:36 PM
hehe, thanks ;-)

You may want to try motion attribute in animator, so animation goes with constant speed (not it is "easy-is" and "easy-out").


<animator name="animateText" attribute="limit" to='$always{classroot.fullText.length}'
duration="1500" start="false" motion="linear"/>

rsilvergun
05-14-2009, 04:44 PM
<canvas height="600" width="800" debug="true">

<!--

This class implements a simple animation that displays a line of text 1 character at a time,
simular to console RPGs (think Dragon Warrior or Final Fantasy).

How it works:
1. Store the text we want to print in the fullText attribute.
2. Constrain the text attribute to be a substring of the fullText attribute (from 0 to the limit attribute).
3. Create an animator that increase the limit attribute. As the limit attribute increates the test attribute is updated and the text 'prints'.

BUG: The TextScroller can't resize larger than the first value of fullText.
Since I can't figure out why, I'm cheating and setting fullText's value to a long sequence of x's

-->
<class name='TextScroller' multiline='true' resize='true' extends='text' >
<attribute name='limit' value='0' type='number'/>
<attribute name="width" value="600"/>
<attribute name='text' value='$always{this.fullText.substr(0,this.limit)} '/>
<attribute name='fullText' type='string' setter='setFullText(fullText)' />

<animator name="animateText" attribute="limit" to='$always{classroot.fullText.length}' duration="2500" start="false" motion="linear"/>

<method name='setFullText' args='myText'>

this.limit = 0;
this.fullText = myText;

</method>

</class>


<!-- The dialogBox class takes care of displaying the dialog box -->
<class name="dialogBox" width="${immediateparent.width}" height='${immediateparent.height/2 - 100}' x='0' y='400' extends="view">

<view width="${immediateparent.width}" height="${immediateparent.height}" onclick='this.goNext.sendEvent()'>

<event name='goNext'/>

<stableborderlayout axis='y'/>

<view name='borderTop' bgcolor="red" height="5" width="${immediateparent.width}" />

<view width="${immediateparent.width}">

<stableborderlayout axis='x'/>

<view name='borderRight' bgcolor="blue" width="5" height="${immediateparent.width}"/>

<view name='TextScrollerBox' height="${parent.height}" width="${parent.width}" onclick='classroot.goNext.sendEvent("Changing Text From Dialog Box click")'>
<TextScroller onclick='this.animateText.doStart()' name="dialogText" fullText='First Text works, cheating here and calling the animator when the container view is called.'>

<handler name='goNext' reference='classroot' args='nextText'>

this.setAttribute('fullText', nextText);
this.animateText.doStart();
Debug.write("Hello World");

</handler>

</TextScroller>
</view>

<view name='borderLeft' bgcolor="blue" width="5" height="${immediateparent.width}"/>

</view>

<view name='borderBottom' bgcolor="red" height="5" width="${immediateparent.width}" />

</view>


</class>
<!-- End class dialogBox-->

<view name='containerView' bgcolor="yellow" height="${parent.height}" width="${parent.width}">

<dialogBox id="myDialogBox"/>
<handler name='onclick'>

myDialogBox.goNext.sendEvent('Changing Text from within a container view.');

</handler>

</view>



</canvas>


Once I figured out how the event system worked it was pretty easy and elegant. On a side note, the documentation on Event Syntax is way out of date, and not very clear on how/why to fire your own events. Then again maybe I'm doing it wrong, but it works great :).

The long of the short is you can use events to access anything no matter how deep it's buried just by using referrence='classroot' and declaring the Event at the top level of your class.

rsilvergun
05-14-2009, 05:37 PM
I can't get TextScroller to display text past the length of whatever string fullText was first assigned. So if fullText starts off 20 characters long then all I can display is 20 characters...

I tried setting resize='true' like kmeixner suggested, but that just makes the view stretch, the text still will not display fully. I'm not sure what you meant by telling me to set the 'text' to the text instead of setting the value....

mjessup
05-15-2009, 04:30 AM
I gave your code a quick try and I got the error in the debugger that madtux mentioned about your animator constraint (in both LPS 4.1.1 and LPS 4.3.0):

ERROR: Invalid event sender: 'xxxxxx' (for event onlength)

Where xxxxx is your initial value of fullText. It was trying to look for an "onlength" event in the string literal (which of course doesn't exist). I got it running on my machine by adding an indirection method for starting the animator and setting its attribute:


<class name='TextScroller' multiline='true' extends='text' >
<attribute name='limit' value='0' type='number'/>
<attribute name="width" value="600"/>
<attribute name='text' value='$always{this.fullText.substr(0,this.limit)} '/>
<attribute name='fullText' type='string' setter='setFullText(fullText)' />

<animator name="animateText" attribute="limit"
duration="2500" start="false" motion="linear" />
<method name="startAnimator"><![CDATA[
animateText.setAttribute('to', fullText.length);
animateText.doStart();
]]></method>

<method name='setFullText' args='myText'>
this.limit = 0;
this.fullText = myText;
</method>
</class>


Then instead of calling scrollobject.animateText.doStart(), call scrollobject.startAnimator(). Another thing is from the documentation I get the impression that resize="true" is meaningless if multiline="true":

If true, the lines of text are wrapped to fit within the text width. ... If you set multiline=true, you probably want to explicitly a width for the text also; if multiline=true and you do not specify a width, the system will pick an arbitrary width (100 pixels at the moment). When multiline=true, the text is automatially re-wrapped whenever the content is modified by calls to setText, or whenever the width of the text view is modified.

which makes sense. Multiline says the text should just wrap when it a single line exceeds the current width of the view. So I think the error from the animator constraint maybe the cause of your problems, give it a try and see if it works the way you expect.

rsilvergun
05-17-2009, 08:00 AM
That works like a charm! Here's the final code for everyone's reference:


<canvas height="600" width="800" debug="true">

<!--

This class implements a simple animation that displays a line of text 1 character at a time,
simular to console RPGs (think Dragon Warrior or Final Fantasy).

Constrain the actual text value to a substring of the full text (stored in another variable) so I can control what text is displayed with an animator.
As the Animator runs it increases the limit, and each time the limit increases another letter is displayed, until the whole string is displayed.

-->
<class name='TextScroller' multiline='true' extends='text' >
<attribute name='limit' value='0' type='number'/>
<attribute name='text' value='$always{this.fullText.substr(0,this.limit)} '/>
<attribute name='fullText' value="" type='string' setter='setFullText(fullText)' />

<animator name="animateText" attribute="limit"
duration="1500" start="false" motion="linear" />
<method name="startAnimator"><![CDATA[
animateText.setAttribute('to', fullText.length);
animateText.doStart();
]]></method>

<method name='setFullText' args='myText'>
this.limit = 0;
this.fullText = myText;
</method>
</class>
<!--End class TextScroller-->

<!-- The dialogBox class takes care of displaying the dialog box -->
<class name="dialogBox" width="${immediateparent.width}" height='${immediateparent.height/2 - 100}' x='0' y='400' extends="view">

<view width="${immediateparent.width}" height="${immediateparent.height}" onclick='this.goNext.sendEvent()'>

<event name='goNext'/>
<event name='snapText'/>

<stableborderlayout axis='y'/>

<view name='borderTop' bgcolor="red" height="5" width="${immediateparent.width}" />

<view width="${immediateparent.width}">

<stableborderlayout axis='x'/>

<view name='borderRight' bgcolor="blue" width="5" height="${immediateparent.width}"/>

<view resource='resources/semi_transparent1.png' stretches='both' name='TextScrollerBox' onclick='classroot.goNext.sendEvent("Changing Text From Dialog Box click")'>
<TextScroller onclick='this.animateText.doStart()' name="dialogText">

<handler name='goNext' reference='classroot' args='nextText'>

this.setAttribute('fullText', nextText);
this.startAnimator();
Debug.write("Hello World");

</handler>

</TextScroller>
</view>

<view name='borderLeft' bgcolor="blue" width="5" height="${immediateparent.width}"/>

</view>

<view name='borderBottom' bgcolor="red" height="5" width="${immediateparent.width}" />

</view>

</class>
<!-- End class dialogBox-->

<view name='containerView' bgcolor="yellow" height="${parent.height}" width="${parent.width}">

<dialogBox id="myDialogBox"/>
<handler name='onclick'>

myDialogBox.goNext.sendEvent('Changing Text from within a container view. fjsdl jflsd lsd jld sd jlksdj fsdj flksdj lkdl kfjsdl jlksd jlksdj lk jlkfdsj lksdj lkj lkflk dsjlfjsdlfjdklsfjlkdsjflksd fj lfjdk lfjlk sdlkjf slkdjflkdsjf lsdj flksdjf lsjflk sfj djfs dflksd jlfsdj flksdj flksjdf sdljkfjds lk END');

</handler>

</view>

</canvas>


This is the code I use, but if you don't want to track down a copy of the resource file (I used google to find a generic transparent png for testing), replace lines 49-50 with this:


<view height="${parent.height}" width="${parent.width}" name='TextScrollerBox' onclick='classroot.goNext.sendEvent("Changing Text From Dialog Box click")'>
<TextScroller width='${immediateparent.width}' onclick='this.animateText.doStart()' name="dialogText">


you have to give TextScroller a width when you don't use a resource, otherwise it breaks multiline.