HyperCard–What Am I Thinking?

Good morning. I've decided to try to create HyperCard in Codea.

What am I thinking?



A couple of days ago I found my copy of "The Complete HyperCard Handbook" by Danny Goodman,



'IMG_9694



And the HyperCard Reference.


Screen Shot 2021-03-14 at 9.27.20 AM


Now if you're of a certain age, you may be asking, "What is HyperCard?" I won't explain. Browser search is your friend.

You may also be asking, "What is Codea?" I'm going to explain, but first a side note.

Note



This isn't going to be a tutorial. This isn't even going to be a blog about what I learn, or how I learn (although there will be bits like that). I'm going to use this blog to keep me motivated and to chronicle my attempt at recreating this wondrous piece of technology using a tool I have no real knowledge of [sic]. This blog is going to be wandering, and less formal. I'm doing this mostly for myself. There will be better ways of doing something, and I may not succeed.

Feel free to let me know what you think on Twitter, MicroBlog, or Mastadon.

End Note

Codea



So what is Codea? Codea is an app that allows you to create applications on iOS. If you know the history of the Apple App Store, you know what a big deal this is. Here is a screenshot of Codea taken from their website at Codea.io


Screen Shot 2021-03-14 at 9.35.58 AM

Codea uses the Lua programming language. I'm in the process of learning/teaching myself Lua. If you want to learn along with me, check out my Lua blog. The really cool thing about Codea is that it allows you to create standalone applications for iOS using Xcode. I was sold, and bought a copy of Codea ($14.99 as of now) but hadn't really used it or even looked at it for over a year.

Then, as I said, I found my old HyperCard materials. I've wanted a HyperCard on the new Apple hardware for years. Something tickled the back of my mind. I was going to try to create a new HyperCard using Codea.

So, yesterday with only three hours of sleep, I pulled up my iPad and fired up Codea and cracked open the HyperCard materials. I guess I should give you the basics of HyperCard.

HyperCard


HyperCard was an Apple development tool that allowed anyone to create useful applications on the (really) old Macs. The concept of HyperCard is that an application is a stack (think deck) of cards. Each card is a screen. Each card can have
  • buttons,
  • graphics,
  • text (which are a form of graphics),
  • and input fields.

Each stack had a background that every card shared. Each object (apart from graphics) could have scripts attached that ran when something like a mouse click affected them. The scripting language was specific to HyperCard and called HyperTalk. Later versions of HyperCard allowed other scripting languaguages such as Apple's own AppleTalk.

HyperCard also provided the tools that allowed you to create stacks, each of the objects, scripts, and to run and test it all. Stacks could be distributed.

Pretty cool stuff even now.

So am I insane thinking I can do this? Time will tell.

Back to Codea!

First Things First



I opened Codea, and figured out all the tools and how it worked. Once I felt I could do something, I started simply and just ran some basic Lua code to make sure it worked as expected (and because I'm still learning Lua). Then I started cobbling together a stack of cards. I work through a problem in small, single steps that I can test as I go. I still don't know how to do TDD in Codea or Lua, so this micro-iterative development process lets me catch problems and isolate them as I go. After a couple of hours of stumbling around Codea, Lua and what I was trying to create, I had a good start. My approach was to create each HyperCard object in the following order:

  1. Stack
  2. Background
  3. Card
  4. Button
  5. Field

As I got the basics of an object working, I'd add it to the stack object, thus building up my HyperCard stack. This allows me to test integration as I go in small iterative steps. My fumbling progress actually produced results which motivated me to keep going. As of yesterday, I have everything but fields.

I'm going to post my code as I go. If you have a better way to do something in Lua or Codea, please let me know on my social channels. As I said, I'm learning.

I've split the code into files in Codea to better manage and isolate things.

Main File



Screen Shot 2021-03-14 at 9.58.38 AM

Going through this:

Lines 9-12 I declare some global constants that determine the size of a stack. I haven't figured out how to declare constants in Lua/Codea, so I'm just uppercasing them.

The function offsetToStack() is my first attempt to translate buttons and other objects to the stack's coordinates. I'll replace this since I've since found that Codea has a translate function that does just that.

The next function, invert() simply inverts Codea's y axis. Codea's screen origin (0,0) is in the lower left corner of the screen. I'm used to working with an origin at the upper-left, so this function does that. There may be a setting in Codea to do this, but I haven't found it.

Codea is similar to PICO-8 in that they are both development tools for dealing with animation. Whereas PICO-8 has the standard functions:

  • _init()
  • _update()
  • _draw()

Codea has only two:

  • setup()
  • draw()

In my setup() function, I create my stack object., specifying a background color of white: color(r,g,b).

In the next function draw(), I clear the screen using Codea's background function. This actually caused me an issue at one point because HyperCard stacks have a background and I used background as a variable name. That caused a name collision. Duh! Everything is global in Lua unless marked as local. I then call the draw() function of my stack to draw it.

Codea can handle multi-touch gestures in iOS, and provides a function called touched(touch) to capture them. I created my own simplistic event chain using this mechanism. I just pass the touch down to my stack and let the stack distribute it to each of the objects it own. It's up to each object to determine whether to respond or now.

The main file is fairly straightforward (to me). I'll make it simpler once I redo the offsetToStack() logic.

Let's look at my stack implementation.

Stack


Here is the stack object code.



Screen Shot 2021-03-14 at 10.24.03 AM


Lines 279-287 is my Stack:init() that takes an optional background color as a parameter. Within this function, I create the stack background on line 281. I'm using my global dimensions as the size of the stack and background. I should make these dimensions parameters so I can eliminate the globals in the main file, and allow for stacks of different sizes. A stack needs cards, but because I'm creating a new stack, I only need a single blank card and insert it into a Lua table of cards. I create this on line 284. A HyperCard stack can only display a single card (the top card by default) and in order to keep track of which card is active I create a property to do this on line 285.

My createblankcard function creates a Card object with the global size and an optional card color in line 293. As the comment states, cards are actually transparent by default allowing the background to "show through" The objects on a card are not transparent, which allows them to composite or overlay with the background and its objects. Think of it this way, the HyperCard background is just a card whose "background color" is not transparent.

Lines 298-301 draws the stack by drawing the background, which is not transparent, under the active card, which is transparent apart from its objects.

Lines 304-307 handle my event chain. Here I get the touch passed in from my main file, and forward it to the background and the active card. Each will pass the event to each of its sub-objects.

Again, the stack object turned out simpler than I had originally thought it would be. Let's move on to the background.

Background



The background code is a little more involved, but not overly so.



Screen Shot 2021-03-14 at 10.37.08 AM

I've named this class BackG to avoid the name collision with background.

Lines 101-107 are just a bit of refactoring. The function initCoords() just takes in the dimensions of the stack background and saves them into properties.

The actual init() function accepts the dimensions and an optional background (of the background card) color. I call my helper function on line 111.

Lines 115-117 are empty Lua tables I'll use to manage the objects owned by the background card.

In lines 119-120 I create a test text image (label) and button. These will be inserted into the empty tables above, but I'm just testing at this point and again trying to keep the code simplistic and iterable. Once things are working, I will remove these two lines since a default background has no objects.

The String object takes a string label, and an x,y coordinate. The Button object takes a string, x,y,h,w, and an optional color.

Function BackG:drawBackground() draws the actual background layer. If the object has a color, we use it otherwise the default color of the background will be white. We then use Codea's rect function to draw it in line 133.

Function BackG:draw() draws the background layer, and each of my test objects. I should iterate over the objects owned by the background, but for now this is just tiny testing steps.

Lines 144-145 is my event chain. The stack object has passed us a touch event. Here I foward it to my button. I don't pass it to the text object because text doesn't react to events in HyperCard. It's treated as an image. Again, this is just test code. In reality I will iterate over of the buttons owned by the background and forward the event to each.

Next, cards.

Card




Screen Shot 2021-03-14 at 10.50.54 AM

This object is similar to BackG. I'm wondering if I should combine the two. The only real difference is in the transparency of the draw. At this point, I'm keeping the two separate in case other differences become evident as I progress.

If you can follow the code of BackG, you should be able to understand Card.

The first real object I tackled was the String (label) object. Maybe I should rename it to Label. Here is the code.

String



Screen Shot 2021-03-14 at 10.56.51 AM

This object is very simple since it cannot own or manage other objects. It's just a label.

In String:init() I translate the passed in coordinates to the stack (I'll use Codea's built-in translate function later.) In lines 319-323 I check to see if an optional color parameter is passed in. If it is, I use it, otherwise I set the color of the text to black, the default color.

The draw() function sets the color of the text (Codea confusingly calls this the fill color.). I use a hard-coded font which I should parameterize–this is small iterative test code again. Ditto for the font size. Line 331 uses Codea's text() function to draw the string onto the screen. Line 317 made sure the string is actually within the stack bounds.

Strings don't respond to events, so I don't do anything in the touched(touch) method.

I tackled buttons next.

Button



The button is the most complicated code so far. Here is the first section.

Screen Shot 2021-03-14 at 11.04.19 AM


We've seen initCoords() before. I should factor this out in the future.

The init() saves the dimensions, text, and an optional color for the button. I should factor the default color code out since it's repeated. I then have two flags and a delay variable. I use these to manage highlighting the button when it's tapped.

The next function is the first time we've seen the way I deal with the event chain at the object level. isTouchInButton() takes a touch event passed from either the background or card that owns this instance of the button. Lines 184-186 use one of the flags I set in init. If I hold my finger on the button, the button object floods with touch events (60 per frame). The test in line 184-186 is a guard to bypass handling touches if we're already processing one. Lines 188-194 tests the position of the touch event against the bounds of the button. If the touch event is in the button, I set the flag and then function returns true (nil otherwise).

This code works, but it's wrong. I need to move the guard and the flag set to the Button:touched(touch) function.

Here is the remainder of the Button code.

Screen Shot 2021-03-14 at 11.15.01 AM

I'm not sure I like the function Button:touchButton(). It's been factored out. This tests if the touch event is in this button and sets a flag to highlight the button. It then clears the isTouched flag to allow other events to flow into the button. The flag needs to be cleared after the highlighting has completed. You might be asking, why set a highlighting flag when we could just highlight the button here or call from here? The reason is, highlighting the button is a form of animation and needs to be done in the draw() function. We can't call drawing related functions outside of draw() I can, however, test the flag in draw() and handle it there.

So how do I highlight the button when it's touched? I invert it for a given amount of time. This makes it flash. It's simplistic, but it's good enough for now. The logic is in Button:highlightButton(). I call this inside draw() at 60 fps. Each time it's called, I see if a second has passed (60 frames at 60fps). I reset the delay to 0 if I've delayed long enough and stop highlighting. If the highlight delay hasn't expired, I increment the delay by 1 frame. I should rename this function since it really doesn't highlight the button, but manages the highlight duration.

The next function actually draws the button frame and background. The function strokeWidth() is a Codea function to specify the line width of the border. stroke() sets the border color. If the highlight flag is set, the button is filled with white, otherwise the button's color. The button is just a rectangle, since I haven't figured out how to create rounded-rects in Codea.

Next, is the drawLabel() function. This is a brute-force function. It gets the dimensions of the label text, sets the text color to black (0), and draws it into the button rectangle offset by the dimensions of the text. I need to redo this. The button should be sized to the text, or I need to fix how I position the text within the button. Again, small iterative steps. I just wanted it to work and look reasonable for testing purposes.

Lines 239-243 draw the complete button. Line 240 handles the highlight delay (I really need to rename the function). I then draw the button and layer its label on it.

Finally, lines 246-249 get a touch event from the owning card or background and pass it to the button's touchButton() function.

What about HyperCard fields?

Fields



Fields are input fields. You can type in them and get a value. Codea has no built-in field capability. I will need to "roll my own", but this involves a lot of things. Drawing, getting keyboard input, cursor management, etc. I need to hunt around and see if someone has already done this. I'm sure it has been. I'll just find something I can understand and use that.


Summary



For a couple of hours work, I think I have accomplished a lot. I've created a:

  • Stack
  • Background
  • Card
  • Button
  • String
  • Event chain
  • Integrated it all

That's fine, but does it all work? The answer is yes.



IMG_5438

In this screenshot, I have two labels, one on the background and one on a card. The same for two buttons. The buttons respond as expected.

It's not HyperCard, it's not a complete authoring environment, but it's more than I was expecting when I started.

Xcode


I was intrigued. Codea can export to Xcode. What would that look like? I took my project and exported it, and opened it in Xcode.

Screen Shot 2021-03-14 at 11.42.38 AM

It looks like the project embeds a Lua runtime, and uses my actual Codea Lua files. Neat! Does it compile and run? Yes. But, I had to update the deployment target, update the settings and create an empty Assets folder in my project. It compiles and runs as a standalone project on iOS. Will Apple accept these apps? Yes. There are several apps built with Codea on the App Store.

How about the Mac? Well, sort of. It compiles, but only under the (Designed for iPad) option. It runs, but this is what it looks like (compare it to the image above).


Screen Shot 2021-03-14 at 11.47.14 AM

It doesn't look the same. Also, mouse clicks don't translate to touches, I guess. So, at this point, I can't use my app on the Mac.

That's more than enough for now. Will I continue playing with this project? I think so. I want to get fields working. Then I can think about how to attach scripts to all of the objects. Scripting using Lua makes sense to me. Reimplementing HyperTalk would be a separate project, I don't feel like tackling right now.

'Til next time.





This site does not track your information.