2022-10-04 00:09:48 +00:00
/**
* Topaz ' s Pizza Timer
* Built for a future video
* https : //theindustriousrabbit.video
*
* Copyright 2022 John Bintz
* MIT License
*/
2022-10-02 20:59:06 +00:00
# include <stdio.h>
2022-10-04 00:09:48 +00:00
// basic amiga stuff
2022-10-02 20:59:06 +00:00
# include <proto/dos.h>
# include <proto/exec.h>
2022-10-04 00:09:48 +00:00
// allow us to load and scale fonts
2022-10-02 20:59:06 +00:00
# include <proto/diskfont.h>
2022-10-04 01:14:18 +00:00
// handle images and sound
# include <proto/datatypes.h>
# include <datatypes/datatypesclass.h>
2022-10-04 00:09:48 +00:00
// timer stuff. using proto/timer.h did not work well, likely
// because of how you have to open the device.
2022-10-02 20:59:06 +00:00
# include <clib/timer_protos.h>
2022-10-04 00:09:48 +00:00
// this is the manual for the original intuition stuff.
// not much here is different: https://ia801905.us.archive.org/33/items/Amiga_Intuition_Reference_Manaual_1985_Adn-Wesley_Publishing_Company/Amiga_Intuition_Reference_Manaual_1985_Addison-Wesley_Publishing_Company.pdf
2022-10-02 20:59:06 +00:00
// gadtools gives a proper set of components for building UIs.
// http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0278.html
2022-10-04 00:09:48 +00:00
# include <proto/intuition.h>
2022-10-02 20:59:06 +00:00
# include <proto/gadtools.h>
2022-10-04 00:09:48 +00:00
# include <intuition/gadgetclass.h>
// drawin' stuff
// This is where we get font handling
2022-10-02 20:59:06 +00:00
# include <proto/graphics.h>
2022-10-04 00:09:48 +00:00
// the main app window
2022-10-02 20:59:06 +00:00
# define WINDOW_WIDTH (240)
2022-10-04 00:09:48 +00:00
# define WINDOW_HEIGHT (80)
# define WINDOW_TITLE "Topaz's Pizza Timer"
2022-10-02 20:59:06 +00:00
# define WINDOW_CHROME_WIDTH (4)
struct NewWindow winlayout = {
20 , 20 , // x, y
WINDOW_WIDTH , WINDOW_HEIGHT , // w, h
0 , 1 , // detailpen, blockpen,
// you have to add the different gadget types you're looking for
// http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_2._guide/node0106.html
2022-10-04 00:09:48 +00:00
IDCMP_REFRESHWINDOW | IDCMP_CLOSEWINDOW | BUTTONIDCMP | SLIDERIDCMP | IDCMP_MENUPICK , // IDCMP flags
2022-10-02 20:59:06 +00:00
WFLG_SMART_REFRESH | WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_CLOSEGADGET | WFLG_ACTIVATE , // window flag from Window struct
NULL , // FirstGadget
NULL , // menu checkmark
WINDOW_TITLE , // title
NULL , // default screen
NULL , // bitmap
WINDOW_WIDTH , WINDOW_HEIGHT , // min size
WINDOW_WIDTH , WINDOW_HEIGHT , // max size,
WBENCHSCREEN // screen where you want the window to open
} ;
2022-10-04 00:09:48 +00:00
struct Screen * screen ;
struct Window * window ;
struct Gadget * windowGadgets ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
//struct NewWindow aboutWindowLayout = { };
struct Window * aboutWindow ;
struct Gadget * aboutWindowGadgets ;
// Fonts!
2022-10-02 20:59:06 +00:00
struct TextAttr Topaz80 = { " topaz.font " , 8 , 0 , 0 } ;
// There's no native 16 pixel Topaz font so we'll use
// diskfont.library to make one.
struct TextAttr Topaz160 = { " topaz.font " , 16 , 0 , 0 } ;
2022-10-04 00:09:48 +00:00
struct TextFont * topaz80Font ;
struct TextFont * topaz160Font ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
// Gadtools stuff
2022-10-02 20:59:06 +00:00
# define START_STOP_BUTTON_ID 0
# define RESET_BUTTON_ID 1
# define HOURS_SLIDER_ID 2
# define MINUTES_SLIDER_ID 3
# define SECONDS_SLIDER_ID 5
void * visualInfo ;
2022-10-04 00:09:48 +00:00
struct Gadget * timerGadget , * hourSlider , * minuteSlider , * secondSlider ;
// gadtools menus
// https://wiki.amigaos.net/wiki/GadTools_Menus
struct MenuData {
int id ;
} ;
# define MENU_ABOUT_ID 0
# define MENU_QUIT_ID 1
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
struct MenuData MENU_ABOUT = { MENU_ABOUT_ID } ;
struct MenuData MENU_QUIT = { MENU_QUIT_ID } ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
struct NewMenu appMenu [ ] = {
{ NM_TITLE , " Pizza Timer " , 0 , 0 , 0 , 0 , } ,
{ NM_ITEM , " About... " , 0 , 0 , 0 , & MENU_ABOUT } ,
{ NM_ITEM , " Quit " , " Q " , 0 , 0 , & MENU_QUIT } ,
{ NM_END , NULL , 0 , 0 , 0 , 0 , } ,
} ;
struct Menu * menu ;
// timer stuff
# define TIMER_INTERVAL 200000
struct Device * TimerBase ;
struct timerequest * TimerIO ;
struct timeval currentSystemTime ;
2022-10-04 01:14:18 +00:00
// sound stuff
// get that bell
# define BELL_FILENAME "bell.8svx"
2022-10-04 00:09:48 +00:00
// our business logic
// for how long should I cook this pizza?
2022-10-02 20:59:06 +00:00
unsigned int uiHours = 0 ;
unsigned int uiMinutes = 12 ;
unsigned int uiSeconds = 0 ;
2022-10-04 00:09:48 +00:00
unsigned int priorHours , originalUiHours ;
unsigned int priorMinutes , originalUiMinutes ;
unsigned int priorSeconds , originalUiSeconds ;
2022-10-02 20:59:06 +00:00
char timerText [ 9 ] ;
2022-10-04 00:09:48 +00:00
ULONG timerStartTime = 0 , timerDistance , timerBuild ;
BOOL timerIsRunning = FALSE ;
BOOL timerStarted = FALSE ;
BOOL terminated = FALSE ;
2022-10-02 20:59:06 +00:00
/**
* Initialize system stuff .
*/
2022-10-04 00:09:48 +00:00
// This function, despite taking no parameters, still needs void as a
// parameter.
// http://www.hipooniosamigasite.org/amigadocs/files/LOON-DOCS-DISKS/LOON5/LatticeC.pt3
int setup ( void ) {
struct MsgPort * timerPort ;
2022-10-02 20:59:06 +00:00
// make sure the font exists on the computer
2022-10-04 00:09:48 +00:00
// http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node0308.html
2022-10-02 20:59:06 +00:00
if ( NULL = = ( topaz80Font = OpenFont ( & Topaz80 ) ) ) {
return 0 ;
}
2022-10-04 00:09:48 +00:00
// load this via disk so it can be scaled up
// http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node0137.html
2022-10-02 20:59:06 +00:00
if ( NULL = = ( topaz160Font = OpenDiskFont ( & Topaz160 ) ) ) {
return 0 ;
}
2022-10-04 00:09:48 +00:00
// get a handle on the Workbench screen
2022-10-02 20:59:06 +00:00
if ( NULL = = ( screen = LockPubScreen ( NULL ) ) ) {
return 0 ;
}
2022-10-04 00:09:48 +00:00
// Get...visual info. GadTools needs this, and it's all private.
// secrets. secrets between gadtools and the amiga.
// don't snoop. they wouldn't like it.
2022-10-02 20:59:06 +00:00
if ( NULL = = ( visualInfo = GetVisualInfo ( screen , TAG_END ) ) ) {
return 0 ;
}
2022-10-04 00:09:48 +00:00
// set up the async timer message port
// we create this port, but it ends up in the timerequest structure when
// we're done with the device open, so we don't have to worry about
// hanging onto a pointer to it.
if ( NULL = = ( timerPort = CreatePort ( 0 , 0 ) ) ) {
return 0 ;
}
// create an IO object we can get responses to on our timer port
if ( NULL = = ( TimerIO = ( struct timerequest * ) CreateExtIO ( timerPort , sizeof ( struct timerequest ) ) ) ) {
return 0 ;
}
// OpenDevice returns an error code on failure, 0 on success, like a Unix command
// Give OpenDevice the thing we will use to communicate with the timer device
if ( 0 ! = ( OpenDevice ( TIMERNAME , UNIT_MICROHZ , ( struct IORequest * ) TimerIO , 0L ) ) ) {
2022-10-02 20:59:06 +00:00
return 0 ;
}
2022-10-04 00:09:48 +00:00
// this allows us to use GetSysTime, rather than performing an IO operation
// to get the current time.
TimerBase = TimerIO - > tr_node . io_Device ;
2022-10-02 20:59:06 +00:00
return 1 ;
}
/**
* Tear down system stuff .
*/
2022-10-04 00:09:48 +00:00
void teardown ( void ) {
// http://amigadev.elowar.com/read/ADCD_2.1/Devices_Manual_guide/node0196.html
struct MsgPort * tempTimerPort ;
if ( TimerIO ) {
CloseDevice ( ( struct IORequest * ) TimerIO ) ;
tempTimerPort = TimerIO - > tr_node . io_Message . mn_ReplyPort ;
if ( tempTimerPort ) {
DeletePort ( tempTimerPort ) ;
}
}
// all right, then. keep your secrets.
2022-10-02 20:59:06 +00:00
if ( visualInfo ) FreeVisualInfo ( visualInfo ) ;
if ( screen ) UnlockPubScreen ( NULL , screen ) ;
if ( topaz80Font ) CloseFont ( topaz80Font ) ;
if ( topaz160Font ) CloseFont ( topaz160Font ) ;
}
2022-10-04 00:09:48 +00:00
/**
* Build the GadTools gadgets for this UI .
* Normally I ' d move this to a separate piece of code ,
* but for example purposes I want everything in this one file
* for easier searching .
*/
struct Gadget * buildUI ( void ) {
2022-10-02 20:59:06 +00:00
struct NewGadget ng ;
struct Gadget * currentGadget ;
struct Gadget * glist ;
2022-10-04 00:09:48 +00:00
// Add an empty gadget for GadTools to store extra data.
// http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node0274.html
2022-10-02 20:59:06 +00:00
currentGadget = CreateContext ( & glist ) ;
ng . ng_LeftEdge = WINDOW_CHROME_WIDTH ;
// TODO: constantize these
ng . ng_TopEdge = 11 ;
ng . ng_Height = 12 ;
ng . ng_Width = 0 ;
ng . ng_GadgetText = NULL ;
ng . ng_TextAttr = & Topaz80 ;
ng . ng_VisualInfo = visualInfo ;
ng . ng_GadgetID = NULL ;
ng . ng_Flags = PLACETEXT_IN ;
// Timer display
ng . ng_Width = WINDOW_WIDTH - WINDOW_CHROME_WIDTH * 2 ;
ng . ng_TextAttr = & Topaz160 ;
ng . ng_Height = 20 ;
timerGadget = currentGadget = CreateGadget (
TEXT_KIND ,
currentGadget ,
& ng ,
2022-10-04 00:09:48 +00:00
GTTX_Text , & timerText ,
2022-10-02 20:59:06 +00:00
GTTX_Justification , GTJ_CENTER ,
TAG_END
) ;
ng . ng_TextAttr = & Topaz80 ;
ng . ng_Height = 12 ;
ng . ng_Width = ( WINDOW_WIDTH - WINDOW_CHROME_WIDTH * 2 ) / 2 ;
ng . ng_TopEdge + = 18 ;
if ( timerIsRunning ) {
ng . ng_GadgetText = " _Stop " ;
} else {
ng . ng_GadgetText = " _Start " ;
}
ng . ng_GadgetID = START_STOP_BUTTON_ID ;
currentGadget = CreateGadget (
BUTTON_KIND ,
currentGadget ,
& ng ,
GT_Underscore , ' _ ' ,
TAG_END
) ;
ng . ng_LeftEdge + = ( WINDOW_WIDTH - WINDOW_CHROME_WIDTH * 2 ) / 2 ;
ng . ng_GadgetText = " _Reset " ;
ng . ng_GadgetID = RESET_BUTTON_ID ;
currentGadget = CreateGadget (
BUTTON_KIND ,
currentGadget ,
& ng ,
GT_Underscore , ' _ ' ,
2022-10-04 00:09:48 +00:00
GA_Disabled , timerIsRunning | | ! timerStarted ,
2022-10-02 20:59:06 +00:00
TAG_END
) ;
ng . ng_LeftEdge = 85 ;
ng . ng_Width = ( WINDOW_WIDTH - WINDOW_CHROME_WIDTH * 2 ) - 85 + 4 ;
ng . ng_TopEdge + = 12 ;
2022-10-04 00:09:48 +00:00
// The level is also displayed to the left of the slider by default.
// We need to leave space for its rendering, which is done separately
// from label rendering.
2022-10-02 20:59:06 +00:00
ng . ng_GadgetText = " Hours: " ;
ng . ng_Flags = PLACETEXT_LEFT ;
ng . ng_GadgetID = HOURS_SLIDER_ID ;
2022-10-04 00:09:48 +00:00
hourSlider = currentGadget = CreateGadget (
2022-10-02 20:59:06 +00:00
SLIDER_KIND ,
currentGadget ,
& ng ,
GTSL_Min , 0 ,
GTSL_Max , 23 ,
GTSL_Level , uiHours ,
2022-10-04 00:09:48 +00:00
// You need both MaxLevelLen and LevelFormat for the label to display.
2022-10-02 20:59:06 +00:00
GTSL_MaxLevelLen , 2 ,
2022-10-04 00:09:48 +00:00
// The level is also a long, so use long number formattting.
2022-10-02 20:59:06 +00:00
GTSL_LevelFormat , " %2ld " ,
GA_Disabled , timerIsRunning ,
TAG_END
) ;
ng . ng_TopEdge + = 12 ;
ng . ng_GadgetText = " Mins: " ;
ng . ng_GadgetID = MINUTES_SLIDER_ID ;
2022-10-04 00:09:48 +00:00
minuteSlider = currentGadget = CreateGadget (
2022-10-02 20:59:06 +00:00
SLIDER_KIND ,
currentGadget ,
& ng ,
GTSL_Min , 0 ,
GTSL_Max , 59 ,
GTSL_Level , uiMinutes ,
GTSL_MaxLevelLen , 2 ,
GTSL_LevelFormat , " %2ld " ,
GA_Disabled , timerIsRunning ,
TAG_END
) ;
ng . ng_TopEdge + = 12 ;
ng . ng_GadgetText = " Secs: " ;
ng . ng_GadgetID = SECONDS_SLIDER_ID ;
2022-10-04 00:09:48 +00:00
secondSlider = currentGadget = CreateGadget (
2022-10-02 20:59:06 +00:00
SLIDER_KIND ,
currentGadget ,
& ng ,
GTSL_Min , 0 ,
GTSL_Max , 59 ,
GTSL_Level , uiSeconds ,
GTSL_MaxLevelLen , 2 ,
GTSL_LevelFormat , " %2ld " ,
GA_Disabled , timerIsRunning ,
TAG_END
) ;
return glist ;
}
2022-10-04 00:09:48 +00:00
void setTimerText ( void ) {
// Only change the timer widget text if the time is different
// from the last render. This prevents unnecessary renders and potential
// flashes of UI redraw.
if (
( priorHours ! = uiHours ) | |
( priorMinutes ! = uiMinutes ) | |
( priorSeconds ! = uiSeconds )
) {
// _Technically_ this will change the label's contents all on its own,
// but the newly-rendered label stomps on the old one without clearing
// it out, so we need to force the full label redraw with GT_SetGadgetAttrs.
sprintf ( timerText , " %02d:%02d:%02d " , uiHours , uiMinutes , uiSeconds ) ;
GT_SetGadgetAttrs (
timerGadget ,
window ,
NULL ,
GTTX_Text , & timerText ,
TAG_DONE
) ;
priorHours = uiHours ;
priorMinutes = uiMinutes ;
priorSeconds = uiSeconds ;
}
2022-10-02 20:59:06 +00:00
}
2022-10-04 00:09:48 +00:00
/**
* Ask the timer to send a message to our message port
* after a defined number of milliseconds .
*/
void startTimer ( void ) {
TimerIO - > tr_node . io_Command = TR_ADDREQUEST ;
TimerIO - > tr_time . tv_secs = 0 ;
TimerIO - > tr_time . tv_micro = TIMER_INTERVAL ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
// SendIO is async, it doesn't wait for the IO to complete before continuing
SendIO ( ( struct IORequest * ) TimerIO ) ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
/**
* Render the UI .
*/
void renderUI ( void ) {
windowGadgets = buildUI ( ) ;
// Use -1 for working with all gadgets.
// You need to add, refresh, and refresh window when adding
// GadTools gadgets.
AddGList ( window , windowGadgets , - 1 , - 1 , NULL ) ;
RefreshGList ( windowGadgets , window , NULL , - 1 ) ;
setTimerText ( ) ;
GT_RefreshWindow ( window , NULL ) ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
/**
* Clear the current UI .
*/
void clearUI ( void ) {
RemoveGList ( window , windowGadgets , - 1 ) ;
FreeGadgets ( windowGadgets ) ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
/**
* Toggle the timer between started and stopped .
*/
void handleToggleTimer ( void ) {
timerIsRunning = ! timerIsRunning ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
// TODO: don't reset the timer when it's stopped/started
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
if ( timerIsRunning ) {
// http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_2._guide/node04FA.html
GetSysTime ( & currentSystemTime ) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
// timers are microsecond resolution
timerStartTime = currentSystemTime . tv_secs * 1000000 + currentSystemTime . tv_micro ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
priorHours = NULL ;
priorMinutes = NULL ;
priorSeconds = NULL ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
originalUiHours = uiHours ;
originalUiMinutes = uiMinutes ;
originalUiSeconds = uiSeconds ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
timerStarted = TRUE ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
// start the async timer
startTimer ( ) ;
} else {
GT_SetGadgetAttrs (
hourSlider ,
window ,
NULL ,
GTSL_Level , uiHours ,
TAG_DONE
) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
GT_SetGadgetAttrs (
minuteSlider ,
window ,
NULL ,
GTSL_Level , uiMinutes ,
TAG_DONE
) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
GT_SetGadgetAttrs (
secondSlider ,
window ,
NULL ,
GTSL_Level , uiSeconds ,
TAG_DONE
) ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
clearUI ( ) ;
renderUI ( ) ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
/**
* Reset the timer .
*/
void handleResetTimer ( void ) {
uiHours = originalUiHours ;
uiMinutes = originalUiMinutes ;
uiSeconds = originalUiSeconds ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
timerStarted = FALSE ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
setTimerText ( ) ;
clearUI ( ) ;
renderUI ( ) ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
/**
* Process a single Intuition message .
*/
void handleIntuitionMessage ( struct IntuiMessage * iMessage ) {
struct Gadget * targetGadget ;
struct Menu * targetMenu ;
struct MenuData * menuData ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
BOOL rerenderTimer = FALSE ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
switch ( iMessage - > Class ) {
// We've released a button.
case IDCMP_GADGETUP :
targetGadget = ( struct Gadget * ) iMessage - > IAddress ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
switch ( targetGadget - > GadgetID ) {
case START_STOP_BUTTON_ID :
handleToggleTimer ( ) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
break ;
case RESET_BUTTON_ID :
handleResetTimer ( ) ;
rerenderTimer = TRUE ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
break ;
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
break ;
// We picked a menu item.
case IDCMP_MENUPICK :
// https://en.wikibooks.org/wiki/Aros/Developer/Docs/Libraries/GadTools
targetMenu = ( struct Menu * ) ItemAddress ( menu , iMessage - > Code ) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
menuData = GTMENUITEM_USERDATA ( targetMenu ) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
switch ( menuData - > id ) {
// I originally tried to use MENU_QUIT.id here
// https://stackoverflow.com/questions/14069737/switch-case-error-case-label-does-not-reduce-to-an-integer-constant
case MENU_QUIT_ID :
terminated = TRUE ;
2022-10-02 20:59:06 +00:00
break ;
2022-10-04 00:09:48 +00:00
}
break ;
// We've moved the mouse. In slider gadget talk, we've changed
// the value of the slider.
case IDCMP_MOUSEMOVE :
targetGadget = ( struct Gadget * ) iMessage - > IAddress ;
switch ( targetGadget - > GadgetID ) {
case HOURS_SLIDER_ID :
uiHours = iMessage - > Code ;
rerenderTimer = TRUE ;
2022-10-02 20:59:06 +00:00
break ;
2022-10-04 00:09:48 +00:00
case MINUTES_SLIDER_ID :
uiMinutes = iMessage - > Code ;
rerenderTimer = TRUE ;
2022-10-02 20:59:06 +00:00
break ;
2022-10-04 00:09:48 +00:00
case SECONDS_SLIDER_ID :
uiSeconds = iMessage - > Code ;
rerenderTimer = TRUE ;
2022-10-02 20:59:06 +00:00
break ;
}
2022-10-04 00:09:48 +00:00
if ( rerenderTimer ) setTimerText ( ) ;
break ;
// bye bye
case IDCMP_CLOSEWINDOW :
terminated = TRUE ;
break ;
// this is required if your window is refreshable and you're using gadtools.
// http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node026F.html
case IDCMP_REFRESHWINDOW :
GT_BeginRefresh ( window ) ;
GT_EndRefresh ( window , TRUE ) ;
break ;
2022-10-02 20:59:06 +00:00
}
2022-10-04 00:09:48 +00:00
}
void endTimer ( void ) {
// TODO: play an included IFF 8SVX sound
2022-10-04 01:14:18 +00:00
APTR bellSound = NULL ;
struct dtTrigger myTrigger ;
struct MsgPort * TimerPort ;
ULONG soundPlayResult , timerSignal ;
if ( bellSound = NewDTObject ( BELL_FILENAME , DTA_GroupID , GID_SOUND , TAG_END ) ) {
myTrigger . MethodID = DTM_TRIGGER ;
myTrigger . dtt_GInfo = NULL ;
myTrigger . dtt_Function = STM_PLAY ;
myTrigger . dtt_Data = NULL ;
// https://github.com/khval/AmosKittens/blob/79f00ba3b81805b54fd4e437f667ea2eecab740d/OS/AmigaOS/animation.cpp#L128
soundPlayResult = DoDTMethodA ( bellSound , NULL , NULL , ( Msg ) & myTrigger ) ;
TimerPort = TimerIO - > tr_node . io_Message . mn_ReplyPort ;
// eventually wait for the sound to stop playing
TimerIO - > tr_node . io_Command = TR_ADDREQUEST ;
TimerIO - > tr_time . tv_secs = 3 ;
TimerIO - > tr_time . tv_micro = 0 ;
SendIO ( ( struct IORequest * ) TimerIO ) ;
}
2022-10-04 00:09:48 +00:00
DisplayBeep ( screen ) ;
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
uiHours = uiMinutes = uiSeconds = 0 ;
setTimerText ( ) ;
timerIsRunning = FALSE ;
timerStarted = FALSE ;
clearUI ( ) ;
renderUI ( ) ;
2022-10-04 01:14:18 +00:00
if ( bellSound ) {
timerSignal = 1L < < TimerPort - > mp_SigBit ;
Wait ( timerSignal ) ;
DisposeDTObject ( bellSound ) ;
}
2022-10-04 00:09:48 +00:00
}
2022-10-02 20:59:06 +00:00
2022-10-04 00:09:48 +00:00
void handleTimerMessage ( void ) {
GetSysTime ( & currentSystemTime ) ;
timerDistance = ( ( currentSystemTime . tv_secs * 1000000 + currentSystemTime . tv_micro ) - timerStartTime ) / 1000000 ;
timerBuild = ( originalUiHours * 3600 + originalUiMinutes * 60 + originalUiSeconds ) - timerDistance ;
if ( timerBuild < = 0 ) {
endTimer ( ) ;
} else {
uiHours = timerBuild / 3600 ;
uiMinutes = timerBuild / 60 % 60 ;
uiSeconds = timerBuild % 60 ;
setTimerText ( ) ;
// this is like a setTimeout loop in JavaScript. keep re-running
// the same timer until we don't need it anymore.
startTimer ( ) ;
2022-10-02 20:59:06 +00:00
}
2022-10-04 00:09:48 +00:00
}
// https://wiki.amigaos.net/wiki/GadTools_Menus
void buildMenu ( void ) {
menu = CreateMenus (
appMenu , TAG_END
) ;
LayoutMenus ( menu , visualInfo , TAG_END ) ;
SetMenuStrip ( window , menu ) ;
}
void clearMenu ( void ) {
ClearMenuStrip ( window ) ;
FreeMenus ( menu ) ;
}
int main ( ) {
// TODO: Menu bar with about menu and change the title to match the time
struct IntuiMessage * iMessage ;
struct MsgPort * TimerPort ;
ULONG windowSignal , timerSignal , foundSignals ;
if ( 0 = = setup ( ) ) {
teardown ( ) ;
return 1 ;
}
// get our timer message port so we can await on timer events
TimerPort = TimerIO - > tr_node . io_Message . mn_ReplyPort ;
// business logic setup
timerIsRunning = FALSE ;
// open the empty window
window = OpenWindow ( & winlayout ) ;
if ( ! window ) {
teardown ( ) ;
return 1 ;
}
// these create the bit mask for Wait() to listen to events on
windowSignal = 1L < < window - > UserPort - > mp_SigBit ;
timerSignal = 1L < < TimerPort - > mp_SigBit ;
renderUI ( ) ;
buildMenu ( ) ;
while ( ! terminated ) {
// http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node02EB.html
// http://www.amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_2._guide/node038A.html
foundSignals = Wait ( windowSignal | timerSignal ) ;
if ( foundSignals & windowSignal ) {
// drain the window event queue
while ( ( ! terminated ) & & ( iMessage = GT_GetIMsg ( window - > UserPort ) ) ) {
handleIntuitionMessage ( iMessage ) ;
}
}
if ( ( foundSignals & timerSignal ) & & timerIsRunning ) {
handleTimerMessage ( ) ;
}
}
clearUI ( ) ;
clearMenu ( ) ;
CloseWindow ( window ) ;
2022-10-02 20:59:06 +00:00
teardown ( ) ;
return 0 ;
}