;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                              ;;
;; Copyright (C) KolibriOS team 2010. All rights reserved.      ;;
;; Distributed under terms of the GNU General Public License    ;;
;;                                                              ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

$Revision$

include 'mousepointer.inc'

;==============================================================================
;///// public functions ///////////////////////////////////////////////////////
;==============================================================================

mouse.LEFT_BUTTON_FLAG   = 0001b
mouse.RIGHT_BUTTON_FLAG  = 0010b
mouse.MIDDLE_BUTTON_FLAG = 0100b

mouse.BUTTONS_MASK = \
  mouse.LEFT_BUTTON_FLAG or \
  mouse.RIGHT_BUTTON_FLAG or \
  mouse.MIDDLE_BUTTON_FLAG

mouse.WINDOW_RESIZE_N_FLAG = 000001b
mouse.WINDOW_RESIZE_W_FLAG = 000010b
mouse.WINDOW_RESIZE_S_FLAG = 000100b
mouse.WINDOW_RESIZE_E_FLAG = 001000b
mouse.WINDOW_MOVE_FLAG     = 010000b

mouse.WINDOW_RESIZE_SW_FLAG = \
  mouse.WINDOW_RESIZE_S_FLAG or \
  mouse.WINDOW_RESIZE_W_FLAG
mouse.WINDOW_RESIZE_SE_FLAG = \
  mouse.WINDOW_RESIZE_S_FLAG or \
  mouse.WINDOW_RESIZE_E_FLAG

align 4
;------------------------------------------------------------------------------
mouse_check_events: ;//////////////////////////////////////////////////////////
;------------------------------------------------------------------------------
;? Check if mouse buttons state or cursor position has changed and call
;? appropriate handlers
;------------------------------------------------------------------------------
        push    eax ebx

        mov     al, [BTN_DOWN]
        mov     bl, [mouse.state.buttons]
        and     al, mouse.BUTTONS_MASK
        mov     cl, al
        xchg    cl, [mouse.state.buttons]
        xor     bl, al
        push    eax ebx

        ; did any mouse button changed its state?
        or      bl, bl
        jz      .check_position

        ; yes it did, is that the first button of all pressed down?
        or      cl, cl
        jnz     .check_buttons_released

        ; yes it is, activate window user is pointing at, if needed
        call    mouse._.activate_sys_window_under_cursor

        ; NOTE: this code wouldn't be necessary if we knew window did
        ;       already redraw itself after call above
        or      eax, eax
        jz      @f

        and     [mouse.state.buttons], 0
        jmp     .exit

        ; is there any system button under cursor?
    @@: call    mouse._.find_sys_button_under_cursor
        or      eax, eax
        jz      .check_buttons_released

        ; yes there is, activate it and exit
        mov     [mouse.active_sys_button.pbid], eax
        mov     [mouse.active_sys_button.coord], ebx
        mov     cl, [mouse.state.buttons]
        mov     [mouse.active_sys_button.buttons], cl
        call    sys_button_activate_handler
        jmp     .exit

  .check_buttons_released:
        cmp     [mouse.state.buttons], 0
        jnz     .buttons_changed

        ; did we press some button earlier?
        cmp     [mouse.active_sys_button.pbid], 0
        je      .buttons_changed

        ; yes we did, deactivate it
        xor     eax, eax
        xchg    eax, [mouse.active_sys_button.pbid]
        mov     ebx, [mouse.active_sys_button.coord]
        mov     cl, [mouse.active_sys_button.buttons]
        push    eax ebx
        call    sys_button_deactivate_handler
        pop     edx ecx

        ; is the button under cursor the one we deactivated?
        call    mouse._.find_sys_button_under_cursor
        cmp     eax, ecx
        jne     .exit
        cmp     ebx, edx
        jne     .exit

        ; yes it is, perform associated action
        mov     cl, [mouse.active_sys_button.buttons]
        call    sys_button_perform_handler
        jmp     .exit

  .buttons_changed:
        test    byte[esp], mouse.LEFT_BUTTON_FLAG
        jz      @f
        mov     eax, [esp + 4]
        call    .call_left_button_handler

    @@: test    byte[esp], mouse.RIGHT_BUTTON_FLAG
        jz      @f
        mov     eax, [esp + 4]
        call    .call_right_button_handler

    @@: test    byte[esp], mouse.MIDDLE_BUTTON_FLAG
        jz      .check_position
        mov     eax, [esp + 4]
        call    .call_middle_button_handler

  .check_position:
        movzx   eax, word[MOUSE_X]
        movzx   ebx, word[MOUSE_Y]
        cmp     eax, [mouse.state.pos.x]
        jne     .position_changed
        cmp     ebx, [mouse.state.pos.y]
        je      .exit

  .position_changed:
        xchg    eax, [mouse.state.pos.x]
        xchg    ebx, [mouse.state.pos.y]

        call    mouse._.move_handler

  .exit:
        add     esp, 8
        pop     ebx eax
        ret

  .call_left_button_handler:
        test    eax, mouse.LEFT_BUTTON_FLAG
        jnz     mouse._.left_button_press_handler
        jmp     mouse._.left_button_release_handler

  .call_right_button_handler:
        test    eax, mouse.RIGHT_BUTTON_FLAG
        jnz     mouse._.right_button_press_handler
        jmp     mouse._.right_button_release_handler

  .call_middle_button_handler:
        test    eax, mouse.MIDDLE_BUTTON_FLAG
        jnz     mouse._.middle_button_press_handler
        jmp     mouse._.middle_button_release_handler

;==============================================================================
;///// private functions //////////////////////////////////////////////////////
;==============================================================================

uglobal
  mouse.state:
    .pos     POINT
    .buttons db ?

  ; NOTE: since there's no unique and lifetime-constant button identifiers,
  ;       we're using two dwords to identify each of them:
  ;       * pbid - process slot (high 8 bits) and button id (low 24 bits) pack
  ;       * coord - left (high 16 bits) and top (low 16 bits) coordinates pack
  align 4
  mouse.active_sys_button:
    .pbid    dd ?
    .coord   dd ?
    .buttons db ?

  align 4
  mouse.active_sys_window:
    .pslot      dd ?
    .old_box    BOX
    .new_box    BOX
    .delta      POINT
    .last_ticks dd ?
    .action     db ?
endg

align 4
;------------------------------------------------------------------------------
mouse._.left_button_press_handler: ;///////////////////////////////////////////
;------------------------------------------------------------------------------
;? Called when left mouse button has been pressed down
;------------------------------------------------------------------------------
        test    [mouse.state.buttons], not mouse.LEFT_BUTTON_FLAG
        jnz     .exit

        call    mouse._.find_sys_window_under_cursor
        call    mouse._.check_sys_window_actions
        mov     [mouse.active_sys_window.action], al
        or      eax, eax
        jz      .exit

        xchg    eax, edx
        test    dl, mouse.WINDOW_MOVE_FLAG
        jz      @f

        mov     eax, [timer_ticks]
        mov     ebx, eax
        xchg    ebx, [mouse.active_sys_window.last_ticks]
        sub     eax, ebx
        cmp     eax, 50
        jg      @f

        mov     [mouse.active_sys_window.last_ticks], 0
        call    sys_window_maximize_handler
        jmp     .exit

    @@: test    [edi + WDATA.fl_wstate], WSTATE_MAXIMIZED
        jnz     .exit
        mov     [mouse.active_sys_window.pslot], esi
        lea     eax, [edi + WDATA.box]
        mov     ebx, mouse.active_sys_window.old_box
        mov     ecx, BOX.sizeof
        call    memmove
        mov     ebx, mouse.active_sys_window.new_box
        call    memmove
        test    edx, mouse.WINDOW_MOVE_FLAG
        jz      @f

        call    .calculate_n_delta
        call    .calculate_w_delta
        jmp     .call_window_handler

    @@: test    dl, mouse.WINDOW_RESIZE_W_FLAG
        jz      @f
        call    .calculate_w_delta

    @@: test    dl, mouse.WINDOW_RESIZE_S_FLAG
        jz      @f
        call    .calculate_s_delta

    @@: test    dl, mouse.WINDOW_RESIZE_E_FLAG
        jz      .call_window_handler
        call    .calculate_e_delta

  .call_window_handler:
        mov     eax, mouse.active_sys_window.old_box
        call    sys_window_start_moving_handler

  .exit:
        ret

  .calculate_n_delta:
        mov     eax, [mouse.state.pos.y]
        sub     eax, [mouse.active_sys_window.old_box.top]
        mov     [mouse.active_sys_window.delta.y], eax
        ret

  .calculate_w_delta:
        mov     eax, [mouse.state.pos.x]
        sub     eax, [mouse.active_sys_window.old_box.left]
        mov     [mouse.active_sys_window.delta.x], eax
        ret

  .calculate_s_delta:
        mov     eax, [mouse.active_sys_window.old_box.top]
        add     eax, [mouse.active_sys_window.old_box.height]
        sub     eax, [mouse.state.pos.y]
        mov     [mouse.active_sys_window.delta.y], eax
        ret

  .calculate_e_delta:
        mov     eax, [mouse.active_sys_window.old_box.left]
        add     eax, [mouse.active_sys_window.old_box.width]
        sub     eax, [mouse.state.pos.x]
        mov     [mouse.active_sys_window.delta.x], eax
        ret

align 4
;------------------------------------------------------------------------------
mouse._.left_button_release_handler: ;/////////////////////////////////////////
;------------------------------------------------------------------------------
;? Called when left mouse button has been released
;------------------------------------------------------------------------------
        xor     esi, esi
        xchg    esi, [mouse.active_sys_window.pslot]
        or      esi, esi
        jz      .exit

        mov     eax, esi
        shl     eax, 5
        add     eax, window_data + WDATA.box
        mov     ebx, mouse.active_sys_window.old_box
        mov     ecx, BOX.sizeof
        call    memmove

        mov     eax, mouse.active_sys_window.old_box
        mov     ebx, mouse.active_sys_window.new_box
        call    sys_window_end_moving_handler

  .exit:
        ret

align 4
;------------------------------------------------------------------------------
mouse._.right_button_press_handler: ;//////////////////////////////////////////
;------------------------------------------------------------------------------
;? Called when right mouse button has been pressed down
;------------------------------------------------------------------------------
        test    [mouse.state.buttons], not mouse.RIGHT_BUTTON_FLAG
        jnz     .exit

        call    mouse._.find_sys_window_under_cursor
        call    mouse._.check_sys_window_actions
        test    al, mouse.WINDOW_MOVE_FLAG
        jz      .exit

        call    sys_window_rollup_handler

  .exit:
        ret

align 4
;------------------------------------------------------------------------------
mouse._.right_button_release_handler: ;////////////////////////////////////////
;------------------------------------------------------------------------------
;? Called when right mouse button has been released
;------------------------------------------------------------------------------
        ret

align 4
;------------------------------------------------------------------------------
mouse._.middle_button_press_handler: ;/////////////////////////////////////////
;------------------------------------------------------------------------------
;? Called when middle mouse button has been pressed down
;------------------------------------------------------------------------------
        ret

align 4
;------------------------------------------------------------------------------
mouse._.middle_button_release_handler: ;///////////////////////////////////////
;------------------------------------------------------------------------------
;? Called when middle mouse button has been released
;------------------------------------------------------------------------------
        ret

align 4
;------------------------------------------------------------------------------
mouse._.move_handler: ;////////////////////////////////////////////////////////
;------------------------------------------------------------------------------
;? Called when cursor has been moved
;------------------------------------------------------------------------------
;> eax = old x coord
;> ebx = old y coord
;------------------------------------------------------------------------------
        cmp     [mouse.active_sys_button.pbid], 0
        jnz     .exit

        mov     esi, [mouse.active_sys_window.pslot]
        or      esi, esi
        jz      .exit

        mov     eax, mouse.active_sys_window.new_box
        mov     ebx, mouse.active_sys_window.old_box
        mov     ecx, BOX.sizeof
        call    memmove

        mov     dl, [mouse.active_sys_window.action]
        test    dl, mouse.WINDOW_MOVE_FLAG
        jz      .check_resize_w

        mov     eax, [mouse.state.pos.x]
        sub     eax, [mouse.active_sys_window.delta.x]
        mov     [mouse.active_sys_window.new_box.left], eax
        mov     eax, [mouse.state.pos.y]
        sub     eax, [mouse.active_sys_window.delta.y]
        mov     [mouse.active_sys_window.new_box.top], eax

        mov     eax, [mouse.active_sys_window.new_box.left]
        or      eax, eax
        jge     @f
        xor     eax, eax
        mov     [mouse.active_sys_window.new_box.left], eax
    @@: add     eax, [mouse.active_sys_window.new_box.width]
        cmp     eax, [Screen_Max_X]
        jl      @f
        sub     eax, [Screen_Max_X]
        sub     [mouse.active_sys_window.new_box.left], eax
    @@: mov     eax, [mouse.active_sys_window.new_box.top]
        or      eax, eax
        jge     @f
        xor     eax, eax
        mov     [mouse.active_sys_window.new_box.top], eax
    @@: add     eax, [mouse.active_sys_window.new_box.height]
        cmp     eax, [Screen_Max_Y]
        jle     .call_window_handler
        sub     eax, [Screen_Max_Y]
        sub     [mouse.active_sys_window.new_box.top], eax
        jmp     .call_window_handler

  .check_resize_w:
        test    dl, mouse.WINDOW_RESIZE_W_FLAG
        jz      .check_resize_s

        mov     eax, [mouse.state.pos.x]
        sub     eax, [mouse.active_sys_window.delta.x]
        mov     [mouse.active_sys_window.new_box.left], eax
        sub     eax, [mouse.active_sys_window.old_box.left]
        sub     [mouse.active_sys_window.new_box.width], eax

        mov     eax, [mouse.active_sys_window.new_box.width]
        sub     eax, 127
        jge     @f
        add     [mouse.active_sys_window.new_box.left], eax
        mov     [mouse.active_sys_window.new_box.width], 127
    @@: mov     eax, [mouse.active_sys_window.new_box.left]
        or      eax, eax
        jge     .check_resize_s
        add     [mouse.active_sys_window.new_box.width], eax
        xor     eax, eax
        mov     [mouse.active_sys_window.new_box.left], eax

  .check_resize_s:
        test    dl, mouse.WINDOW_RESIZE_S_FLAG
        jz      .check_resize_e

        mov     eax, [mouse.state.pos.y]
        add     eax, [mouse.active_sys_window.delta.y]
        sub     eax, [mouse.active_sys_window.old_box.top]
        mov     [mouse.active_sys_window.new_box.height], eax

        push    eax
        mov     edi, esi
        shl     edi, 5
        add     edi, window_data
        call    window._.get_rolledup_height
        mov     ecx, eax
        pop     eax
        mov     eax, [mouse.active_sys_window.new_box.height]
        cmp     eax, ecx
        jge     @f
        mov     eax, ecx
        mov     [mouse.active_sys_window.new_box.height], eax
    @@: add     eax, [mouse.active_sys_window.new_box.top]
        cmp     eax, [Screen_Max_Y]
        jle     .check_resize_e
        sub     eax, [Screen_Max_Y]
        neg     eax
        add     [mouse.active_sys_window.new_box.height], eax
        mov     ecx, [Screen_Max_Y]
        cmp     ecx, eax
        jge     .check_resize_e
        mov     [mouse.active_sys_window.new_box.height], ecx

  .check_resize_e:
        test    dl, mouse.WINDOW_RESIZE_E_FLAG
        jz      .call_window_handler

        mov     eax, [mouse.state.pos.x]
        add     eax, [mouse.active_sys_window.delta.x]
        sub     eax, [mouse.active_sys_window.old_box.left]
        mov     [mouse.active_sys_window.new_box.width], eax

        mov     eax, [mouse.active_sys_window.new_box.width]
        cmp     eax, 127
        jge     @f
        mov     eax, 127
        mov     [mouse.active_sys_window.new_box.width], eax
    @@: add     eax, [mouse.active_sys_window.new_box.left]
        cmp     eax, [Screen_Max_X]
        jle     .call_window_handler
        sub     eax, [Screen_Max_X]
        neg     eax
        add     [mouse.active_sys_window.new_box.width], eax
        mov     ecx, [Screen_Max_X]
        cmp     ecx, eax
        jge     .call_window_handler
        mov     [mouse.active_sys_window.new_box.width], ecx

  .call_window_handler:
        mov     eax, mouse.active_sys_window.old_box
        mov     ebx, mouse.active_sys_window.new_box

        push    esi
        mov     esi, mouse.active_sys_window.old_box
        mov     edi, mouse.active_sys_window.new_box
        mov     ecx, BOX.sizeof / 4
        repe
        cmpsd
        pop     esi
        je      .exit

        mov     [mouse.active_sys_window.last_ticks], 0
        call    sys_window_moving_handler

  .exit:
        ret

align 4
;------------------------------------------------------------------------------
mouse._.find_sys_window_under_cursor: ;////////////////////////////////////////
;------------------------------------------------------------------------------
;? Find system window object which is currently visible on screen and has
;? mouse cursor within its bounds
;------------------------------------------------------------------------------
;< esi = process slot
;< edi = pointer to WDATA struct
;------------------------------------------------------------------------------
        mov     esi, [Screen_Max_X]
        inc     esi
        imul    esi, [mouse.state.pos.y]
        add     esi, [_WinMapAddress]
        add     esi, [mouse.state.pos.x]
        movzx   esi, byte[esi]
        mov     edi, esi
        shl     edi, 5
        add     edi, window_data
        ret

align 4
;------------------------------------------------------------------------------
mouse._.activate_sys_window_under_cursor: ;////////////////////////////////////
;------------------------------------------------------------------------------
;? <description>
;------------------------------------------------------------------------------
        ; activate and redraw window under cursor (if necessary)
        call    mouse._.find_sys_window_under_cursor
        movzx   esi, word[WIN_STACK + esi * 2]
        lea     esi, [WIN_POS + esi * 2]
        jmp     waredraw

align 4
;------------------------------------------------------------------------------
mouse._.find_sys_button_under_cursor: ;////////////////////////////////////////
;------------------------------------------------------------------------------
;? Find system button object which is currently visible on screen and has
;? mouse cursor within its bounds
;------------------------------------------------------------------------------
;< eax = pack[8(process slot), 24(button id)] or 0
;< ebx = pack[16(button x coord), 16(button y coord)]
;------------------------------------------------------------------------------
        push    ecx edx esi edi

        call    mouse._.find_sys_window_under_cursor
        mov     edx, esi

        ; check if any process button contains cursor
        mov     eax, [BTN_ADDR]
        mov     ecx, [eax]
        imul    esi, ecx, SYS_BUTTON.sizeof
        add     esi, eax
        inc     ecx
        add     esi, SYS_BUTTON.sizeof

  .next_button:
        dec     ecx
        jz      .not_found

        add     esi, -SYS_BUTTON.sizeof

        ; does it belong to our process?
        cmp     dx, [esi + SYS_BUTTON.pslot]
        jne     .next_button

        ; does it contain cursor coordinates?
        mov     eax, [mouse.state.pos.x]
        sub     eax, [edi + WDATA.box.left]
        sub     ax, [esi + SYS_BUTTON.left]
        jl      .next_button
        sub     ax, [esi + SYS_BUTTON.width]
        jge     .next_button
        mov     eax, [mouse.state.pos.y]
        sub     eax, [edi + WDATA.box.top]
        sub     ax, [esi + SYS_BUTTON.top]
        jl      .next_button
        sub     ax, [esi + SYS_BUTTON.height]
        jge     .next_button

        ; okay, return it
        shl     edx, 24
        mov     eax, dword[esi + SYS_BUTTON.id_hi - 2]
        mov     ax, [esi + SYS_BUTTON.id_lo]
        and     eax, 0x0ffffff
        or      eax, edx
        mov     ebx, dword[esi + SYS_BUTTON.left - 2]
        mov     bx, [esi + SYS_BUTTON.top]
        jmp     .exit

  .not_found:
        xor     eax, eax
        xor     ebx, ebx

  .exit:
        pop     edi esi edx ecx
        ret

align 4
;------------------------------------------------------------------------------
mouse._.check_sys_window_actions: ;////////////////////////////////////////////
;------------------------------------------------------------------------------
;? <description>
;------------------------------------------------------------------------------
;< eax = action flags or 0
;------------------------------------------------------------------------------
        ; is window movable?
        test    byte[edi + WDATA.cl_titlebar + 3], 0x01
        jnz     .no_action

        mov     eax, [mouse.state.pos.x]
        mov     ebx, [mouse.state.pos.y]
        sub     eax, [edi + WDATA.box.left]
        sub     ebx, [edi + WDATA.box.top]

        ; is there a window titlebar under cursor?
        push    eax
        call    window._.get_titlebar_height
        cmp     ebx, eax
        pop     eax
        jl      .move_action

        ; no there isn't, can it be resized then?
        mov     dl, [edi + WDATA.fl_wstyle]
        and     dl, 0x0f
        ; NOTE: dangerous optimization, revise if window types changed;
        ;       this currently implies only types 2 and 3 could be resized
        test    dl, 2
        jz      .no_action

        mov     ecx, [edi + WDATA.box.width]
        add     ecx, -window.BORDER_SIZE
        mov     edx, [edi + WDATA.box.height]
        add     edx, -window.BORDER_SIZE

        ; is it rolled up?
        test    [edi + WDATA.fl_wstate], WSTATE_ROLLEDUP
        jnz     .resize_w_or_e_action

        cmp     eax, window.BORDER_SIZE
        jl      .resize_w_action
        cmp     eax, ecx
        jg      .resize_e_action
        cmp     ebx, edx
        jle     .no_action

  .resize_s_action:
        cmp     eax, window.BORDER_SIZE + 10
        jl      .resize_sw_action
        add     ecx, -10
        cmp     eax, ecx
        jge     .resize_se_action
        mov     eax, mouse.WINDOW_RESIZE_S_FLAG
        jmp     .exit

  .resize_w_or_e_action:
        cmp     eax, window.BORDER_SIZE + 10
        jl      .resize_w_action.direct
        add     ecx, -10
        cmp     eax, ecx
        jg      .resize_e_action.direct
        jmp     .no_action

  .resize_w_action:
        add     edx, -10
        cmp     ebx, edx
        jge     .resize_sw_action
  .resize_w_action.direct:
        mov     eax, mouse.WINDOW_RESIZE_W_FLAG
        jmp     .exit

  .resize_e_action:
        add     edx, -10
        cmp     ebx, edx
        jge     .resize_se_action
  .resize_e_action.direct:
        mov     eax, mouse.WINDOW_RESIZE_E_FLAG
        jmp     .exit

  .resize_sw_action:
        mov     eax, mouse.WINDOW_RESIZE_SW_FLAG
        jmp     .exit

  .resize_se_action:
        mov     eax, mouse.WINDOW_RESIZE_SE_FLAG
        jmp     .exit

  .move_action:
        mov     eax, mouse.WINDOW_MOVE_FLAG
        jmp     .exit

  .no_action:
        xor     eax, eax

  .exit:
        ret