2007-12-17

The Best of All Features

I’ve added the best feature to Woopsi. The feature that tops all others. The feature that’s been missing, and will make all other features pale in comparison.

At last, a debug console.

WoopsiDebugConsole

It’s just a combination of a screen, window and multiline textbox that open when Debug::output() is called, but it should make developing easier. I got absolutely sick of not being able to debug anything, particularly when Woopsi works fine in all of the emulators but breaks on the real hardware. Having to unplug the flash cart, take out the micro SD card, stick it into the SD adaptor, stick it into the card reader, wait for Windows to recognise it, copy the ROM across, reverse the whole process, get the DS booted and finally load Woopsi to discover that it still doesn’t work and that I have no way to work out why was driving me crazy.

Other new changes. Text::wrap() wasn’t splitting at line returns in the last line of text; that’s now fixed. I’ve fixed some more bugs in the TextViewer - that must be the buggiest piece of code I’ve ever written. It’s full of bugs because the Text class (which used to be in the TextViewer) is full of bugs, and the bugs cancel each other out. As soon as I fix the bugs in one class, the other class stops working. It makes it impossible to find out where the bug is or what it is.

I’ve fixed the clipping routine in the SuperBitmap class - another pain in the behind. For some reason, the code worked perfectly when emulated, but went wrong on the DS - instead of drawing filled rects of a particular colour, it kept drawing black rects or doing nothing at all. Some very strange pointer stuff must have been happening, but I’ve rewritten it and it all seems to work now.

A few changes to the MultiLineTextBox - it now outputs using the GraphicsPort, automatically sets its buffered row count to the number of visible rows (if no value is specified), forgets rows of text that fall outside its buffer, and implements a “setAutomaticDrawing()” function. If set to “false”, draw() must be called after calling setText() or addText() for the changes to be visible. Lets you call multiple addText() functions without multiple draws occurring.

There are a few changes I could make to the MultiLineTextBox - it should really scroll with the DMA hardware and only print new text, but on the DS itself it’s quick enough at the moment that I’m not worried about it.

Comments

Jeff on 2007-12-17 at 22:18 said:

You know, you really should build with an environment that shows you warnings - its a good idea to get rid of them so that real problems can show through…

C:/NDS/home/jeffl/trunk/Woopsi/source/main.cpp: In function ‘int main()’:

C:/NDS/home/jeffl/trunk/Woopsi/source/main.cpp:178: warning: unused variable ‘multiText’

C:/NDS/home/jeffl/trunk/Woopsi/source/main.cpp:220: warning: unused variable ‘calculator’

C:/NDS/home/jeffl/trunk/Woopsi/source/main.cpp:223: warning: unused variable ‘pong’

C:/NDS/home/jeffl/trunk/Woopsi/source/main.cpp:226: warning: unused variable ‘pacman’

C:/NDS/home/jeffl/trunk/Woopsi/woopsi/alert.cpp: In constructor ‘Alert::Alert(s16, s16, u16, u16, char, char, FontBase*)’:

C:/NDS/home/jeffl/trunk/Woopsi/woopsi/alert.cpp:26: warning: unused variable ‘buttonWidth’

Nothing tragic there, just disconcerting to see them go past every time. In my day job, we have our warning levels cranked up as high as they will go, and we force all warnings to be treated as errors - there are no cases where we consider a warning to be acceptable, since the programmer should always be able to achieve the same result without warning (even if it means explicitly initialising variables that you just know can’t be accessed before they are set)

Jeff on 2007-12-17 at 22:19 said:

Having said that, “yeehah! a offical debug console!!!”

Thank you thank you thank you…

Jeff on 2007-12-17 at 23:07 said:

I’ve reduced my development cycle by:

  • enabling wifi network sharing on my imac
  • pointing a web page directly into the Woopsi/Release area

a) compile b) reboot nintendo c) erase myapp.nds (X on the R4 file selection list) d) start dsorganise browser e) click favorites f) click link to my release dir g) click binary to download h) reboot nds

I could glue f & g together if I wanted to, since the favorite could be the binary.

For some reason, DSOrganise still can’t boot the binary - I suspect this is an R4 problem - it seems like DSO just doesn’t do the DLDI thing it claims. Also the erase step seems necessary - I took a few cycles before I realised that DSO wasn’t overwriting existing files, just silently putting the download “somewhere else”…

But, there’s no fiddling with SD cards which is a real speed improvement.

Steven on 2007-12-17 at 23:16 said:

I think I may actually start using that approach, but since I’ve got a webserver in my room, and my DS is setup to access the internet from my WiFi router, I just need to copy the nds onto the webserver and dl from there… now if only there was a util the did the whole thing as 1 step….

ant on 2007-12-17 at 23:26 said:

The warnings do show up when I build the ROM. The Alert warning is because I got part way through writing that class and then got distracted by everything I’ve been doing since then, so it’s a good reminder that I need to go back and sort it out. The other warnings are because I’m getting pointers to each of those classes in main.cpp but am not doing anything with them. What suppose I should be doing is deleting those after the main loop, but as the code will never reach there it’s a bit pointless.

I actually decided I was fed up of the warnings the other day and, following the advice of a “C++ Coding Standards” book I bought a few weeks ago, called the pointers like this:

Thingy* pointer = new Thingy(); pointer;

However, this just gave me another warning telling me that the call didn’t do anything.

Cheers, C++ book. Very useful, that.

Writing the debug console was stupidly difficult because, in order to write it, I had to remove all of the existing debug methods. Every time I came across a bug I had no way to work out what the problem was because there was no debug code any more, so I ended up writing out dozens of calls to the TextWriter and sprintf-ing every variable I wanted to trace. The MultiLineTextBox and Text classes were chock full of bugs.

Argh!

If you find any problems with it, don’t be too surprised.

Jeff on 2007-12-18 at 01:18 said:

Just use:

new Thingy();

That’ll work exactly as expected, creating a new instance and losing the pointer to it. It shouldn’t give a warning because the compiler can see exactly what you want to do.

As to the Debug console, one wish I had was for a smaller font (which would then give more content on-screen. I suspect I’d be happy with a MONO-CASE font - something like PF Arma Five which is available free (Creative Commons Attribution Licence) here: http://www.pinvoke.com/font/ - however, I’m not so sure how it would look MONO-SPACED.

Jeff on 2007-12-18 at 01:19 said:

Steven,

you should be able to change the makefiles to include a step that ftp’s the finished .nds file onto your webserver.

I had a custom step in there to dlditool it as well, before I realised that the R4 doesn’t need that.

Jeff on 2007-12-18 at 01:27 said:

… or perhaps http://www.dafont.com/bm-mini.font which looks to be 5 x 8 (+1 for inter-character spacing) which would give 256/6= 42ish characters across the screen instead of 32.

Actually, it does raise the issue - does PAgfx.exe support non-square characters? I have this feeling it determines the height of the characters by dividing the width of the bitmap by 32, or some such…

ant on 2007-12-18 at 07:50 said:

PAGfx doesn’t do anything clever with fonts. It treats them like any other bitmap. Width and height of characters must therefore be passed into Woopsi’s font constructor.

If I get around to supporting non-fixed width fonts, that kind of meta data is usually stored in the font header.

I’ll look into adding a small font as a secondary system font.

Jeff on 2007-12-18 at 09:24 said:

Hmmm. Why does Debug::output() require a Gadget input?


        // Locate Woopsi by finding the top of the tree
        while (gadget->getParent() != NULL) {
                gadget = gadget->getParent();
        }

vs


#define woopsiApplication ((Woopsi*)Woopsi::singleton)

so you don’t need to pass a gadget into each output call. A global really really doesn’t cost that much…

(I tried to integrate Debug::output() into one of my helper classes which isn’t a Gadget, nor does it have any pointers to any - fortunately, I can pass in woopsiApplication but thats sort of covering up a pretty spotty blemish)

Jeff on 2007-12-18 at 09:30 said:

Even if you don’t remove the need for Gadget, you could add something like this to simplify things a bunch:


#include
...

void Debug::printf(Gadget *g, const char *fmt,...)
{
        char buff[256];
        va_list args;
        va_start(args,fmt);
        vsnprintf(buff,sizeof(buff),fmt,args);
        va_end(args);
        output(g, buff);
}

ant on 2007-12-18 at 09:46 said:

The singleton define is a good plan, I’ll do that. The args thing is also good, I shall do that too.

Jeff on 2007-12-18 at 10:43 said:

One other question - why put the text into a window? They seem to work perfectly well directly in a screen, and since the Debug screen is one that you wouldn’t normally put extra stuff into (because you can’t get at it), its just a waste of pixels to put the Window decorations up…

Jeff on 2007-12-18 at 10:47 said:

Its easy to be critical, of course, I hope I’m not coming off as telling you that you’re doing it all wrong because thats not my intent. Its a briliant framework, no mistake about it.

I’m a bit frustrated at the moment, because I’ve taken a bit of a step backwards - I had a class that was working fine at keeping the time in sync, but sometime around the palib changes, it just stopped working, and nothing, not accessing the IPC area, not calling gmtime(), nothing seems to work any more.

I’m still expecting its my problem, but I wonder if you could check as well? If a window registers a VBL() and just Debug::output’s the time, you should see it incrementing.

(This is the issue where I wonder whether I’ve got mismatched PALib/libnds - oh, and of course, we’ve just decided to move office at work so I’m up to my eyeballs in real work as opposed to messing with the DS)

ant on 2007-12-18 at 11:35 said:

The Debug console uses a window because… erm… Well, there isn’t a really good reason for it. I suppose I just expect things to take place within windows. I’m also writing and testing the behaviour of the multiline textbox, so it’s useful to check that it works as I expect it to when used as I expect it to be used.

Oh, hang on - there was a reason for it. I’m going to add a scrollbar to the debug console, so that you can scroll up through the buffered rows of text. That was it. I’m not sure if the multiline textbox should inherit from the still-to-be-written scrolling panel gadget. If it did, it would effectively make the TextViewer redundant, which would be marvellous.

Regarding the RTC, it doesn’t work here either. Just to be sure, I tried one of the PALib RTC examples, and that doesn’t work. It looks like an emulator problem. Both the example and Woopsi work perfectly with the RTC functions when used with No$GBA. I don’t think DeSMuMe has RTC emulation. Have you tried it on the real DS?

Any and all constructive criticism is very welcome. I think I’ve implemented most of the new features and amendments you’ve suggested - I wouldn’t make the changes if I didn’t think they were worthwhile! Woopsi is definitely a better system now than it would have been if I’d been programming with no input other than my own ideas.

Speaking of which, one other change I think I’m going to make is to change the window’s getClientRect() function return a rect offset below the window title - there’s no reason why someone who wanted to overlap the title couldn’t ignore the rect co-ordinates, but there’s every reason why someone wouldn’t want to overlap it and the current solution doesn’t help at all.

I’ve added the Debug::prinft() thing too. Singleton will come soon. I’m thinking of making the constructor private and forcing Woopsi to work as a singleton. I’m pondering it before making the change, though - does it have a downside? Any thoughts?

Jeff on 2007-12-18 at 19:26 said:

RTC: no, it doesn’t work on hardware, though it definitely did. I showed it to my wife who was most impressed. I know it didn’t work in DesMuMe. I guess I keep chasing…

If you make the constructor private, you rely on static initialisation and/or the Woopsi object being initialised at “unexpected times”. ie, you can’t put

Woopsi theApp;

in your main application any more. You also preclude my subclassing of Woopsi to treat it as the “real application object”, which I know is not something you are enamoured of.

ie, my main looks like this:


int main()
{
        // Create woopsi application
        OrganizerApplication theApp;
        theApp.Startup();              // start it up
        theApp.draw();                  // ensure physical screen is up to date
        theApp.RunLoop();          // let the event loop run
        theApp.Shutdown();        / and we are done

        return 0;
}

class OrganizerApplication :
         public WoopsiApplication
{
public:
        OrganizerApplication();
        virtual ~OrganizerApplication();
        void Startup(void);
        void Shutdown(void);

        // various functions we know how to perform
        void FileBrowser(void);
...

My MenuScreen then does things like this:


        b = new Button(r.x, r.y, 64, 16, "Browse");
        b->setRefcon(BROWSE_BUTTON);
        b->setEventHandler(this);
        addGadget(b);

and


void MenuScreen::handleRelease(EventArgs e)
{
        // ignore it if the stylus is not in the button still
        if (!e.gadget->checkCollision(e.eventX,e.eventY)) return;

        switch (e.gadget->getRefcon()) {
        case BROWSE_BUTTON:
                ((OrganizerApplication*)woopsiApplication)->FileBrowser();
               break;

ie, it merely translated button pushes into method calls against the main application object, which it already has singleton access to. Whilst I could have had the application logic listen directly to the button releases, that meant that I would have to have an application-wide set of enums for the buttons, instead of one per screen, which would make it harder to add new content to screens; it would impact (slightly but its there) all other screens.

I could push the application logic into a distinct object class, and maintain two singletons, one Woopsi and one AppLogic, but that costs 8 bytes instead of 4.

;-)

Jeff on 2007-12-18 at 19:39 said:

As to the scrollbar on the debug window, I agree its an interesting problem to try to solve, and I don’t think there’s a complete no-brain answer. In the past I’ve seen it done two ways, with the text widget owning the scrollbar, and with the window.

Motif Windows looked like that a lot, as I recall, with the window contents really not knowing that they’d been scrolled off screen - you really rely on your clipping logic to be efficient in that world, and it complicated things because you want the state of the scrollbar to be tied to the state of the contents. If there were no contents off-screen, some people want the scroll bar to hide, which changes the available area on-screen, which changes the layout, which might scroll more stuff back on-screen.

Of course, that approach expects that you have the entire text view expanded and just scroll it up/down the screen, which means you don’t see its bottom border till you get to the bottom of the box. Not nice, in my opinion, and probably not what you were thinking.

In this case, if you have a debug window with a seperate scrollbar, then you need to keep track of the number of lines displayed in the text box to ensure that you have the position and size of the scrollbar grip correct, and thats pushing awkward logic out of the textbox (which knows all that stuff already, and didn’t need to expose it) and into the enclosing window. Personally, I’d rather than text box handled it.

Windows Sys32ListView definitely does the scrollbar thing itself - interestingly, the standard window class also does scrollbars itself, and the implementation is so bad, I’ve never been able to make it work in a way that looks acceptable to end users - we end up inserting our own child controls.

Some windows controls, however, have a notion of a “buddy” control - you can put an up/down control next to a text box, tell them that they are buddies, and the text seems to automagically work out how to increment/decrement its values when you press the up/down. Personally, I think its cute, but not justified. However, you could perhaps build something similar with a “buddy” scrollbar that the TextViewer knew how to listen to, thus keeping the logic out of the owning screen (other than to establish buddyhood) - the TextView would talk to the scrollbar the same way that my List talks to its DataSource

All said and done, I think I’d go with passing a GADGET_ flag into the TextViewer to say “display a scroll bar”, rather than have two viewer classes. The current behaviour of dragging simply by stylus in the text means that you need all the scrolling logic anyway, the scrollbar is a tiny bit of graphics by comparison.

Jeff on 2007-12-18 at 19:43 said:

Of course, if you get the Buddy scrollbar working seperately, I can use that in my List as well

;-)

ant on 2007-12-18 at 20:04 said:

The TextViewer needs scrapping. I’ve got a vague idea that if I create a gadget that has TextViewer-like scrolling, with clipping, but doesn’t draw anything new to the screen (just calls an update(x, y, width, height) method), other gadgets can inherit from it and implement their own update() functions. That’ll mean I can have TextViewer-style scrolling in any gadget (ListBox, for example) that inherits from it - the scrolling is already handled in the base class, and all I need to do is write another drawing function that clips to a particular region.

My plan for the scrollbar was to pass it a pointer to a scrolling gadget in its constructor. The scrolling gadget would expose its total width and height, plus its current scroll position, and the scrollbar can resize and reposition its grip to fit. If the scrolling gadget contains member pointers to horizontal and vertical scrollbars, it can even update the scrollbars if it gets dragged. The interface is then constructed like this:


ScrollingPanel* panel = new Panel(x, y, width, height);
VerticalScrollBar* bar = new VerticalScrollBar(panel, x, y, width, height);

The scrollbar’s constructor would do something like this:


VerticalScrollBar(ScrollingPanel* panel, s16 x, s16 y, s16 width, s16 height) {
    _panel = panel'
    _panel->setVerticalScrollBar(this);
}

The drag() function of the scrolling gadget would inform the scrollbars (if set) that the position of the visible region was changing. Similarly, the scrollbar drag() function would notify the scrolling gadget that its position was changing. Both would adjust themselves to match.

Regarding the screen client rect idea - I can’t do it. The Screen::getGraphicsPort() function bases its clipping rectangle on the size of the client rect. If the rect.y value is 13 (screen title height), that means that any attempts to draw into the y space between 0 and 13 are automatically clipped. Screen::getTitleHeight() is the only way around it, I’m afraid. Without me doing some rethinking and putting in a lot of other work, anyway.

ant on 2007-12-18 at 20:16 said:

Gahh, the spam filter has made the comments go out of sync again.

OK, no to the private Woopsi constructor.

(Pause to look through your code.)

Ahh, I see how you’ve handled the singleton. Yep, that makes sense. I’ll add that in.

EDIT:

Oh, wait! How about if the Woopsi constructor sets a static variable in the debug console? That seems like it might be a bit tidier.

EDIT EDIT:

Well, that’s what I’ve done, but I have the distinct feeling that approach is totally wrong, and I should have done what you suggested in the first place. However, that doesn’t feel quite right either. Maybe I’ll sleep on it…

ant on 2007-12-18 at 20:26 said:

Regarding the RTC stuff:

Woopsi RTC

My clock appears to be wrong, but it is at least running.

Jeff on 2007-12-18 at 21:55 said:

I’m not quite sure what you mean by having the scrollable gadget expose its ‘width and height’ and current position. Any given scrollbar is only really interested width OR height. In fact, its really only interested in min/max/current.

Are you suggesting two classes, HorizontalScrollBar and VerticalScrollBar, which simplifies the drawing logic? The Windows scrollbar just has a flag bit to say “vertical” rather than “horizontal”. But I can see where horizontal scrollbars are going to be less common…

And are you suggesting that the scrollbar asks its owner for the dimension information, or that the owner tells the scrollbar? Your constructor shows “graphic” size being passed in, but not “scrollarea” size, if you get my meaning. min/max/current values for the scrollbar.

My List has a ‘makeVisible(int row)’ method which allows the application to programmaticaly force the current view to show a specific row; it also allows scrolling via keypad. I’d be interested to see what you would be coding in the owners drag() method - I assume its just ScrollBar::SetCurrent(…something).

The question is how does the scrollbar notify the owner that a scroll is required because it has been touched - is that just “raise a valueChanged event”? Will the system handle them being raised during a drag() loop?

ant on 2007-12-18 at 22:58 said:

I’ll have two scrollbars, yep - one vertical, one horizontal. Makes things so much easier.

The scrolling panel’s drag function would look like this:


bool drag(s16 x, s16 y) {
    scroll(x, y);
    if (_scrollBarVertical != NULL) {
        _scrollBarVertical->resetGripPosition();
    }
}

The scrollbar’s resetGripPosition() function would look something like this:


void resetGripPosition() {
    if (_scrollingPanel != NULL) {
        s32 scaledY = (_height * _scrollingPanel->getScrollY()) / _scrollingPanel->getScrollYMax();
        _grip->moveTo(_grip->getX(), scaledY);
}

I think that’s the right way around. This should allow the scrollbar to resize its grip gadget if the scrolling panel’s dimensions change.

The grip will be a child gadget of the scrollbar container to enable me to use the existing gadget functionality, but this detail will be hidden from the developer.

It’ll work in the opposite way if the scrollbar is dragged or clicked - the scrollbar will order the scrolling panel to scroll to match.

I’m completely avoiding using the event system internally. It seems neater to me that the event system is strictly used as a bridge between Woopsi’s internal magic black box and the outside application.

Jeff on 2007-12-18 at 23:23 said:

Um, I’m not sure about having the scroll bar do the calculation itself.

My List, which would like to use your scrollbars, does not have even-sized rows. This allows, say, the selected row to show twice as much data as the rows around it, or to use a larger font, etc. The scrolling really needs to be based on abstract row numbers, not pixel scaling.

Pushing the idea to its extreme, I can visualise someone with a panel that contains a jpeg that is more than 32K pixels tall - your s16 values are going to start overflowing. Ok, I can’t really see a jpeg more than 32K pixels tall, but I can see a particular long text page (if someone loaded up an eBook - if its William Gaddis or James Joyce, that could be one long PARAGRAPH.

ant on 2007-12-18 at 23:27 said:

I think the TextViewer uses u32 or s32 values for all of its scrolling, so I’d probably use one of those. The code above is just a quick illustration of a half-formed idea.

It depends on how I want to scroll things, really. If I go for a block-scroll approach I’d scroll based on individual element heights and row numbers. If I go for a smooth-scrolling gadget I’d need to do it based on pixels.

Needs more thought, but I’ve found that the programming method that suits me best is just to jump in and see what happens.