Jump to content
IGNORED

New GUI for the Atari 8-bit


flashjazzcat

Recommended Posts

I'm considering adopting the MADS relocatable binary file format for the GUI, a description of which is here (extracted and translated from the MADS docs):

 

MADS Relocatable file format.txt

 

This follows my reading on the o65 relocatable file format. The reasoning behind my consideration of the MADS file format (MR) is simply based on the fact that the GUI is being developed using MADS. Enabling binaries to be relocatable will be useful for API plugins and drivers, etc, but may or may not be especially relevant to application programming (since apps may yet load at a fixed address). In any case, this is stuff we need to think about, and opinions are appreciated. I imagine some people will want to code for the GUI using CC65, but - as I say - the relocatable file format may be most relevant to drivers and OS extensions.

Edited by flashjazzcat
  • Like 1
Link to comment
Share on other sites

For broader adoption It's still missing full english manual - but that will be solved eventually...

Soon I hope - I'm effectively interpreting the MR file format specs from behind a thick linguistic veil... ;)

I'm half Polish so its little easier for me ;)

 

Did anyone try to contact Tebe and who ever else is behind Mads and offer help with english manual ?

Link to comment
Share on other sites

Did anyone try to contact Tebe and who ever else is behind Mads and offer help with english manual ?

Not sure. I haven't contacted him directly myself, but I have posted a lot of requests on the coding forum here and offered up my own translated version of the manual (done in chunks using Google). Many, many important aspects of MADS remain a mystery to me, and I feel like I may be missing a lot of important stuff.

Edited by flashjazzcat
Link to comment
Share on other sites

OK - finally got a proper event-pipe worked out and running in the VBL. All mouse clicks and keystrokes are collected in the interrupt and pushed into the FIFO queue. I decided to implement double-click and drag events at this level, so we get rather richer low-level events than simply MOUSEDOWN and MOUSEUP. Because of the internal double-click check, the polling code automatically pushes a following MOUSEUP event behind the initial MOUSEDOWN if the operation was not deemed to be a double-click, unless of course the mouse was just held down continuously. Scroll handles and drag / resize operations now watch for a MOUSEUP event rather than monitoring the mouse button directly, which seems much neater to me. It also means all click operations are now buffered.

 

I'm enjoying continued recourse to Dan Winslow's very helpful explanations while putting all this together. :)

 

Anyway, at the low level, we have this at the moment:

 

EVENT		.struct ; event pipe events (low-level, before system event handler processes them)
what 	.byte ; type of event (EventType)
message	.byte ; message (e.g. keycode for KEYDOWN)
x		.word ; coordinates (only for mouse events)
y		.byte ; low-level events don't use virtual coordinate system, so y is 8 bit
when		.long ; 24 bit elapsed ticks counter
.ends
;
.enum EventType ; low-level events
NULL
HOVER
MOUSEDOWN
MOUSEUP
DOUBLECLICK
DRAG
KEYDOWN
KEYUP
.ende

 

I'm not even sure the "when" field will get used at all - it was primarily included for interpreting double-clicks outside of the interrupt code, but that's no longer necessary (the interrupt - since it only gets a brief time slice every VBLANK - maintains a "state" value for the mouse buttons, so it knows what to look for next time around. It also maintains an internal timer for double-click delays, etc). Of course there are more things to add yet. Window scrolling and whatnot are naturally handled by the window manager, which takes care of any events which happen outside of the client area. All the application needs to do is redraw the window contents at the specified size and location when asked to do so.

 

The coordinate system for windows is going to be the next challenging step, along with the clipping required so that objects can be drawn partly visible. With luck, we should have a scrolling window full of icons by the end of the week. ;)

Edited by flashjazzcat
Link to comment
Share on other sites

Well, it's probably too late for that and I even doubt it's realistic at all, but I couldn't resist to ponder on draggable screens/full-width windows in different graphics modes (like The Pawn on Atari ST or the Amiga GUI) - e.g. for a picture viewer or even some paint software. Could be realized by DL modifications.

 

Thorsten

Link to comment
Share on other sites

Well, it's probably too late for that and I even doubt it's realistic at all, but I couldn't resist to ponder on draggable screens/full-width windows in different graphics modes (like The Pawn on Atari ST or the Amiga GUI) - e.g. for a picture viewer or even some paint software. Could be realized by DL modifications.

Sure, could be done, but we'd require mode-independent drawing routines, which would be slower. The resolution of GUI elements would also naturally take a disastrous hit in anything but hi-res mode. Draggable screens would be very cool, though, and could be very effectively implemented using the display list.

 

I'm having second thoughts already about the double-click check in the VBL, since a MOUSEDOWN event can not then be returned before a short delay while checking for double-clicks - pointless in many circumstances, and merely introduces an unnecessary pause before single-click operations.

Edited by flashjazzcat
Link to comment
Share on other sites

I pulled the double-click detection out of the VBL. The window manager now calls this routine to detect double-clicks and drags:

 

check_mouse_actions ; figure out if mouse was double-clicked or dragged
jsr get_time ; save time of mousedown event
jsr dbl_delay ; short delay, long enough for mouse to be released
jsr PullEvent
beq no_dbl_click
lda EventBlock[0].what
cmp #EventType.MOUSEUP
bne no_dbl_click ; we should really push the event back into the pipe
jsr sub_time ; subtract previously saved timestamp from time of this event
jsr check_dbl_delay ; see if elapsed time is short enough
bne no_dbl_click
jsr dbl_delay ; short delay, long enough for mouse button to be pressed again
jsr PullEvent
beq no_dbl_click
lda EventBlock[0].what
cmp #EventType.MOUSEDOWN
bne no_dbl_click
jsr sub_time
jsr check_dbl_delay
bne no_dbl_click
lda #EventType.DOUBLECLICK
sta EventBlock[0].what
rts
no_dbl_click ; no event, so check if mouse has moved
lda curr_mousex
cmp mousex
bne dodrag
lda curr_mousex+1
cmp mousex+1
bne dodrag
lda curr_mousey
cmp mousey
bne dodrag
rts
dodrag
lda #EventType.DRAG
sta EventBlock[0].what
rts

This means code which needs to act simply on MOUSEDOWN events isn't burdened with the double-click / drag checking delay.

Edited by flashjazzcat
Link to comment
Share on other sites

At long last I'm pushing on with the very arduous job of converting the existing source code to run on a banked cartridge. This is an interesting (if rather daunting) task in its own right, and one which opens up many exciting possibilities.

 

Further reading up on the MADS relocatable format has allowed me to formulate some rough ideas regarding symbols and the relocatable loader, etc. One thing I'm considering is devoting an entire 8KB cartridge bank to the relocatable loader and dynamic linker. The bulk of this bank will be populated with a symbol table, not unlike the SDX library. The root node of the linked list of symbols will originally point to the start of the ROM table, but as relocatable drivers and extensions are installed, new public symbols will be pushed to the head of the list in RAM. This will allow the mouse driver, for example, to define the sampling routine as public symbol which the interrupt code will then reference. In addition, if a developer chooses to code applications using MADS, it will be possible to use external symbolic references to GUI API calls, which will be fixed up at runtime.

 

I'll include a mechanism for non-relocatable apps (perhaps those written using another assembler or CC65, for example) to explicitly locate symbols, although we may still require some kind of jump table for those apps which can't easily take advantage of the relocatable loader and linker.

 

As far as resources go, it would be desirable to have an application which outputs object trees (menus, etc) in binary format, but until such an application is coded up, I think it's important to be able to hand-code dialogues and menu trees. So - for the moment at least - resources will be written using a plain-text meta language (either embedded in the application or loaded from disk) and converted at runtime.

 

Those applications which occupy more than 16KB of code space will need to "spill over" into additional extended banks, and in any case we need a means of defining code and data blocks. Hopefully the MADS relocatable format will be up to the task.

 

In addition, I'd like to use one or more banks of the cart as a ROMdisk, not unlike the CAR device in SpartaDOS X. It'll be accessed via a normal CIO handler, however, and will contatin the default desktop manager application together with the default configuration files. Disk based application files will supercede those on the cart, and the user may write his own replacement desktop manager if he wishes, hosting it on disk. We can also have default system fonts, etc, on the ROMdisk, and this means that they will be installed in a manner consistent with normal disk installation.

 

EDIT: would it be useful / sensible to provide support for dynamic linkage of CC65 .OBX files? I haven't yet looked into these in as much detail.

Edited by flashjazzcat
Link to comment
Share on other sites

For C apps, I was thinking of a C wrapper around a set of calls that would access some kind of lookup table to jump to routines, something like

 

int register_app( char *appname,unsigned int event_callback_address, parm1, parm2, etc. )
{
 //push parms however the GUI wants them if need be
 GUI_CALL( jmp_table[REGISTER_APP] );
}

 

where (in this case ) event_callback_address would be the routine that I want the GUI to call when it wants to send me an event. jmp_table would be an address table in your space that holds all the available GUI calls, which I would access via a bunch of integers, as in #define REGISTER_APP 32. So the above GUI_CALL would wind up doing whatever it needed to do to get things set up and the it would jsr to the address at location 32 in your jump table.

 

Other calls like

WINDOW_HANDLE handle;

handle=(WINDOW_HANDLE)create_window( blah blah blah )

realize_window( handle, ... )

 

would wind up all vectoring through the same jmp table to various routines. PLus you need to give me back handles to objects like windows, text, resources, etc, so exactly what a 'HANDLE" is in this situation needs to be dictated by you.

 

The upshot is that rather than actually late-binding through a dynamic linker, the C would just treat you as a giant externally callable library. I'll also need some kind of routine that jumps to you and never comes back...it would probably be the case that the only thing the the main() would do is

 

void my_callback( event somevent, eventdata *somedata )
{
 switch(event.kind)
 {
   case APP_REGISTERED:
   {
     //open windows, create stuff, etc.
   }
   ... other zillion or so cases here ...
  }
}

int main( void )
{
 if ( register_app("coolapp",my_callback, my_resource_file, blah blah) )
 {
 }else{
   //complain about not being able to start
   printf("wtf SUX\n");
 }
}

 

From C's perspective, it would never get back out of the register_app call until the app was terminated. Someone ( you or I ) needs to save the stack context in the app registration data such that when you decide you're done with me you can reset the stack and RTS to me, which will pop me out of the call to register_app. In between times, of course, you've made lots of calls to my_callback to give me time to execute things.

 

So..The GUI is running along. Then somebody clicks on my app in a disk window and says 'start' or whatever. Your loader looks at the executable file, loads it more or less like a DOS would, and you jump to it's start ( ie., main(), more or less ). The C env is then set up ( mostly just a call parms stack, which I can control ), and then it runs into the register_app call, which sends the thread back to you after saving off the current call stack. From your perspective, you have just returned from a jsr at the end of the start_that_app routine. Then you go off and about your business.

 

Hmm, I'll have to mess with the call stack a bit to send it back to you in the right way but I'm sure it's doable, did stuff very like this for a threading library.

 

P.S. I wouldn't mess with .OBX files if I were you. You've got enough complications.

Edited by danwinslow
Link to comment
Share on other sites

Looks good, Dan. I kind of figured a straighforward jump table would be most useful for C. It should be easy enough to provide support for standard $FFFF binary files and relocatable binaries which hook into the linker.

 

You're dead right about my having enough complications, by the way: Making a banked cartridge isn't so tough using the long JMP/JSR routines I wrote a few months back. Handing all the $4000-$7FFF bank switching when the object tree and resources are all in extended memory is a different matter. I can see a lot of rewrites coming up, since a lot of existing code simply wasn't geared up for this, since it blithely assumes everything has unique address space. This is certainly the nastiest stage of the whole project.

 

I was looking back today at some of what you wrote regarding memory management, and I'm very much pro-buffering (as well as the small unbuffered pool an app may chose to maintain in its own bank). Select Object / Get Object / Edit Object / Put Object seems to me far better than littering the code with bankswitching calls simply in order to ensure unbuffered access to all data structures.

Link to comment
Share on other sites

I think the worst is over. :) It looks like the most economical way to do things - as far as the GUI "executive" itself is concerned - is to have the object tree bank switched in the whole time and selectively switch in other banks as required. When setting up menu items and the like, its sufficient to temporarily buffer individual entries outside of the banked region to avoid relentless switching in and out of different banks. As far as applications which want to directly edit objects are concerned, I'll go down the fetch / edit / send object route, using a buffer outside of the banked region.

 

Another big change is that I've decided to make the objects a uniform size (probably sixteen bytes) and simply have a pointer to additional data on the heap. This makes memory allocation for objects themselves very straightforward: one can simply begin with a zeroed out 16KB block and allocate objects by looking for empty slots. We can reserve the complex heap management for the heap itself. I think 1,000 objects should be sufficient at any one time.

Link to comment
Share on other sites

I've recently started to delve into the programming language known as "Inform 7", once again, after setting it aside for a while.

 

It's intended purpose is to create interactive fiction, however, I've been experimenting with concepts to re-purpose it as a more general AI language, for specific things, like Expert Systems, because it is actually fairly well-suited for that sort of thing, if you look at it from that perspective. Inform 7 has an almost English-like syntax, making it a very easy language to get started with, and it includes extensive documentation.

 

I'm mentioning this because Inform 7 automatically generates visual hierarchical charts, known as "Skeins", from your code. While no one has used Inform 7 in this manner yet (for non Interactive Fiction purposes), you could easily use it as a expert system & simulator that generates visual trees, for menus, banking constructs, & such. It may be a good tool for you to use to aid in concept visualization.

 

You can download Inform 7 here.

Link to comment
Share on other sites

I've recently started to delve into the programming language known as "Inform 7", once again, after setting it aside for a while.

Thanks - downloading now. Sounds interesting indeed.

 

Not strictly related, but I was leafing through a copy of PCW User from 1992 the other night and it contained reviews of a couple of text handling applications: an outline processor and a hypertext system. These were both pretty cool things back in the day, and I recall there was some kind of outline processor for the A8, although I forget the name. I'm not sure if there was a hypertext system. With the Internet being ubiquitous now, reading about these hypertext systems provides much nostalgia.

 

Back to the GUI, and having studied the TOS 1.4 source code in greater detail (it makes interesting reading, by the way, and can be found here), I've finally been won over by the right-threaded binary tree. I'm devoting a single bank (i.e. 16KB) to a 1,024 element array of 16 byte objects. A threaded tree makes complete sense and is much easier to traverse without recursion.

 

The new object structure becomes roughly this:

 

OBJECT    	.struct
class	.byte	; object type (0-63, bits 0-5, bit 6 = selected flag, bit 7 = visible flag)
ob_head	.word	; pointer to first child
ob_tail	.word	; pointer to last child
ob_next	.word	; pointer to next sibling, or parent if no more siblings
ob_spec	.word	; pointer to additional data
x	  .word
y	  .word	; 16 bits for virtual coordinate system
width	.word
height	.byte
.ends

 

Previously, we had parent, child, prev and next pointers. The two bytes we've saved have been given over to the OB_SPEC pointer, which simply points to additional, supplementary object structures allocated from the stack in another 16KB bank. Many objects need no additional information, so we're looking at really fast object allocation and deallocation using an array.

 

To clarify, the threaded binary tree in this implementation is defined thus:

 

  • OB_HEAD points to a node's first child, or is NULL if the node has no children
  • OB_TAIL points to a node's last child, or is NULL if the node has no children
  • OB_NEXT points to the next sibling, or in the case of the last sibling, back to the parent

This is the exact data structure used by TOS, and is nicely explained here. I found most descriptions of threaded trees hard to follow, but Tim Oren's text, The Atari Compendium by Scott Sanders, and the TOS source code itself proved highly instructive.

 

There seems to be light at the end of the tunnel now, following a rather bleak day yesterday. The same long JMP/JSR mechanism used by the cart code will (in a modified form) be employed by applications which need to occupy more than a single 16KB bank. Any static resources in the application's banks will be back-referenced by supplementary object structures in the heap bank, which will keep track of which bank the resources reside in. The final problem is apps which need to use large chunks of RAM outside of their code and data blocks; these will be serviced by buffered access to entire 16KB banks, using API calls and the caching mechanism we discussed some time back. This mechanism leaves the door open for task switching or multi-tasking in the future, since applications can be instantly swapped by swapping 16KB banks (of course we'd also need to carefully swap out zero page registers and volatile buffers in the non-banked area). However, any kind of multi-tasking is far-off prospect: I merely wish to design the system so that the door is left open for it in the future.

Edited by flashjazzcat
Link to comment
Share on other sites

 

Back to the GUI, and having studied the TOS 1.4 source code in greater detail (it makes interesting reading, by the way, and can be found here), I've finally been won over by the right-threaded binary tree. I'm devoting a single bank (i.e. 16KB) to a 1,024 element array of 16 byte objects. A threaded tree makes complete sense and is much easier to traverse without recursion.

 

Just for the record, that's TOS 4.04 that you're looking at. It came out in 1992, and is pretty well known as Falcon TOS, because that's what it came with. Atari did eventually release MultiTOS for all 16/32 machines, but it was very buggy and needed a fairly powerful machine to run. TOS 1.04 itself came out around 1989, standard in the STacy, newer ST's, and as a ROM update for the other ST's.

 

Great work on the 8bit GUI concept - really looking forward to its full release. Thanks for all your hard work! :)

Link to comment
Share on other sites

Just for the record, that's TOS 4.04 that you're looking at. It came out in 1992, and is pretty well known as Falcon TOS, because that's what it came with.

Ah yes - sorry about that! I had just taken a quick look at the version number at the top of one of the source listings, but common sense should have reminded me that it was TOS 4.04. Thanks for the correction. :)

 

Presumably the basic underpinnings are the same as earlier TOS revisions, but the fact that this is Falcon TOS makes the source code even more interesting. I wonder if it includes stuff like live window dragging and contents scrolling in list boxes, etc.

 

One thing I'm still undecided about is rectangle lists. I mentioned before that I still think they are complete overkill for a GUI of this size, but on the other hand we still need some kind of smart screen redraw. I have a lot of ideas to do with buffering regions of the screen and using bitmasks. The big PITA is that the window frames and drop shadows are not on byte boundaries, so it's very difficult to clip regions outside of windows and keep the redraws looking neat. Double buffering the entire screen is an attractive option, but again a lot of wasted effort when it's only small parts of the screen which have changed. Suggestions are welcome.

Edited by flashjazzcat
Link to comment
Share on other sites

Thanks for the suggestions. ;)

 

The new object code is taking much longer than anticipated to debug: It's taken me all this time just to get the menu bar to render properly again. However, the rewards justify the hard work, since the threaded tree is much more versatile (and since all the objects are 16 bytes long, they're much easier to look at in a debugger). A single non-recursive tree walker now handles all messaging and figures out what object the mouse is over: one simply passes the walker the address of "routine" and a flag to tell it whether to execute it before branching to child nodes (rendering) or afterwards (checking for clicked objects). It's been absolute Hell breaking everything and then putting it together again, though...

Link to comment
Share on other sites

New back-end is now completed and fully debugged. The weird startup crashes which used to happen in the old demo have been eradicated. Although it may seem as if this project had gone off the boil for a while, I'm actually feeling very positive about the direction the revised code is taking and I hope we'll make some more dramatic leaps over the coming months. Everything (with the exception of the screen buffering, for which I have a few competing ideas) is absolutely clear in my mind now and it's just a case of laboriously coding things up according to the grand plan.

 

Prior to getting some scrolling content in the client areas of the windows, I've been working on a button control and this is the first incarnation:

 

post-21964-0-99460200-1314709450_thumb.png

 

MrFish and I have been discussing various ideas for the 3D "push" effect which I'm testing now, and we'll plump for the one which looks best. The GUI should be pretty easy to skin, since most components are described by a bitmap with left / middle / right or top / middle / bottom images, the middle sections being repeated to fill the size of the object.

 

I realize the button is rather oddly placed at the moment, but we have no dialogue object yet; the OK button is currently the only child of the demo window's client area. Once we have a dialogue box, I'll release a demo folks can click away at. ;)

 

In the meantime, it's back to SIDE...

Edited by flashjazzcat
Link to comment
Share on other sites

Here's a short video showing an actual new control (the first in months): clickable buttons:

 

http://www.youtube.com/watch?v=zWy9CgAbvEo

 

Please excuse the button design: this is one of several I'm playing with at the moment and - while probably the least good-looking - it's the best one for demonstrating the 1px 3D button shift. I've no idea if I'll keep this feature in, but it looks cool and has good novelty value. ;) The more balanced button design has even borders and the text doesn't shift 1px down and to the right.

 

What's actually happening in the video is that I'm clicking on the button, and then moving the mouse while keeping the button pressed. The click only registers if you release the mouse button with the pointer over the command button. This behaviour is precisely consistent with MS Windows.

 

For those interested, here's the initial click code for the button widget:

 

click_cmd_button ; click code for command button
jsr read_ob_spec
lda #button_state.down ; push the button
click_button_loop1
sta ob_spec_buf+cmd_button.state
jsr write_ob_spec
jsr get_object_position
jsr draw_cmd_button ; draw button in pressed state
click_button_loop2
jsr PullEvent ; see if the button has been released
bne no_button_up
lda EventBlock[0].what
cmp #EventType.MOUSEUP
beq button_clicked ; if mouse is up, we're done - register click
no_button_up ; if no event or event wasn't mouse up
jsr test_over_obj ; has mouse pointer moved off the button?
beq is_over_button
jsr read_ob_spec
lda #button_state.up
cmp ob_spec_buf+cmd_button.state
beq click_button_loop2 ; if mouse is off button and it's already up, do nothing
bne click_button_loop1 ; else let button up
is_over_button ; if mouse is over button, see if it's just moved back over it after being somewhere else
jsr read_ob_spec
lda #button_state.down
cmp ob_spec_buf+cmd_button.state
beq click_button_loop2 ; if button is already pressed, do nothing
bne click_button_loop1
button_clicked ; mouse released, but only register click if pointer was over the button
jsr read_ob_spec
lda #button_state.up
cmp ob_spec_buf+cmd_button.state
beq button_not_clicked
sta ob_spec_buf+cmd_button.state
jsr write_ob_spec
jsr draw_cmd_button ; ensure button is left un-pressed
; ************ do button callback here ******************
button_not_clicked
rts
;

Edited by flashjazzcat
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...