; ***************************************************************************
; copyright   : (C) 2026 by Martin Preuss
; email       : martin@libchipcard.de
;
; ***************************************************************************
; * This file is part of the project "AqHome".                              *
; * Please see toplevel file COPYING of that project for license details.   *
; ***************************************************************************

#ifndef AQH_AVR_GUI2_GUIAPP_ASM
#define AQH_AVR_GUI2_GUIAPP_ASM


; ***************************************************************************
; defines

.equ GUIAPP_WINDOWSTACK_ENTRIES        = 8

; Widget in flash

.equ GUIAPP_OFFS_BEGIN                 =  OBJECT_SIZE
.equ GUIAPP_OFFS_ROOTWINDOW_LO         =  GUIAPP_OFFS_BEGIN+0
.equ GUIAPP_OFFS_ROOTWINDOW_HI         =  GUIAPP_OFFS_BEGIN+1
.equ GUIAPP_OFFS_SCREENSAVER_LO        =  GUIAPP_OFFS_BEGIN+2
.equ GUIAPP_OFFS_SCREENSAVER_HI        =  GUIAPP_OFFS_BEGIN+3
.equ GUIAPP_OFFS_CURRENTWINDOW_LO      =  GUIAPP_OFFS_BEGIN+4
.equ GUIAPP_OFFS_CURRENTWINDOW_HI      =  GUIAPP_OFFS_BEGIN+5
.equ GUIAPP_OFFS_TOUCHWIDGET_LO        =  GUIAPP_OFFS_BEGIN+6
.equ GUIAPP_OFFS_TOUCHWIDGET_HI        =  GUIAPP_OFFS_BEGIN+7
.equ GUIAPP_OFFS_GUITIMER              =  GUIAPP_OFFS_BEGIN+8
.equ GUIAPP_OFFS_WINDOWSTACK_POS       =  GUIAPP_OFFS_BEGIN+9
.equ GUIAPP_OFFS_WINDOWSTACK_BEGIN     =  GUIAPP_OFFS_BEGIN+10
.equ GUIAPP_SIZE                       =  GUIAPP_OFFS_WINDOWSTACK_BEGIN+(GUIAPP_WINDOWSTACK_ENTRIES*2)


.equ GUIAPP_GUITIMER = 2



; ***************************************************************************
; data

.dseg

guiapp_touch_event:
  .byte WIDGET_TOUCH_SIZE



; ***************************************************************************
; code

.cseg



; ---------------------------------------------------------------------------
; @routine GuiApp_new @global
;
; @return CFLAG set of okay, cleared otherwise
; @return Y address of newly created object
; @param r16 value for OBJECT_OFFS_OPTS
; @clobbers any

GuiApp_new:
  ldi r24, LOW(GUIAPP_SIZE)
  ldi r25, HIGH(GUIAPP_SIZE)
  bigcall Object_Alloc              ; (!r16, !r17)
  brcc GuiApp_new_ret
  rcall GuiApp_Init                 ; (r16, r17, X)
  sec
GuiApp_new_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_Init @global
;
; @param Y address of object in SDRAM
; @param r16 value for OBJECT_OFFS_OPTS
; @clobbers r16, r17, X

GuiApp_Init:
  ; call base class
  bigcall OBJ_Init                          ; (r16, r17, X)

  ; set default signal map
  ldi r16, LOW(GuiApp_DefaultSignalmap*2)
  std Y+OBJECT_OFFS_SIGNALMAP_LO, r16
  ldi r16, HIGH(GuiApp_DefaultSignalmap*2)
  std Y+OBJECT_OFFS_SIGNALMAP_HI, r16

  ; create root window
  push yl
    push yh
      mov xl, yl
      mov xh, yh
      ldi r16, 0  ; opts
      ldi r17, 0  ; pack
      bigcall RootWindow_new

      ; always visible
      ldd r16, Y+OBJECT_OFFS_FLAGS
      sbr r16, (1<<WIDGET_FLAGS_VISIBLE_BIT) | (1<<WIDGET_FLAGS_LAYOUT_BIT) | (1<<WIDGET_FLAGS_DIRTY_BIT)
      std Y+OBJECT_OFFS_FLAGS, r16

      mov xl, yl
      mov xh, yh
    pop yh
  pop yl
  brcc GuiApp_Init_ret

  std Y+GUIAPP_OFFS_ROOTWINDOW_LO, xl
  std Y+GUIAPP_OFFS_ROOTWINDOW_HI, xh

  ldi r16, GUIAPP_GUITIMER
  std Y+GUIAPP_OFFS_GUITIMER, r16

GuiApp_Init_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_Every100ms @global
;
; @param Y address of object in SDRAM

GuiApp_Every100ms:
  ldi r16, OBJECT_SIGNAL_TIMER
  clr r17
  bigcall OBJ_HandleSignal
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_MsgReceived @global
;
; @param Y address of object in SDRAM

GuiApp_MsgReceived:
  ldi r16, OBJECT_SIGNAL_RECVMSG
  clr r17
  bigcall OBJ_HandleSignal
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_PreventScreenSaver @global
;
; @param Y address of object in SDRAM
; @clobbers any, !Y

GuiApp_PreventScreenSaver:
  rcall GuiApp_GetScreenSaver
  brcc GuiApp_PreventScreenSaver_ret
  push yl
    push yh
      mov yl, r18
      mov yh, r19
      ldi r16, WIDGET_SIGNAL_KEEPALIVE
      clr r17
      bigcall OBJ_HandleSignal
    pop yh
  pop yl
GuiApp_PreventScreenSaver_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_GrabTouchEvents
;
; @param Y address of object in SDRAM
; @param X byte address of widget grabbing touch events
; @return CFLAG set if grabbed, cleared otherwise
; @clobbers R16, R17

GuiApp_GrabTouchEvents:
  ldd r16, Y+GUIAPP_OFFS_TOUCHWIDGET_LO
  ldd r17, Y+GUIAPP_OFFS_TOUCHWIDGET_HI
  or r16, r17
  clc
  brne GuiApp_GrabTouchEvents_ret         ; only grab if not already grabbed!
  std Y+GUIAPP_OFFS_TOUCHWIDGET_LO, xl
  std Y+GUIAPP_OFFS_TOUCHWIDGET_HI, xh
  sec
GuiApp_GrabTouchEvents_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_UngrabTouchEvents
;
; @param Y address of object in SDRAM
; @param X byte address of widget ungrabbing touch events
; @return CFLAG set if ungrabbed, cleared otherwise
; @clobbers R16, R17

GuiApp_UngrabTouchEvents:
  ldd r16, Y+GUIAPP_OFFS_TOUCHWIDGET_LO
  ldd r17, Y+GUIAPP_OFFS_TOUCHWIDGET_HI
  eor r16, xl
  eor r17, xh
  or r16, r17
  clc
  brne GuiApp_UngrabTouchEvents_ret         ; only ungrab same widget!
  clr r16
  std Y+GUIAPP_OFFS_TOUCHWIDGET_LO, r16
  std Y+GUIAPP_OFFS_TOUCHWIDGET_HI, r16
  sec
GuiApp_UngrabTouchEvents_ret:
  ret
; @end





; ***************************************************************************
; getters and setters

; ---------------------------------------------------------------------------
; @routine GuiApp_GetRootWindow @global
;
; @param Y address of object in SDRAM
; @param CFLAG set if response is not a NULL pointer
; @return r19:r18 root window
; @clobbers none

GuiApp_GetRootWindow:
  ldd r18, Y+GUIAPP_OFFS_ROOTWINDOW_LO
  ldd r19, Y+GUIAPP_OFFS_ROOTWINDOW_HI
  sec
  tst r18
  brne GuiApp_GetRootWindow_ret
  tst r19
  brne GuiApp_GetRootWindow_ret
  clc
GuiApp_GetRootWindow_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_SetRootWindow @global
;
; @param Y address of object in SDRAM
; @param X address of new window

GuiApp_SetRootWindow:
  std Y+GUIAPP_OFFS_ROOTWINDOW_LO, xl
  std Y+GUIAPP_OFFS_ROOTWINDOW_HI, xh
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_GetCurrentWindow @global
;
; @param Y address of object in SDRAM
; @return CFLAG set if response is not a NULL pointer
; @return r19:r18 current window
; @clobbers none

GuiApp_GetCurrentWindow:
  ldd r18, Y+GUIAPP_OFFS_CURRENTWINDOW_LO
  ldd r19, Y+GUIAPP_OFFS_CURRENTWINDOW_HI
  sec
  tst r18
  brne GuiApp_GetCurrentWindow_ret
  tst r19
  brne GuiApp_GetCurrentWindow_ret
  clc
GuiApp_GetCurrentWindow_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_GetScreenSaver @global
;
; @param Y address of object in SDRAM
; @param CFLAG set if response is not a NULL pointer

GuiApp_GetScreenSaver:
  ldd r18, Y+GUIAPP_OFFS_SCREENSAVER_LO
  ldd r19, Y+GUIAPP_OFFS_SCREENSAVER_HI
  sec
  tst r18
  brne GuiApp_GetScreenSaver_ret
  tst r19
  brne GuiApp_GetScreenSaver_ret
  clc
GuiApp_GetScreenSaver_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_SetScreenSaver @global
;
; @param Y address of object in SDRAM
; @param X address of new window

GuiApp_SetScreenSaver:
  std Y+GUIAPP_OFFS_SCREENSAVER_LO, xl
  std Y+GUIAPP_OFFS_SCREENSAVER_HI, xh
  ret
; @end





; ***************************************************************************
; signal handlers


; ---------------------------------------------------------------------------
; @routine GuiApp_OnDestroy @global
;
; @param Y address of object in SDRAM
; @return CFLAG set if signal handled
; @clobbers any, !Y

GuiApp_OnDestroy:
  ; TODO (destroy rootWindow)

  ; Clear CFLAG so that destroy handlers of base classes are called
  ; after this routine returns. This way the last routine called will be
  ; OBJ_OnDestroy which then frees the whole SDRAM object on heap.
  clc
  ret
; @end


; ---------------------------------------------------------------------------
; @routine GuiApp_OnTimer
;
; @param Y address of object in SDRAM
; @clobbers any, !Y

GuiApp_OnTimer:
  rcall guiAppCheckTouch                ; (any, !Y)
  rcall guiAppSendTimerEvents           ; (any, !Y)
  rcall guiAppCheckSendGuiEvents        ; (any, !Y)
  rcall guiAppHandlePendingDestructions ; (any, !Y)
  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_OnMsgReceived @global
;
; @param Y address of object in SDRAM
; @param X pointer to message received in SDRAM

GuiApp_OnMsgReceived:
  rcall guiAppSendRootMsgEvents
  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_OnTouch
;
; @param Y address of object in SDRAM
; @param X pointer to touch object (see @ref WIDGET_TOUCH_OFFS_X_LO)
; @clobbers any, !Y

GuiApp_OnTouch:
  rcall guiAppSendTouchEvents
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_EnterWindow
; 
; @param Y address of object in SDRAM
; @param X main window to enter
; @return CFLAG set if new window active

GuiApp_EnterWindow:
  rcall GuiApp_GetCurrentWindow   ; r19:r18=current window
  brcc GuiApp_EnterWindow_l1      ; jmp if no current window
  push xl
    push xh
      push yl
        push yh
          mov yl, r18
          mov yh, r19
          ldi r16, WIDGET_SIGNAL_HIDE
          clr r17
          bigcall OBJ_TreeHandleSignalChildenFirst
          mov xl, yl
          mov xh, yh
        pop yh
      pop yl
      rcall guiAppPushMainWindow  ; push current window onto stack
    pop xh
  pop xl
  brcc GuiApp_EnterWindow_ret

GuiApp_EnterWindow_l1:
  rcall guiAppSetCurrentWindow
  push yl
    push yh
      mov yl, xl
      mov yh, xh
      ldi r16, WIDGET_SIGNAL_SHOW
      clr r17
      bigcall OBJ_TreeHandleSignal

    pop yh
  pop yl
  sec
GuiApp_EnterWindow_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine GuiApp_LeaveWindow
; 
; @param Y address of object in SDRAM

GuiApp_LeaveWindow:
  rcall GuiApp_GetCurrentWindow       ; r19:r18=current window
  brcc GuiApp_LeaveWindow_ret         ; jmp if no current window
  push yl
    push yh
      mov yl, r18
      mov yh, r19
      ldi r16, WIDGET_SIGNAL_HIDE
      clr r17
      bigcall OBJ_TreeHandleSignalChildenFirst
    pop yh
  pop yl
GuiApp_LeaveWindow_l1:
  rcall guiAppPopMainWindow           ; X=popped window
  brcc GuiApp_LeaveWindow_noPrevious
  rcall guiAppSetCurrentWindow
  push yl
    push yh
      mov yl, xl
      mov yh, xh
      ldi r16, WIDGET_SIGNAL_SHOW
      clr r17
      bigcall OBJ_TreeHandleSignal
    pop yh
  pop yl
  rjmp GuiApp_LeaveWindow_ret
GuiApp_LeaveWindow_noPrevious:
  clr xl
  clr xh
  rcall guiAppSetCurrentWindow

GuiApp_LeaveWindow_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine guiAppSetCurrentWindow
;
; @param Y address of object in SDRAM
; @param X address of new window

guiAppSetCurrentWindow:
  std Y+GUIAPP_OFFS_CURRENTWINDOW_LO, xl
  std Y+GUIAPP_OFFS_CURRENTWINDOW_HI, xh
  ret
; @end



; ---------------------------------------------------------------------------
; @routine guiAppPushMainWindow
;
; @param Y address of object in SDRAM
; @param X window to push on stack and activate
; @return CFLAG set if pushed, cleared on error

guiAppPushMainWindow:
  ldd r16, Y+GUIAPP_OFFS_WINDOWSTACK_POS
  cpi r16, GUIAPP_WINDOWSTACK_ENTRIES
  brcc GuiApp_PushMainWindow_ret

  mov zl, yl
  mov zh, yh
  adiw zh:zl, GUIAPP_OFFS_WINDOWSTACK_BEGIN
  clr r17
  lsl r16
  rol r17
  add zl, r16
  adc zh, r17
  st Z+, xl
  st Z, xh
  ldd r16, Y+GUIAPP_OFFS_WINDOWSTACK_POS
  inc r16
  std Y+GUIAPP_OFFS_WINDOWSTACK_POS, r16
  sec
GuiApp_PushMainWindow_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine guiAppPopMainWindow
;
; @param Y address of object in SDRAM
; @return CFLAG set if popped, cleared on error
; @return X window popped from stack

guiAppPopMainWindow:
  ldd r16, Y+GUIAPP_OFFS_WINDOWSTACK_POS
  tst r16
  clc
  breq guiAppPopMainWindow_ret
  dec r16
  std Y+GUIAPP_OFFS_WINDOWSTACK_POS, r16

  mov zl, yl
  mov zh, yh
  adiw zh:zl, GUIAPP_OFFS_WINDOWSTACK_BEGIN
  clr r17
  lsl r16
  rol r17
  add zl, r16
  adc zh, r17
  ld xl, Z+
  ld xh, Z
  sec
guiAppPopMainWindow_ret:
  ret
; @end




; ---------------------------------------------------------------------------
; @routine guiAppSendTimerEvents
;
; @param Y ptr to GUIAPP
; @clobbers any, !Y

guiAppSendTimerEvents:
  ldi r16, OBJECT_SIGNAL_TIMER
  clr r17
  ldi r20, (1<<OBJECT_OPTS_TIMER_BIT)
  ldi r21, (1<<OBJECT_OPTS_TIMER_BIT)
  rcall guiAppSendRootEventsIfOpts
  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine guiAppCheckSendGuiEvents
;
; Send GUI events to current window if timer elapsed.
;
; @param Y ptr to GUIAPP
; @clobbers any, !Y

guiAppCheckSendGuiEvents:
  push yl
    push yh
      rcall GuiApp_GetRootWindow
      brcc guiAppCheckSendGuiEvents_done

      ldd r16, Y+GUIAPP_OFFS_GUITIMER
      tst r16
      breq guiAppCheckSendGuiEvents_done
      dec r16
      std Y+GUIAPP_OFFS_GUITIMER, r16
      brne guiAppCheckSendGuiEvents_done
      
      ; timer elapsed, send layout events, draw events
      push yl
        push yh
          mov yl, r18
          mov yh, r19
          ldi r16, WIDGET_SIGNAL_LAYOUT
          clr r17
          ldi r20, (1<<WIDGET_FLAGS_VISIBLE_BIT) | (1<<WIDGET_FLAGS_LAYOUT_BIT)
          bigcall OBJ_TreeHandleSignalIfFlagsSet  ; (any, !Y)

          ldi r16, WIDGET_SIGNAL_DRAW
          clr r17
          ldi r20, (1<<WIDGET_FLAGS_VISIBLE_BIT) | (1<<WIDGET_FLAGS_DIRTY_BIT)
          bigcall OBJ_TreeHandleSignalIfFlagsSet  ; (any, !Y)

        pop yh
      pop yl  
      ldi r16, GUIAPP_GUITIMER
      std Y+GUIAPP_OFFS_GUITIMER, r16
      
guiAppCheckSendGuiEvents_done:
    pop yh
  pop yl  
  ret
; @end



; ---------------------------------------------------------------------------
; @routine guiAppHandlePendingDestructions
;
; Send GUI events to current window if timer elapsed.
;
; @param Y ptr to GUIAPP
; @clobbers any, !Y

guiAppHandlePendingDestructions:
  push yl
    push yh
      rcall GuiApp_GetRootWindow
      brcc guiAppHandlePendingDestructions_done
      mov yl, r18
      mov yh, r19
      bigcall OBJ_TreeDestroyMarked
guiAppHandlePendingDestructions_done:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine guiAppSendTouchEvents
;
; Send touch events to current window or grabbing widget.
;
; @param Y ptr to GUIAPP
; @param X pointer to touch object (see @ref WIDGET_TOUCH_OFFS_X_LO)
; @clobbers any, !Y

guiAppSendTouchEvents:
  push yl
    push yh
      ldd r18, Y+GUIAPP_OFFS_TOUCHWIDGET_LO
      ldd r19, Y+GUIAPP_OFFS_TOUCHWIDGET_HI
      mov r16, r18
      or r16, r19
      breq guiAppSendTouchEvents_sendToAll
      mov yl, r18
      mov yh, r19
      ldi r16, WIDGET_SIGNAL_TOUCH
      clr r17
      bigcall OBJ_HandleSignal
      rjmp guiAppSendTouchEvents_done
guiAppSendTouchEvents_sendToAll:
      rcall GuiApp_GetCurrentWindow   ; r19:r18=current window (none)
      brcc guiAppSendTouchEvents_done
      ; send touch signal
      mov yl, r18
      mov yh, r19
      ldi r16, WIDGET_SIGNAL_TOUCH
      clr r17
      ldi r20, (1<<WIDGET_OPTS_INPUT_BIT)
      ldi r21, (1<<WIDGET_OPTS_INPUT_BIT)
      bigcall OBJ_TreeHandleSignalIfMatchingOpts
guiAppSendTouchEvents_done:
    pop yh
  pop yl
  ; send keepAlive to screensaver
  rcall GuiApp_PreventScreenSaver
  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine guiAppSendRootMsgEvents
;
; Send touch events to root window.
;
; @param Y ptr to GUIAPP
; @param X pointer to message received in SDRAM
; @clobbers any, !Y

guiAppSendRootMsgEvents:
  ldi r16, OBJECT_SIGNAL_RECVMSG
  adiw xh:xl, NETMSG_OFFS_CMD
  ld r17, X
  sbiw xh:xl, NETMSG_OFFS_CMD
  ldi r20, (1<<OBJECT_OPTS_MSGRECV_BIT)
  ldi r21, (1<<OBJECT_OPTS_MSGRECV_BIT)
  rcall guiAppSendRootEventsIfOpts
  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine guiAppSendRootEventsIfOpts
;
; Send events to all widgets with matching OPTS_LO.
;
; @param Y ptr to GUIAPP
; @param  R16 signal number
; @param  R17 selector
; @param  xl param1
; @param  xh param2
; @param  r20 mask for OBJECT_OFFS_OPTS
; @param  r21 value for OBJECT_OFFS_OPTS to match
; @clobbers any, !Y

guiAppSendRootEventsIfOpts:
  push yl
    push yh
      rcall GuiApp_GetRootWindow   ; r19:r18=root window (none)
      brcc guiAppSendRootEventsIfOpts_done
      ; send signal to root window and all below
      mov yl, r18
      mov yh, r19
      bigcall OBJ_TreeHandleSignalIfMatchingOpts
guiAppSendRootEventsIfOpts_done:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine guiAppCheckTouch
;
; @param Y ptr to GUIAPP
; @clobbers any, !Y

guiAppCheckTouch:
  bigcall Display_InputGetState    ; r16=flags, r5:r4=x, r7:r6=y
  mov r17, r16
  andi r17, (1<<DISPLAY_IFLAGS_CHGCOORD_BIT) | (1<<DISPLAY_IFLAGS_CHGPRESS_BIT)
  breq guiAppCheckTouch_ret
  ldi xl, LOW(guiapp_touch_event)
  ldi xh, HIGH(guiapp_touch_event)
  st X+, r4  ; X
  st X+, r5
  st X+, r6  ; Y
  st X+, r7
  st X, r16  ; flags
  sbiw xh:xl, 4 ; (4 times incremented)
  ; send touch signal
  ldi r16, WIDGET_SIGNAL_TOUCH
  clr r17
  bigcall OBJ_HandleSignal
guiAppCheckTouch_ret:
  ret
; @end





; ***************************************************************************
; data in FLASH

GuiApp_DefaultSignalmap:
  ; header
  .dw Object_DefaultSignalmap*2  ; next table to use
  ; entries
  .db 0, OBJECT_SIGNAL_TIMER,   LOW(GuiApp_OnTimer),       HIGH(GuiApp_OnTimer)
  .db 0, OBJECT_SIGNAL_RECVMSG, LOW(GuiApp_OnMsgReceived), HIGH(GuiApp_OnMsgReceived)
  .db 0, WIDGET_SIGNAL_TOUCH,   LOW(GuiApp_OnTouch),       HIGH(GuiApp_OnTouch)
  .db 0, OBJECT_SIGNAL_DESTROY, LOW(GuiApp_OnDestroy),     HIGH(GuiApp_OnDestroy)
  .db 0, 0, 0, 0  ; end of table



#endif
