[Utility] Application Module (GUI, Loops & Events)

Share your advanced PureBasic knowledge/code with the community.
IndigoFuzz

[Utility] Application Module (GUI, Loops & Events)

Post by IndigoFuzz »

Hey all,

Another little utility module which I've put together following a design pattern that I've used in a lot of projects that allows you to make more modular applications.

Features
  • Handle GUI applications and have the application terminate when the main window closes.
  • Register event handlers to fire custom events.
  • Register interval functions that will be called every x milliseconds, or every cycle.
  • Pass user data to the events And interval functions.
Amalgamated Code:

Code: Select all

; ====================================================================================================
; Title:        Application Module
; Description:  Utility module for managing application runtime.
; Author:       Michael 'Micah' King (micah@indigofuzz.co.uk)
; Revision:     1 (03 FEB 2016)
; License:      Any improvements to be shared with the community.
; Website:      http://www.indigofuzz.co.uk
; ====================================================================================================

; - Declare App Module
DeclareModule App
  
  Enumeration Events ; - Standard Events and Base for Custom Events
    #Event_Initialise
    #Event_Release
  EndEnumeration
  
  Prototype.a pEvent(Event, *EventInfo) ; - Prototype for Event Handlers
  Prototype.a pCallback(*UserData) ; - Prototype for Interval Callbacks
  
  Declare ConnectEventHandler(Event, *Callback.pEvent) ; - Connect a new Event Handler
  Declare DisconnectEventHandler(Event, *Callback.pEvent) ; - Disconnect an Event Handler
  Declare BroadcastEvent(Event, *EventInfo = #Null) ; - Broadcast new Event
  
  Declare RegisterInterval(*Callback, Interval = 0, *UserData = #Null) ; - Register an Interval Callback
  Declare UnregisterInterval(*Callback) ; - Unregister an Interval Callback
  
  Declare HandleWindowEvents(State = #True) ; - Enable to call WindowEvent() each Loop
  Declare Execute(MainForm = #PB_Ignore, QuitOnClose = #False) ; - Execute (You can specify a Main Window)
  Declare Quit() ; - Break Main Loop
  
  
EndDeclareModule

; - Implement App Module
Module App
  
  Structure EVTHANDLER
    eventID.i
    *handler.pEvent
  EndStructure
  
  Structure LOOPHANDLER
    interval.i
    lastCall.i
    *userData
    *handler.pCallback
  EndStructure
  
  Structure INSTANCE
    List evtHandlers.EVTHANDLER()
    List loopHandler.LOOPHANDLER()
    handleUI.a
    mainForm.i
    quitOnClose.a
    execute.a
  EndStructure
  
  Global this.INSTANCE
  this\mainForm = #PB_Ignore
  
  ; - Event Handlers
  Procedure ConnectEventHandler(Event, *Callback.pEvent)
    With this
      If *Callback
        ForEach \evtHandlers()
          If \evtHandlers()\eventID = Event And \evtHandlers()\handler = *Callback
            ProcedureReturn #True
          EndIf
        Next
        AddElement(\evtHandlers())
        \evtHandlers()\eventID = Event
        \evtHandlers()\handler = *Callback
      EndIf
      ProcedureReturn #False  
    EndWith  
  EndProcedure
  
  Procedure DisconnectEventHandler(Event, *Callback.pEvent)
    With this
      If *Callback
        ForEach \evtHandlers()
          If \evtHandlers()\eventID = Event And \evtHandlers()\handler = *Callback
            DeleteElement(\evtHandlers())
            ProcedureReturn #True
          EndIf
        Next
      EndIf
      ProcedureReturn #False  
    EndWith  
  EndProcedure
  
  Procedure BroadcastEvent(Event, *EventInfo = #Null)
    With this
      ForEach \evtHandlers()
        If \evtHandlers()\eventID = Event
          \evtHandlers()\handler(Event, *EventInfo)
        EndIf
      Next
    EndWith
  EndProcedure
  
  ; - Interval Functions
  Procedure RegisterInterval(*Callback, Interval = 0, *UserData = #Null)
    With this
      If *Callback
        ForEach \loopHandler()
          If \loopHandler()\handler = *Callback
            \loopHandler()\interval = Interval
            \loopHandler()\lastCall = *UserData
            \loopHandler()\lastCall = ElapsedMilliseconds()
            ProcedureReturn #True
          EndIf
        Next
        AddElement(\loopHandler())
        \loopHandler()\handler = *Callback
        \loopHandler()\userData = *UserData
        \loopHandler()\interval = Interval
        \loopHandler()\lastCall = ElapsedMilliseconds()
        ProcedureReturn #True
      EndIf
    EndWith
  EndProcedure
  
  Procedure UnregisterInterval(*Callback)
    With this
      If *Callback
        ForEach \loopHandler()
          If \loopHandler()\handler = *Callback
            DeleteElement(\loopHandler())
            ProcedureReturn #True
          EndIf
        Next
      EndIf
    EndWith
    ProcedureReturn #True
  EndProcedure
  
  ; - Main Loop
  Procedure doIntervals()
    Protected time.i = ElapsedMilliseconds()
    With this
      ForEach \loopHandler()
        If \loopHandler()\interval > 0
          If \loopHandler()\lastCall + \loopHandler()\interval < time
            \loopHandler()\handler(\loopHandler()\userData)
            \loopHandler()\lastCall = time
          EndIf
        Else
          \loopHandler()\handler(\loopHandler()\userData)
        EndIf
      Next
    EndWith
  EndProcedure
  
  Procedure mainLoop()    
    With this
      While \execute = #True        
        If \handleUI
          If \mainForm > -1
            If \quitOnClose
              If IsWindow(\mainForm) = #False
                Break
              EndIf
            Else
              If IsWindow(\mainForm)
                WindowEvent()
              EndIf
            EndIf
          Else
            WindowEvent()
          EndIf
        EndIf
        doIntervals()
      Wend
    EndWith
  EndProcedure
  
  Procedure HandleWindowEvents(State = #True)
    With this
      \handleUI = State
    EndWith
  EndProcedure
  
  Procedure Execute(MainForm = #PB_Ignore, QuitOnClose = #False)
    With this
      \mainForm = MainForm
      \quitOnClose = QuitOnClose
      \execute = #True
      If \mainForm > -1 And IsWindow(\mainForm)
        \handleUI = #True
      EndIf
    EndWith
    BroadcastEvent(#Event_Initialise)
    mainLoop()
    BroadcastEvent(#Event_Release)
  EndProcedure
  
  Procedure Quit()
    With this
      \execute = #False
    EndWith
  EndProcedure  
  
EndModule


; - Demonstration of the App Module
; - Execute with Debugger On

;IncludePath "../include"
;XIncludeFile "ifz/app.pbi"

EnableExplicit

; - Something to pass through the Interval Callback
Structure IntervalInfo
  Count.i
EndStructure

Global Interval.IntervalInfo

; - A Custom Event (Inherits Standard Events)
Enumeration App::Events
  #Event_Custom
EndEnumeration

; - Do this when an Event is Called
Procedure OnEvent(Event, *EventInfo)
  Debug "Event Called.  Event ID: " + Str(Event)
EndProcedure

; - Call this Interval and do other things
Procedure OnInterval(*UserData)
  Protected *info.IntervalInfo = *UserData
  
  Debug "Interval Called.  Count: " + *info\Count
  *info\Count + 1
  
  Select *info\Count
    Case 3
      App::BroadcastEvent(#Event_Custom)
    Case 5      
      App::Quit()
  EndSelect
  
EndProcedure

; - Connect some Event Handlers
App::ConnectEventHandler(App::#Event_Initialise, @OnEvent())
App::ConnectEventHandler(App::#Event_Release, @OnEvent())
App::ConnectEventHandler(#Event_Custom, @OnEvent())

; - Create a loop function that executes every x milliseconds
App::RegisterInterval(@OnInterval(), 750, @Interval)

; - Start Main Loop
App::Execute()

Debug "Execution Ended"

Feedback welcome :)

P.S: This is going to be included in the GitHub repository you can access freely by following the link in my signature.
User avatar
Caronte3D
Addict
Addict
Posts: 1362
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: [Utility] Application Module (GUI, Loops & Events)

Post by Caronte3D »

A simple example of use would be good to understand.
I like the idea ;)
IndigoFuzz

Re: [Utility] Application Module (GUI, Loops & Events)

Post by IndigoFuzz »

There is an example included :)
Post Reply