2007-10-30

More Updates - Good Things, Bad Things

More Woopsi updates. Bad news first, the TextViewer is broken again. Actually, I think it was always broken, but it’s only now that I’ve extracted the text manipulation code from it and fixed the problems there that the problems with the TextViewer have come to light. Nothing major - it’s just one of those annoyingly tricky maths things that’s obvious once you can see where the problem is.

I thought I could avoid the whole issue by inheriting from the SuperBitmap instead and just use that as a pre-made canvas, but that will double the amount of memory being copied around so I threw that idea out.

Back to the good news. Radio buttons and radio button groups now work. It’s possible to set the state of an individual button (by using a pointer to that button), or to set the selected button within the entire group (by using a pointer to the group). Radio buttons support the tri-state concept, so they can be either “off”, “on” or “something else”. I’ve called the third state “mu” in honour of “Zen and the Art of Motorcycle Maintenance”. The third state can only be activated through the API, which makes the interface tidier.

I’ve created a glyph for the radio buttons that looks fairly similar to the original Amiga button. I can’t use the originals exactly because WinUAE is playing silly buggers and won’t give me a non-doubled resolution, so my screenshots are the wrong size. More aggravating, both Photoshop and Paintshop muck up the resizing. As radio buttons have circular borders in two colours, and as I haven’t got a two-colour circle routine, I’ve just used a glyph to represent the entire gadget. This means that the radio button image can’t be resized, but (because the DS’ touch screen isn’t known for its accuracy) the rest of the button can be resized. The clickable area of the button can be much larger than the glyph.

One neat thing that the button group does is resize itself automatically to contain radio buttons as they’re added. Developers don’t need to worry about making sure the group’s container is wide/tall enough for the buttons they’re adding (that’s important in order to detect clicks and redraws, etc). Writing that feature made me realise that all of the gadgets could resize automatically to accomodate their children (or text, in the case of buttons), which would give me a completely font size-independent interface. It’d have to be optional, natch, but it might be something for version 2.

When trying (and failing) to get a decent screenshot of the radio button glyph, I noticed that the glyph shown when a depth gadget was clicked is different to the standard glyph. Woopsi’s depth gadgets now have a “clicked” image as well as an “unclicked” image.

Checkboxes are also now available, and they too support the tri-state concept. Again, the third state can only be set via the API. At the moment, I’m not convinced that the glyph for the third state is particularly good (it’s just the tick in white instead of black), but that can be fixed later. The checkbox border uses the standard Woopsi border code since it’s rectangular, so the boxes can be resized.

I’ve fixed a few daft bugs in the Text class (the data manipulator) - I’d got columns and rows upside down and multiplied all wrong (must have been late when I did that), so the glyph populated/blank code wasn’t working. I’ve added a few other useful methods to it, too - calculating line lengths in pixels as well as characters, and calculating the same lengths but with any trailing spaces trimmed.

Trailing space removal was essential for the next addition - a multiline text box. Still got a few things to do to it (limiting the number of lines of text that it will buffer in memory, block scrolling), but it functions in more or less the same way as the standard textbox (text alignment, etc) but uses the Text class to handle text manipulation. It can be used all over the place - the debug console, the About requester, the Alert requester, etc.

I started modifying the original textbox to support all of this, then noticed that most of the other gadgets inherit from that and I was breaking everything. Ooops!

Short break to reboot Parallels for the third time in 20 minutes.

If Windows would stop crashing on me, I might be able to find out what else I’ve done… (Grumble grumble.)

Aha! Mainly just tidying up - fixing bugs, moving things out of the window and screen classes into the gadget class (child redraw code - took me a while to figure out why the radio buttons weren’t getting redrawn), etc. Most gadget action methods (click(), move()) now return bools to indicate whether or not they succeeded or failed. An action can return false if it doesn’t complete - for example, calling “moveTo()” with the gadget’s current co-ordinates results in no movement, which the “moveTo()” function detects and rejects. Finally, I’ve added “enabled” and “disabled” events. I’m thinking of adding a “drawn” event, too (or “painted”, in Windows speak).

One thing I need to note down. If I do implement a preferences system in Woopsi, I’ll need to have a version with prefs and a version without. I decided last night that this was another opportunity for a bad pun, so the sans-prefs version will be called “Little Woop”, and the full-fat version will be called “Big Woop”. I might need a new circus-ey, piratey logo for that one.

The SVN code is all up to date, and should you want a demo, the ROM images are in there, too. Oh! I need to put the docs in there. I’ll do that now.

In the meantime, here’s a screenshot (scrappy layout at the moment as I’m just testing things):

Woopsi Checkboxes

(Note to self, on looking at an Amiga screenshot - textboxes that can be altered have a double-thickness border. Bevel out, then bevel in. Should be easy enough to add.)

Comments

Jeff on 2007-10-31 at 00:58 said:

I don’t quite see why you need to seperate a preferences system out, unless you are planning to do something like skinning. Surely the Woopsi classes that implement preferences panels just sit on top of the Woopsi gadgets - if I don’t include them in my application, they won’t get linked - compiled yes, not not linked.

(And, of course, if you implement them so that inclusion is actually via #include which contains inline class definitions, you don’t even compile the .cpp files unless you actually want to use them)

I think checkboxes on OSX and Windows both use a [=] ie a solid horizontal dash to indicate ‘mu’

When I tried the latest demo, I managed to close all the windows then realised that I couldn’t get them back - no big deal really, just a bit disappointing that all that goodness was gone. It did make me wonder, however, does the Screen get notified if the user clicks in what I think of as “the desktop” ?

ant on 2007-10-31 at 01:22 said:

Skinning is on the list for version 2, but no version 2 features are guaranteed to get included. I’m planning on having the preferences included automatically, so they’re available with no programmer effort by default. That’s why I’d need Little Woop - for people that don’t want users to tinker with their interface settings. I haven’t completely thought it through yet, but I’ve got a fairly good idea of what I want to achieve with it.

Thanks for the mu info. Just remembered - the OSX “Keyboard & Mouse” settings window has a mu checkbox on it when children in a list are mixed on/off. You’re right - it’s a dash. I’ll change that.

Yeah, I was going to put a non-closeable (is that even a word? I don’t think it is) window on each screen with buttons to recreate the windows, but didn’t get around to it.

The screen does get notified if a user clicks on it. It becomes the active gadget (making the current active gadget and its children inactive), gains focus (firing a focus event, simultaneously blurring the formerly-focused window/gadget and causing it to fire a blur event), and processes the click (firing a click event).

Secretly, it’s even possible to add buttons, textboxes, checkboxes, etc, to the screen, but this isn’t supported in the API (there are no “screen->addCheckBox()” functions, and the gadget list is private) and it may have unusual effects. The screen is kind of set up to cater for this eventuality (windows won’t swap depths with anything that isn’t a window, which is good, but buttons could be added to the stack above windows, which is bad), but I haven’t tested it and won’t support it. The “approved” way to achieve this would be to make a borderless window the size of the screen, and add gadgets to it. And I think I’ve gone off on a digression here…

Jeff on 2007-10-31 at 02:24 said:

I was thinking more of a popup menu that appeared when you clicked in the desktop - a way to re-open those hidden windows (for the demo) and a way to avoid having a dedicated menu bar (for my vapor-ware organiser for my wife)

(DSuite’s icons seem a bit clumsy in that clicking on them moves them around - its a real untidy solution, to my eye)

One other thing to do with making “real” apps - how difficult is it to have a ‘swap physical screens’ api - ie, as it stands, something like an address book can need so much space that it won’t fit on the touch-screen, but for the most part, you could have a list on one screen, and the full detail card on the other. Touching an “edit” gadget can flip the detail card onto the current screen for editing by just flipping screens, rather than needing to switch the ownership of the windows around.

Hmmm, that was all off the top of my head, and I’m not sure an address book justifies the complexity. But I hope you get my drift. I want to use Woopsi to build a organiser which I expect to download XML content from a home server, including transient things like shopping lists, and let my wife tick off boxes as she goes. Not much text editing needs to happen but ticking off checkboxes. (However, editing the content of todo texts will be a big win and shift her off her dependence on the iPod)

Obviously, you can have the list on the non-touch screen and just use keyboard focus shifting - hmmm, shifting around using the cross-button seems like something that Woopsie might need to do for me, if it has resized the controls I asked it to display. I might not know which control is to the left of which, etc…

Hard to say. One thing I can say is that I hate DSOrganise’s approach which requires that you to tab from field to field, you can’t tap on a field to shift the focus there.

ant on 2007-10-31 at 08:30 said:

I thought about using the top screen, bit decided against it. Would be useful to have, though, especially for text editing, so why not put it in? I’d add an extra button to the screen’s title bar that looked a bit like the DS logo, clicking which would swap the bottom screen to the top and vice versa. I’d only support having one screen in the top physical screen, because otherwise there would be no way to switch between them.

Actually implementing this would be complicated. I’ll have to go through the code and work out what changes are needed - there are complexities like the rect splitting code and depth swapping that will get screwed up by having gadgets on the top screen.

Using the pad to cycle through gadgets would be fairly easy, but there’s currently no focus indicator on the gadgets. It could be either a caret or a different fill colour - any thoughts?

ant on 2007-10-31 at 10:00 said:

Wait a minute, what am I talking about? If I get the screen swapper to increase the current screen’s Y co-ordinate to (y + 512), I can get the draw functions to recognise that any Y value of 512 or greater should be drawn at (512 - y) on the top screen. It needs to be 512, because the screen dragging means that the maximum Y co-ordinate on the bottom screen is 511.

It will mean some tinkering with the rect splitting routines (they currently ignore any rectangles outside the co-ordinates of the bottom screen), but it’ll be less work than adding a _screen value to every object and trying to keep them all in sync. The only remaining problem is screen depth switching, but I can just make that ignore any screens with Y co-ordinates greater than 511.

Thinking some more about d-pad control, I’m not sure that this will work. It breaks the system’s conventions for screen A to be active, but for Woopsi to send d-pad events to screen B. The way around this is for screen B’s event handler to receive pad events and send them to the correct screen/window/gadget on screen A.

In the case of the organiser application, the top screen would contain a window with a list box gadget, and the bottom screen would contain the editor window. Pushing the “down” button (for example) would fire a keypress event in the editor window, which is received by that window’s event handler. The event handler sends the keypress to the list box gadget in the top screen, which (depending on how I write it) either automatically chooses the next item in the list, or fires another event so that another event handler can choose the next item in the list.

The only problem I can see with this scenario is keyboard input. It makes sense for the keyboard requester to automatically swap the bottom screen to the top when it opens, so that the whole of the bottom screen is dedicated to the keyboard. This would make text input easier. Closing the keyboard would automatically restore the original screen configuration. This screws up the idea of having the list in the top window, though, so I’d have to just make the keyboard a standard window. It’d be a lot smaller in that case, so that other windows could still be visible.

Jeff on 2007-10-31 at 10:14 said:

So, if I understand this, your “Screen” class is something that supplies a 256x192 drawspace into which you can put “Window”s which contain “Gadget”s.

Rather than swap “Screen”s onto different nds-screens, you are suggesting that a “Screen” be extended to think its big enough to cover the whole screen. In which case I don’t quite see what the problem is with focus - the 256x384 pixels would only represent ONE “Screen”. Yes, you can’t activate things on the top screen by touching them, but it makes no sense to have one focus on the top screen, and another on the bottom.

I have to admit, I was thinking of something a little different. If you envisage “Screens” as index cards, pushing the switch widget would have slid the top index card up onto the top screen, and logically pushed whatever was on the top screen down behind the stack of “Screens” on the bottom screen. I can see where this is probably prohibitively expensive in terms of VRAM, but it would have meant that you could have ‘n’ screens. It gets away from the issue that the control to switch screens is only on the bottom. I guess think “Rolodex”.

For keyboard input, you could have the front card slide half-way up revealing the keyboard, except I can see where the text input Gadget might scroll off-screen - that’d be bad. But why would you need to “restore the screen config” - surely it justs creates a Gadget at the top of the visibility hierarchy, processes events as normal then closes it again. All Gadgets, etc behind it should automatically update.

ant on 2007-10-31 at 11:08 said:

Right on the screen-as-workspace concept, but you’ve got my screen swapping solution interpreted wrongly. We’re actually saying the same thing. I’m not suggesting that I extend screens to be 384 pixels high; that would mean lots of changes, and from having done something similar with TextViewerDS, I know that it gets hugely complicated to try and treat two separate blocks of VRAM as a single block.

Screens are already a stack, and clicking on the depth gadget moves the current screen up the stack (or to the bottom if it’s already at the top of the stack). You can already have n screens (well, 256 actually, as it’s an unsigned byte - I might change this to an unsigned short at some point for more screens/gadgets/etc). VRAM isn’t a problem at all, as everything in Woopsi, barring the SuperBitmap (which has its own buffer), is generated on the fly. The only VRAM used is the screen you can see. This is an improvement over the original Amiga system, which I think allocated a buffer for every screen opened. (You know that there are two screens in the demo, right? One with the TextViewer and radio buttons, and one with the Pong demo?)

The new swapper gadget would increase the screen’s Y co-ordinate by 512 when it is clicked, not it’s height. The drawing functions would then do this:

if y < 512 draw on bottom screen else draw on top screen end if

The drawing functions are founded on the idea that any gadgets below another gadget don’t get drawn. Increasing the Y value of a screen in order to move it to the top nds-screen ensures that nothing overlaps it, so it always gets drawn. Any gadgets within it report their Y positions as being (parent + offset from parent), so all of the gadgets within that screen also avoid being overlapped by anything on the bottom nds-screen. Screen height stays the same; we just get two visible screens at once simply by interpreting their Y co-ordinate differently in the draw functions.

The only difference between our ideas is that your top nds-screen moves to the back of the stack when it switches back to the lower nds-screen. My top nds-screen would move to the front of the bottom stack, and the screen previously at the front of that stack would move to the top nds-screen.

The more I think about it, the more the rolodex approach makes sense. However, if I went down that route I’d scrap the additional button and make the bottom screen in the stack automatically get drawn on the top nds-screen (if more than one screen is opened). That way, clicking a screen’s depth gadget would gradually raise it up the stack until it flipped nds-screens out of the way. That would be quite cool, actually.

ant on 2007-10-31 at 11:33 said:

Of course, the other way to do it would be to decrease the Y value to -512. That would allow me to have the screen visibly zip up to the top screen just by gradually decreasing the screen’s Y co-ordinate.

It would mean some changes to the screen dragging code, though - I’d need to move most of it into the “moveTo()” function (which needs to be done anyway), and I’d have to get the function to cope with the screen flip.

Jeff on 2007-10-31 at 22:57 said:

Just to make life difficult, one other aspect of the user interface I was considering was getting away from the ‘menu bar at the top’ approach. The natural fit seemed to me to be a row of buttons at the bottom of the bottom screen, which allowed the user to flip through to the card of their choice. ie,


_Addresses_/_Todo/_Calendar/_Shopping/

This gives you direct access to any of the underlying screens without having to ‘click,click,click’ the depth gadgets. Yes, there’s a limit on how many you can have, but think icons, not the text I’ve shown.

Now, I’m not saying it must work this way, far from it. But the ability to implement such a thing would be desirable. Two ways occurred to me, one of which was to just replicate the buttons onto all screens, and the other was to move the buttons to the front screen whenever the focus changed.

When you click the Addresses button, you get a list displayed ON THE BOTTOM SCREEN. You select the address of interest, and the TOP screen shows that persons details. Press an editbutton - the list moves to the top screen and the bottom screen becomes an input form. If you edit a text field, the bottom screen moves to the top and the keyboard appears at the bottom, with commit/cancel buttons. This would be modal and there needs to be a way to prevent the user from flipping screen depth at that point…

I’m still not sure on that whole ‘if you edit a text field’ thing, as to how you notify the system. Its almost ‘if you click inside it’ but that would mean the form flips back/forward from top to bottom as you ‘tab’ through the fields with your cross button. Probably better to say ‘tab into a field, press A to edit’ and ‘click directly to edit’ - thus you need to distinguish how the focus moved into the form.

(This is largely notes relaly, talking out loud to myself, as to how to implement a form - its not something that Woopsi needs to implement but its probably worth considering in terms of expected library-usage)

Jeff on 2007-10-31 at 23:01 said:

I’m sure I just types two fairly long comments in here, but they appear to have gone in the bit bucket - I’m typing this one just to see if there’s a problem

Jeff on 2007-10-31 at 23:03 said:

Ok, that one worked - I guess I type it again.

One more thing to think about that would be desirable, from my perspective, would be to ensure that the screen-flipping was something that was under program control. That way, I could implement a control that looked like this:


\ICON1/\ICON2/\ICON3/

at the bottom of the screen and have immediate access to any specific screen without having to flip through them. Rolodex again, and closer to the way I expect to use it.

Jeff on 2007-10-31 at 23:07 said:

The one thing the stack of screens doesn’t necessarily fit is the address book UI I was expecting to attempt. On the bottom screen, I show a list of addresses (or a list of to do entries, etc). If you select one of them, the top screen would change to show more detail for that entry. If you then press A, the two screens switch places (putting the list on the dead screen, and the detail on the touch screen).

If you directly click, or tab-into-and-press-A, a text field, a keyboard window gets displayed on the touch screen, with commit/cancel buttons. This needs to be modal in that you can’t change screen depth or switch to another window while its active.

The ‘tab-into’ is my way of saying ‘manouever around the forms on the field using the cross pad’

Jeff on 2007-10-31 at 23:11 said:

(Sorry about the seperate posts here, but I don’t want to type another essay and lose it all)

As to whether the detail form should have moved to the top screen when the keyboard was shown, or just overlaid it, I’m not sure - I think you’d need to see it in operation to decide whether it was desirable or not.

The ‘screens switch places’ involves messing with the ‘order’ of the screens which may be more complex than Woopsi wants to support. The alternative to that was to have the stack of screens be:

[read-only details] [list of addresses] [read-write details]

And never let the user directly select the read-only or read-write details screens, only the list. That way, you can just move up/down by one and the system shows

[read-only] [list]

or

[list] [readwrite]

This mandates that the keyboard be a window that comes up over the [readwrite] screen rather than a screen of its own, I think. That doesn’t sound too bad, though it does obscure the context that the data is being entered into

Jeff on 2007-10-31 at 23:19 said:

Of course, I suspect that the whole thing could be done without needing screen-flipping - my icons at the bottom of the screen (which would preferably not be inside a window, would just flip around the visibility of windows which would be pre-existing.

What are the chances of making a Window change owning Screen? ie, I could have a ‘list of addresses’ window and when I press my edit button, it changed ownership from bottom Screen to top Screen - that achieves the same flipping around, without the complicated ‘fixed screen stack order’.

Of course, having the entire screen move up “looks cooler”, windows jumping from screen to screen would probably look distasteful unless they completely filled the screen - too much jumping around.

ant on 2007-10-31 at 23:53 said:

I’ve got screen flipping working. Wasn’t too tricky, really, as I just had to edit a few of the drawing functions in the Gadget class, overload the TextWriter::drawString() function, and tinker with the SuperBitmap and BitmapButton classes.

I tried implementing the rolodex screen depth switching idea, and although it’s quite a nice effect I won’t use it as the default screen depth swapping mode. Like you say, it’s better to have it under program control.

To that end, I’ve added two new functions to the Screen class - “flipToTopScreen()” and “flipToBottomScreen()”. They will move a screen to a different physical screen and redraw it. The flipped screen behaves just like any other screen, except that if it’s on the top physical screen it can’t be clicked (natch).

I need to add “raise()”, “lower()”, “raiseToTop()” and “lowerToBottom()” functions to the base Gadget class to alter a gadget’s depth (and thus a screen’s depth). I might change the “swapDepth()” function so that a gadget pointer can be passed in its parameter, so that the two gadgets are depth-swapped. All of the new functions should probably indicate their success or failure by returning a bool. Maybe “isTopGadget()” and “isBottomGadget()” functions too. I don’t want to expose the internal gadget vector unless absolutely necessary.

I think that covers everything needed to implement a rolodex-style rotation system.

ant on 2007-10-31 at 23:54 said:

Oh, two things that don’t work on the top screen are screen dragging and TextViewer scrolling. The latter is broken anyway, so I can get that working. Screen dragging will never be an issue, for obvious reasons…

ant on 2007-11-01 at 00:03 said:

Regarding buttons not in a window - the best way to achieve this is to put the gadgets in a borderless window. A borderless window is basically nothing but a container gadget - it doesn’t really do anything but hold other gadgets. You could, however, add a “addMyGadget()” function to the screen and hope for the best.

At the moment, screens don’t gradually move up, they just jump. I might look at screen movement when it’s not midnight (ahem).

I considered looking at being able to swap gadget parents (swapping windows between screens). In the end, I decided not to bother, as I couldn’t think of a practical use for it. Admittedly I didn’t think very hard, and it should be an easy change to make.

EDIT:

Latest source and ROMs in SVN if you want a look.

EDIT EDIT:

Oops, forgot to include one of the files in SVN. I really need to install Ankh, it’d make things easier.

ant on 2007-11-01 at 00:48 said:

I found your comment - I switched on a new adblocker earlier because I’m now getting two dozen spam comments a day (Jennifer really wants me to buy some dodgy pharmaceuticals) , and it looks to be a little overzealous.

Definitely sounds like you need more control than my original idea would have given you. Sounds like you need “disableDepthSwap()” and “disableDrag()” functions too (or something similar, I need to think about it some more).

Jeff on 2007-11-01 at 03:41 said:

I downloaded the .nds and its looking quite impressive. I’m actually at work at the moment so I haven’t time to look at the sources, but the actual demo is looking very good indeed.

A couple of things.

When I ran the demo using version 0.3.6.0 of NDesMume, scrolling the “Text” window all the way to the bottom locked the whole thing up. Doing the same thing on a real DSlite (yes, its at work with me) works fine. Odd.

When I pressed the “A” button, the superbitmap on the top screen seemed to grow but then wraps around the screen at the left, and then as it grows off the bottom back onto the top. Pong kept drawing its bat all the while but none of the other top windows were visible any more

I can’t see the widget that lets me switch the two screens, but I guess you didn’t check that in yet.

I didn’t realise that you could grab a “Screen” at the top and drag it down - I should have realised that that would be there, because thats how AmigaOS did things, didn’t it? I wonder if perhaps clicking in a back Screen bar should bring it forward? For me it didn’t, I had to press the z-order icon which seemed a little counter-intuitive. Pressing it on the frontmost screen pushes back.

Or am I confused and there are two screens back there called “Another Screen” and I’m not keeping track of which is which? That seems more likely.

ant on 2007-11-01 at 07:41 said:

DeSMuME is currently at version 0.7.2 for Windows and 0.7.3 for OSX - it got picked up by a different developer and moved to a new site. You’ve probably found a bug in the old emulator. The A button currently resized the SuperBitmap demo window - that’s a debugging test that I left in. The resized code should check against screen dimensions, though.

Hmm, screen depth sorting that works like the windows? Interesting, might do that. The depth gadget on the screen currently raises the screen up one place in the stack or wraps it to the bottom if it’s at the top already.

There isn’t a gadget to flip screens yet, still need to do that.

Another thing I need to do is fix the window title bar text ok the top screen - it’s currently not being drawn.

Jeff on 2007-11-01 at 08:34 said:

Yes, the text scrolling seems to work in desmume on osx so a bug in the windows emulator is quite likely.

The thing about the screen depth gadget is that it seems unpredictable as to whether it will raise the screen or not. If you drag one screen down, the click the depth gadget on the back screen, its not consistent in changing the screen order. It almost seemed to me like it assumes “if it can be pressed, this screen must be frontmost” which is definitely not the case.

To make sure we’re on the same page, pressing the depth button sends a screen back, not brings it forward, correct? Thats how it works with the Windows, so it seems intuitive that it should be the same with screens (which is why I think clicking the “screen title” should do the same depth switch as clicking a “window title”)

Here is a session (in DesMume), typed as I click: 1. starts up with “Woopsi Demo Screen” visible 2. click Woops Demo Screen depth gadget. 3. Another screen comes forward 4. click Another Screen depth gadget 5. visually clicks, but no change to depth (unexpected) 6. click Another Screen depth gadget 7. Woopsi Demo Screen comes forward (expected)

Starting from that point. 8. drag Woopsi Demo Screen down 9. Another Screen visible at top A. click Another screen depth gadget B. visually clicks, no change to depth (expected) C. click it again D. Woopsi Screen goes back (unexpected)

Jeff on 2007-11-01 at 08:51 said:

  1. should be “Another Screen visible at top of the lower-screen.” Its at the back still.

(just clearing up the ambiguity)

Jeff on 2007-11-01 at 09:38 said:

I noticed that when I started up the Woopsi demo after having restarted my R4 from inside a game (rather than a reboot), it locked up. I suspected that there might be memory-initialisation problems but couldn’t be more specific.

However, I’ve just come to try coding a real application and I noticed that the superbitmap constructors are inconsistent in setting the _usingBitmapPointer flag.

It might be worth adding a custom new operator at the base of your Gadget class which uses calloc() to clear all object memory. ie,

Gadget(s16 x, s16 y, u16 width, u16 height, Gadget* parent = NULL);
virtual ~Gadget();

void *operator new(size_t sz) { return calloc(1,sz); };
void operator delete(void *p) { free(p);  };

That should ensure that any instance variables in your classes are initialised to zero. On the other hand, the truly masochistic can use

void *operator new(size_t sz) {
    void *r = calloc(1,sz);
    if (r) memset(r,0xFD,sz);
    return (r);
}

which forces all your member variables to non-zero, which tends to kill you if you missed out on any class-specific initialisation (in your constructors). Since the DS doesn’t have a fantastic debugger, I would tend to init to zero and accept the laziness it implies.

Jeff on 2007-11-01 at 09:59 said:

I’ve been paring stuff back out of the demo to get to the point where I can start adding my own stuff - its always easier to modify something that works, rather than start from scratch.

I learned the hardway that Window::setBorderless() doesn’t do what you think it does - if you created the window with a border, the border size is taken into account. If you then call setBorderless(true), it still draws the frame around the outside of the window.

Jeff on 2007-11-01 at 10:05 said:

Given that memory is at a premium, its a bit of a waste using bool variables for every flag. Consider:

    struct {
            int _isClicked : 1;
            int _isActive : 1;
            int _isDragging : 1;
            int _isDeleted : 1;
            int _isBorderless : 1;
            int _isDraggable : 1;
            int _isVisible : 1;
            int _isCloseable : 1;
            int _isEnabled : 1;
    };

which should use one bit per flag. Thats 9 bools (probably 9 ints) compacted down to one.

ant on 2007-11-01 at 10:09 said:

The SuperBitmap constructors are intentiionally inconsistent. The gadget can work in two ways - it can be passed a bitmap pointer created externally (in which case, _usingBitmapPointer is true) or it can create its own bitmap in RAM. In the latter case, the constructor calls the “initBitmap()” function which sets _usingBitmapPointer to false and allocates the bitmap RAM.

I do this so that I can either create an external bitmap (via all_gfx, for example) and pass that in, or get the SuperBitmap gadget to create its own canvas bitmap. If I’m passing in an external bitmap I don’t want the gadget to try to delete it (since all_gfx data is const), but if I’ve allocated my own memory I need it deleted in the destructor.

The commented-out “setBitmap()” function was supposed to allow developers to change the bitmap if they wanted to after construction, but for some reason it kept crashing.

You’re right about the depth gadget - when I implemented the rolodex swapping I fixed the problem, but when I stripped it out again I removed the fix. The problem is that the top screen is in the same stack as the bottom screen, so when you click the depth gadget and nothing happens, the woopsi-screen in the bottom nds-screen is actually being depth-swapped with the woopsi-screen in the top nds-screen. The fix is easy - just get the depth swap routine to check which nds-screen a woopsi-screen is in before swapping with it.

You’re right about the depth swapping in general, too - clicking anywhere on a screen should raise it to the top of the stack, and the depth gadget should move it back to the bottom. It’s best to keep things consistent.

ant on 2007-11-01 at 10:21 said:

Yeah, setBorderless() on windows just inherits from setBorderless() on the Gadget class. The idea was that you’d only ever set it in the constructor - that was the way Amiga windows worked. However, it should be possible to override the base method and get it to remove the borders. One problem with this is that removing the borders will cause all of the gadgets to shift up and left to fill in the space vacated.

The struct idea is a good plan. I haven’t seen a struct initialised like that before.

Jeff on 2007-11-01 at 10:52 said:

Being completely petty (or anal retentive or whatever), I’ve been told that

Gadget::Gadget(s16 x, s16 y, u16 width, u16 height, Gadget* parent) { _x = x; _y = y; _width = width; _height = height; _parent = parent;

    init();

}

is better written as:

Gadget::Gadget(s16 x, s16 y, u16 width, u16 height, Gadget* parent) : _x(x), _y(y), _width(width), _height(height), _parent(parent) { init(); }

I think the argument is to do with guaranteed population order of the members before any throwing, etc which might happen inside your constructor. On the other hand, it allows the member variables to be defined ‘const’ if they logically need to be.

Just a style thing more than anything else…

Jeff on 2007-11-01 at 11:02 said:

I hope I didn’t confuse you - the struct isn’t being ‘initialised’ to 1’s You are telling the compiler “this is an integer, but only use ‘1’ bit”

Apple used to do things like:

union { struct { int flags : 8; int address : 24; }; void *ptr; };

which gives you a 32-bit value ptr whose top byte can be manipulated as though it were an independent variable called flags and whose bottom 3 bytes were an independent variable called address - attempting to store more than 24 bits into address would automatically get masked correctly, BY THE COMPILER rather than requiring you to code it.

This is actually a fairly old C construct thats been in the language forever, but gets ignored these days because few people worry about dealing with hardware - PALib could be using bitfield definitions like this to give real variable names to each of the bits in the NDS control registers rather than using or operations.

Similiarly NDS colors could be defined as

typedef { int alpha : 1; unsigned int green : 5; unsigned int blue : 5; unsigned int red : 5; } nds_color;

but I’m not sure that there’s that much value in that since so few applications actually want to mess about with the internals of a color.

ant on 2007-11-01 at 11:03 said:

RE: constructors, another thing I didn’t know. Heh, can you tell I’ve only really been using C++ for the last 6 months? So how would that be written if you’re inheriting from another class and need to call its constructor?

Jeff on 2007-11-01 at 11:06 said:

As to ‘borderless’ windows, there’s no need to override the base class - I just needed to pass the correct arguments to addWindow() and it worked fine.

Having changed tack completely (on my first attempt at an application), I think it might be useful if the input title to a screen were NULL, that it did not create a title bar, and that it only show the depth gadget if it knew that there was more than one screen. That might be a bit tricky to coordinate, but the presence of that gadget seems cosmetically annoying in trivial applications that don’t need screen-flipping.

(ie, the app I’m working on wants a completely blank bottom screen which contains a number of buttons directly on a borderless background)

Jeff on 2007-11-01 at 11:16 said:

Re: constructors.

Exactly as you’d expect.

MyGadget(int x, int y, foo *f) : Gadget(x, y), _foo(f) { … }

One gotcha with some C++ compilers is that they insist that the order of initialisers must be the same as the order of their declaration in the class definition. I think its to do with ensuring that your class is initialised in a rational order. In fact, I’ve seen some people who do

MyClass() :someptr(NULL) { someptr = new Ptr(); }

This ensures that the someptr variable is initialised to NULL if the objects gets anywhere near initialised (and thus likely to have its destructor called). In this trivial example, its not such a big deal. The subtlety kicks in if you have a deep class hierarchy. Consider:

class A { A() { … }; } class B : A { B() : A() { … }; } class C : B { C() : B() { … }; }

Now, when you create a C (via new C()), the compiler will first call A::A() to initialise its A’ness, then B::B() to initialise its B’ness, then call C::C() to initialise its C’ness. Consider what happens if the A and B parts function correctly, but something goes wrong inside the C constructor and an exception is thrown. In that case, will the compiler call A::~A() and B::~B() - ie, the destructors? Since the constructors for those classes were successfully called, I believe the standard says yes.

Having said all that, I believe that the initialiser stuff (the _foo(f)) is all executed before the constructor, thus giving a more stable object to work with.

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

Don’t get too fussed about the style stuff - its a bit subjective, and unless you start doing things like throwing out of constructors (which is pretty much considered a bad thing these days), its relatively unlikely to cause problems anyway.

Jeff on 2007-11-01 at 11:22 said:

Is there a reason for this?

// Copy bitmap to gadget void SuperBitmap::draw() { Gadget::draw(); }

No need to overload the member if you are just going to call your superclass..

Jeff on 2007-11-01 at 11:25 said:

Hmmm, just noticed that none of the SuperBitmap routines are virtual? Or Checkbox? Or RadioButton? I won’t check them all, I’ll just assume that none of the Gadgets allow themselves to be subclassed. Any reason for that?

ant on 2007-11-01 at 11:27 said:

I found that in trying to call draw() in a subclass, the compiler complained that it couldn’t find SubClass::draw() if I’d already overloaded SubClass::draw(Rect clipRect). In order to save myself the hassle of typing Gadget::draw() every time I needed it, I just added the extra overload.

Speaking of the clipRect thing, I’ve just read that it’s better (faster and more efficient) to pass structs around as pointers, so I might switch that around at some point.

Regarding subclass function virtualisation - heh, my mistake.

ant on 2007-11-01 at 11:35 said:

Incidentally, if you’ve got a SourceForge ID I think I can give you access to the bug tracker…

Jeff on 2007-11-01 at 11:45 said:

Getting to the end of my night - I hit one blocker too many so I’m going to be stopping real soon.

As it stands, you can only add gadgets that Woopsi has pre-canned because you’ve implemented Window::addXXX() methods. Whilst I understand the desire to not expose the _gadgets[] array, it means that I was not able to create a SuperBitmap and then call addBitmapButton() against it - because its not a Window. Is this a concious choice? I had coded up

bottomScreen = mywoopsi.addScreen(..) bottomBackground = bottomScreen.addSuperBitmap(…) goButton = bottomBackground.addBitmapButton(…)

and of course that didn’t compile. I suspect that I’m supposed to have a Window in there somewhere, between Screen and SuperBitmap, and add the button to the window, then rely on the z-ordering of the gadgets in the window to draw the button on top of the background.

Anyway, as it looks, I feel like I’ve achieved more tonight than I have done in previous weeks - you should be proud of the quality of what you have produced, the ease that it gives the programmer is brilliant.

(And the docs are in English, not French!!!)

ant on 2007-11-01 at 11:51 said:

Oh, and for borderless screens - I think that’s in the screen constructor, the same as windows.

Jeff on 2007-11-01 at 11:52 said:

No sourceforge id though it sounds like it might be a good idea to organise - less off-topic comments…

oh, and think your spamfilter just ate another message. anyway, time for bed at this end.

ant on 2007-11-01 at 16:01 said:

Ahh, yes - preventing sub-gadgets being added to SuperBitmaps is a conscious decision. If you’re trying to do what I think you’re trying to do (a full-screen background image with a button on top), you should:

bottomScreen = mywoopsi.addScreen(…) bottomWindow = mywoopsi.addWindow(…) bottomBackground = bottomWindow.addSuperBitmap(…) goButton = bottomWindow.addBitmapButton(…)

The system has an implicit concept of “container” gadgets - gadgets that can contain other gadgets. Strictly speaking, this is an artificial limitation, since the recursive design of the gadget class means that any gadget can contain any other gadget. However, gadgets do have certain expectations about their parents to be able to achieve some of their functionality. Screens don’t expect to be owned by anything other than a Woopsi instance, and windows don’t expect to be owned by anything other than a screen. Similarly, screens don’t expect to suddenly encounter a button in their gadget lists.

Woopsi, screens, windows and radio button groups are all examples of gadgets that can contain other gadgets. Textboxes, buttons, superbitmaps and radio buttons are treated as “leaf nodes” in the gadget tree - they are the end-points of a particular branch and can’t contain any further gadgets.

There are two main reasons for this. First of all, it makes things slightly easier to code - if I know that a window is always owned by a screen, I can cast its parent to a Screen object without having to check its _type property first. Secondly, it allows the system to enforce the gadget hierarchy and retain some kind of semantic model to the GUI that the developer puts together. A button shouldn’t be attached to a screen or superbitmap, and the “addButton()”-style child creation system prevents developers from going down these routes.

You’re right about the z-ordering - as long as you add your gadgets in the right order, from bottom to top, your superbitmap will be the lowest gadget on the window, and the button will appear superimposed over the top. The proposed “raise()”, “lower()”, etc, functions will allow the developer to juggle the order of gadgets as they wish.

If you want a SourceForge ID, just nip over to sourceforge.net and sign up - registration is free, they don’t demand much in the way of personal details (just a name and an email address, I think), and I should be able to work out how to give you access to the bug tracker section.

I was hoping that someone would have a go at creating an interface with Woopsi. Apart from the obvious fact that someone using your work in some way validates the time you’ve spent on it, usage scenarios are always difficult to predict. Having feedback from someone who’s trying to use the library and encountering problems lets me tweak it as I go along. Of course, I couldn’t ask any developers to do this - if it turns out that there is something horribly wrong somewhere (perhaps the event system isn’t as simple to implement as I think, for example), it might be the case that I need to make major changes that break all of the work they’ve put in. Having a volunteer guinea pig solves the problem, so many thanks for that!

Jeff on 2007-11-01 at 19:42 said:

I figured out the need for a Window in the mix and what you suggested worked just fine. I’m still of two minds as to whether Screen should be a subclass of Window (intellectually, of course - you own the architecture, not me). The similiarities of Screen and Window depth, the fact that both can be containers (though with limits on what they can contain), etc strongly suggests they should be more closely related. But thats gut feel, not an established fact.

In my other windowing system, I have made exactly that distinction (that Windows were distinct from Gadgets) and it caused no end of problems when I tried to implement “SubWindows” which might contain “Sub-Sub-Windows”, etc. The primary problem in my space is that I don’t specify Gadget sizes at all - instead, I say the equivalent of:

Window *w; Gadget *g = CheckBox::Create(“Label”); w->AddGadget(g);

The Window asks the checkbox “how much space to do you need”.

The Checkbox works out what its minimum size needs to be based on the known metrics of its “glyph” and the text size of label.

The Window allocates space from its remaining rectangle, and gives that to the checkbox - it may give more than the Checkbox asks for, and expects that the CheckBox will draw itself appropriately. This latter sounds odd, but when you have TextEntry fields, the field has a max number of characters, but if you actually gave it the 32000 pixels it asked for, the screen would be full. So, the Window hands it 200 and its the TextEntry fields problem to fit in that space, adding auto-scrolling or whatever it takes.

The problem comes when the windows does not provide enough space - the gadget has to say “not enough room” and the window has to rethink its layout algorithm, or add in some scroll-bars of its own, or some such. Again, it seems odd that a window might not provide enough space, but this is what happens if you have a subwindow - (like a split-pane that lets you resize the allocation between two big text-entry widgets)

The problem is that my subwindows are schizophrenic - on the one hand, they are gadgets and have to try to fit into their container - on the other hand, they believe they are windows and can dictate the size of their content. This is a problem because to determine what their size is, they need to ask their content what its minimum size would be.

Thats a long almost-unrelated story - the only relationship to Woopsi which you really can’t see, is that the message that we used to communicate things like ‘I’m too big to fit’, etc are sent through two distinct funnels. A subwindow ended up being a composite object (ie, it inherits from both Window and Gadget) and that complicated a bunch of other stuff quite badly.

The place I can see a similiar problem causing Woopsi grief would be my list Gadget - I really think I want to display scroll bars when there is content off-screen. Some sort of indicator that you can’t see it all - the TextViewer Gadget caught me out this way when I first saw it.

The natural fit seemed to be to subclass SuperBitmap and have it draw each list row with the drawLine, etc routines. But rather than having to “draw” the scroll-bar, I was just going to have a sub-Gadget.

I think this is all academic - Woopsi’s design seems to imply that my List Gadget should own the SuperBitmap and the ScrollBar, rather than be a subclass of SuperBitmap - thats fine, its just not the way I was thinking. I suspect that if SuperBitmap were not a Gadget, it would have made more sense to me.

One other thing concerns me - the use of _type. Typically in C++ If you every find yourself asking the question “what class is this object”, you haven’t factored it properly. You should be invoking a method on the object “doYouDoXXX()” instead, because you should be asking about capabilities of the object, not its pedigree. Over in Objective-C they really have that stuff nailed down nicely - any object “may” respond to any message regardless of what their class is. The only uses of getType() I can see so far are in Screen::setActiveWidget() where it looks through its own array - which should only contain ‘windows’ and what I’ll call ‘decorations’.

Surely just having two arrays completely obviates the need for the check - you should only ever get windows in the _windows[] array and you put screen titles, menubars, depth gadgets etc into the _gadgets[] array. Since that complicates your logic, you could also just have areYouAWindow() which regular gadgets would reply “no”. Whilst this seems to be a violation of Gadget’s nice vanilla API, the reality is that you have implied this secretive behaviour in the implementation - its better that its exposed cleanly in the interface.

In Screen::Click() you special-case out the click in the screen title and yet this is a natural fit for just passing the click to the title gadget and having it notify its enclosing Gadget in the standard way. No need for a special case.

I’ll see what I can organize about the sourceforge access…

ant on 2007-11-02 at 10:03 said:

Splitting the “decoration” gadgets (that are really a part of the current gadget) and the child gadgets seems like both a good idea and bad idea. Rather than a “_windows” vector I’d have “_decorations” and “_gadgets”. Things like the borders, titles and depth gadgets would go into the _decorations list, and everything else would stay in the _gadgets list.

There are complications. The rectangle splitting functions will need to be passed a pointer to the correct vector to process, but that shouldn’t be too difficult to implement. The click/release/etc functions will need to check against two vectors - first the _gadget list (which will always be higher up the z-order) and then the _decoration list.

On the other hand, it does create a logical split between components and children, which is very useful when handling depth swapping.

It would make things tidier in some ways and messier in others. I’d have to look through the code to try and gauge the impact before implementing it.

This did give me another good idea, though - if I have a _hidden vector, I can move gadgets out of _gadgets into it when they are hidden and into _hidden, then move them back when they are shown. This will make the need to constantly check for _isVisible unnecessary. Also, there’s no need for me to retain deleted gadgets in the _gadget vector once they’ve been deleted, since I already have a pointer to them in the _deleteQueue. That’ll make things easier, too.

One reservation I have about automatically-resizing gadgets in Woopsi is the DS’ touchscreen - it really isn’t accurate at all, and if Woopsi can resize its buttons down to the size of a glyph (ie. half the size they are now), it’ll be completely unusable. The resize routines (if I decided to implement them) could be made more intelligent, but it’s probable that leaving GUI layouts up to the developer would result in more usable interfaces.

The listbox gadget is on the to-do list. The major problem with abusing the SuperBitmap to create other gadgets is the memory requirements. If you create a list of 200 items, each 20 characters wide with an 8x10 font, assuming 2px padding between items, you’ll need a bitmap of 160x2400. That’s 750K wiped out in one list box. When you’ve only got 4MB of RAM to play with, that isn’t efficient enough. The route I’m going down is to buffer listbox items in a vector, and follow the TextViewer method to draw it - use the DMA hardware to blockscroll and fill in the top/bottom rows with the TextWriter. Total bitmap memory: 0K.

Jeff on 2007-11-02 at 21:31 said:

I’m not suggesting you need to do the auto-“resize”. That was really just trying to illustrate the point that I ended up with windows having to ask both up and down the containment hierarchy in an attempt and ending up locking themselves up. The key thing about the NDS screen is that its so small that its unlikely to have complex enough screens to layout. Instead, the screen-flipping is a better way to solve the ‘lots of data onscreen’ problem.

It might be nice to be able to tell a checkbox that it has a size of zero and have it ignore that and compute its needed size - ie auto “size”.

As to the list box, I wasn’t thinking of a listbox element having a bitmap that encompassed the list. Instead, it covered exactly the screen, and drew into it the elements I needed, managing the scrolling as you suggest. However, rather than limiting myself to Text, I was expecting to have the ListBox then be subclassed to do the drawing of each row - the ListBox maintains a vector _content which the outer application can put whatever data structure it likes into. The ListBox manages the ‘state’ and the ‘scrolling’ and merely re-calls itself to draw each row, passing the state (selected/not) and the content _content[rownum].

That lets you draw list elements in columns, draw checkboxes, icons, etc without the ListBox knowing or caring.

The default ListBox Gadget can assume that its _content[] array consists of null-terminated strings and provide appropriate draw routines - but it definitely needs to enclose that assumption into as few places as possible. This probably means being careful not to delete the content of the _content[] array.