; _______________________________________________________________________________________
;|                                                                                       |
;| ..::FreshLib::..  Free, open source. Licensed under "BSD 2-clause" license."          |
;|_______________________________________________________________________________________|
;
;  Description: TTabBar object class
;
;  Target OS: Any
;
;  Dependencies:
;_________________________________________________________________________________________
module "TTabBar library"


tsNeutral = 0
tsHovered = 1
tsSelected = 2


struct TTab
  .caption      dd ?
  .icon         dd ?
  ._width       dd ?
  ._x           dd ?
  .user_data    dd ?
ends


object TTabBar, TWindow

; private fields

  ._canvas       dd ?
  ._canvas_valid dd ?

  ._items        dd ?            ; TArray of TTab structures.

  ._hovered      dd ?
  ._selected     dd ?

  .x_start       dd ?

  ._on_sel_change dd ?

; params

  param  .Selected, ._selected, .SetSelected
  param  .Hovered,  ._hovered, .SetHovered

  param  .OnSelChange, ._on_sel_change, ._on_sel_change

; methods

  method .SetSelected, .value
  method .SetHovered, .value

  method .ScrollToSelected

  method .Next
  method .Prev

  method .Create, .Parent
  method .Destroy
  method .ForceRefresh

  method .HitTest, .x, .y

  method .InsertTab, .index, .caption, .icon, .userdata
  method .DeleteTab, .index, .userFreeCallback

  method .__UpdateImages
  method ._UpdateX, .from
  method ._UpdateW, .tab
  method ._RenderView

  method .DrawTab, .pTab, .state

  method .SelfPaint, .pDstImage, .xDst, .yDst, .xSrc, .ySrc, .width, .height

  method .Autosize

  ; system events methods

  method .EventMouseLeave
  method .EventMouseMove, .x, .y, .kbdState
  method .EventButtonPress, .button, .kbdState, .x, .y
  method .EventScroll, .direction, .command, .value

endobj


interface TTabBar.OnSelChange, .self, .pTabUnselected, .iTabUnselected, .pTabSelected, .iTabSelected


; new

method TTabBar.Create
begin
        inherited [.Parent]
        push    esi

        mov     esi, [.self]
        mov     [esi+TTabBar._cursor], mcArrow
        mov     [esi+TTabBar.__want_focus], FALSE

        stdcall CreateArray, sizeof.TTab
        mov     [esi+TTabBar._items], eax

        xor     eax, eax
        dec     eax
        mov     [esi+TTabBar._selected], eax
        mov     [esi+TTabBar._hovered], eax

        pop     esi
        return
endp


method TTabBar.Destroy
begin
        pushad
        mov     esi, [.self]
        stdcall DestroyImage, [esi+TTabBar._canvas]
        popad
        inherited
        return
endp



method TTabBar.ForceRefresh
begin
        mov     eax, [.self]
        and     [eax+TTabBar._canvas_valid], 0
        inherited
        return
endp


method TTabBar.SelfPaint
begin
        pushad
        mov     esi, [.self]

        cmp     [esi+TTabBar._canvas_valid], 0
        jne     @f
        exec    esi, TTabBar:_RenderView
@@:
        mov     ebx, [esi+TTabBar._canvas]
        test    ebx, ebx
        jz      .finish

        stdcall DrawSolidRect, [.pDstImage], [.xDst], [.yDst], [.width], [.height], [GUI.clDialogBk]

        mov     eax, [esi+TTabBar.x_start]
        add     eax, [.xSrc]
        stdcall BlendImage, [.pDstImage], [.xDst], [.yDst], ebx, eax, [.ySrc], [.width], [.height]

.finish:
        popad
        inherited [.pDstImage], [.xDst], [.yDst], [.xSrc], [.ySrc], [.width], [.height]
        return
endp


method TTabBar.HitTest
begin
        pushad

        mov     esi, [.self]
        mov     edi, [esi+TTabBar._items]
        mov     eax, [esi+TTabBar.x_start]
        add     [.x], eax

        mov     ecx, [edi+TArray.count]
        lea     edi, [edi+TArray.array]
        xor     edx, edx

.loop:
        dec     ecx
        js      .not_found

        mov     eax, [edi+TTab._width]
        cmp     [.x], eax
        jl      .found

        sub     [.x], eax
        inc     edx
        add     edi, sizeof.TTab
        jmp     .loop

.found:
        mov     [esp+4*regEAX], edi
        mov     [esp+4*regEDX], edx
        clc
        popad
        return

.not_found:
        stc
        popad
        return
endp


method TTabBar.InsertTab
begin
        pushad

        mov     esi, [.self]
        mov     edx, [esi+TTabBar._items]
        mov     eax, [.index]
        cmp     eax, [edx+TArray.count]
        cmova   eax, [edx+TArray.count]
        mov     [.index], eax

        stdcall InsertArrayItems, edx, eax, 1
        mov     [esi+TTabBar._items], edx
        mov     edi, eax

        stdcall StrDup, [.caption]
        mov     [edi+TTab.caption], eax

        mov     eax, [.icon]
        mov     edx, [.userdata]
        mov     [edi+TTab.icon], eax
        mov     [edi+TTab.user_data], edx

        exec    esi, TTabBar:_UpdateW, edi
        exec    esi, TTabBar:_UpdateX, [.index]

        exec    esi, TTabBar:__UpdateImages

        and     [esi+TTabBar._canvas_valid], 0
        exec    esi, TTabBar:RectChanged2, 0
        popad
        return
endp




method TTabBar.DeleteTab      ;, .index, .userFreeCallback
begin
        pushad

        mov     esi, [.self]
        mov     ebx, [esi+TTabBar._selected]
        set     esi, TTabBar:Selected, -1

        mov     edx, [esi+TTabBar._items]

        stdcall GetArrayItem, edx, [.index]
        jc      .finish

        stdcall StrDel, [eax+TTab.caption]
        cmp     [.userFreeCallback], 0
        je      .element_empty

        pushad
        stdcall [.userFreeCallback], [eax+TTab.user_data]
        popad

        and     [eax+TTab.user_data], 0

.element_empty:

        stdcall DeleteArrayItems, edx, [.index], 1
        mov     [esi+TTabBar._items], edx

        cmp     ebx, [.index]
        jb      .selected_ok

        dec     ebx
        jns     .selected_ok

        xor     ebx, ebx
        cmp     [edx+TArray.count], ebx
        jne     .selected_ok

        dec     ebx

.selected_ok:
        set     esi, TTabBar:Selected, ebx

        exec    esi, TTabBar:_UpdateX, [.index]
        exec    esi, TTabBar:__UpdateImages

        and     [esi+TTabBar._canvas_valid], 0
        exec    esi, TTabBar:RectChanged2, 0

.finish:
        popad
        return
endp


method TTabBar._UpdateX
begin
        pushad
        mov     esi, [.self]
        mov     edx, [esi+TTabBar._items]
        mov     edi, [edx+TArray.count]

        xor     ecx, ecx

        mov     ebx, [.from]
        cmp     ebx, edi
        jae     .finish

        mov     eax, ebx
        imul    eax, sizeof.TTab
        sub     eax, sizeof.TTab
        lea     edx, [edx+TArray.array+eax]
        js      .set

        mov     ecx, [edx+TTab._x]

.loop:
        add     ecx, [edx+TTab._width]

.set:
        inc     ebx
        add     edx, sizeof.TTab
        mov     [edx+TTab._x], ecx
        cmp     ebx, edi
        jb      .loop

.finish:
        popad
        return
endp



method TTabBar._UpdateW
begin
        pushad
        mov     esi, [.tab]
        mov     eax, [esi+TTab.icon]

        xor     ecx, ecx
        xor     edx, edx
        mov     ebx, [GUI.tabMarginX]
        test    eax, eax
        jz      @f
        mov     ecx, [eax+TImage.wrapW]
        mov     edx, ebx
@@:
        shl     ebx, 1
        add     ecx, edx
        add     ecx, ebx
        mov     [edi+TTab._width], ecx

        stdcall StrPtr, [esi+TTab.caption]
        stdcall GetTextBounds, eax, -1, [GUI.DefaultFont]
        cmp     eax, [GUI.maxTabWidth]
        cmova   eax, [GUI.maxTabWidth]

        add     [edi+TTab._width], eax

        popad
        return
endp



method TTabBar._RenderView
begin
        pushad

        mov     esi, [.self]
        mov     edi, [esi+TTabBar._items]
        mov     ebx, [edi+TArray.count]

        mov     ecx, [esi+TTabBar._canvas]
        jecxz   .finish

        stdcall DrawSolidRect, ecx, 0, 0, [ecx+TImage.wrapW], [ecx+TImage.wrapH], 0

        lea     edi, [edi + TArray.array]
        xor     ecx, ecx
        dec     ecx

.loop:
        inc     ecx
        cmp     ecx, ebx
        jae     .finish

        mov     eax, tsSelected
        cmp     ecx, [esi+TTabBar._selected]
        je      .drawit

        mov     eax, tsHovered
        cmp     ecx, [esi+TTabBar._hovered]
        je      .drawit

        mov     eax, tsNeutral

.drawit:
        exec    esi, TTabBar:DrawTab, edi, eax

        add     edi, sizeof.TTab
        jmp     .loop


.finish:
        inc     [esi+TTabBar._canvas_valid]
        popad
        return
endp




method TTabBar.DrawTab;, .pTab, .state
.bounds TBounds
begin
        pushad

        mov     esi, [.pTab]
        mov     edi, [.self]
        mov     edi, [edi+TTabBar._canvas]
        test    edi, edi
        jz      .finish

        mov     eax, [esi+TTab._x]
        xor     ecx, ecx
        mov     [.bounds.x], eax
        mov     [.bounds.y], ecx

        mov     eax, [esi+TTab._width]
        mov     ecx, [edi+TImage.wrapH]
        mov     [.bounds.width], eax
        mov     [.bounds.height], ecx

        mov     ebx, [.state]
        stdcall DrawSolidRect, edi, [.bounds.x], [.bounds.y], [.bounds.width], [.bounds.height], [GUI.clTabBk+4*ebx]

        cmp     ebx, tsSelected
        jne     .selected_ok

        stdcall DrawSolidRect, edi, [.bounds.x], 0, [.bounds.width], 2, [GUI.clBorderFocused]

.selected_ok:
        add     [.bounds.y], 2
        sub     [.bounds.height], 2

        mov     eax, [GUI.tabMarginX]
        mov     ecx, [GUI.tabMarginY]
        add     [.bounds.x], eax
        add     [.bounds.y], ecx
        shl     eax, 1
        shl     ecx, 1
        sub     [.bounds.width], eax
        sub     [.bounds.height], ecx

        mov     ebx, [esi+TTab.icon]
        test    ebx, ebx
        jz      .icon_ok

        mov     ecx, [ebx+TImage.wrapW]
        stdcall BlendImage, edi, [.bounds.x], [.bounds.y], ebx, 0, 0, ecx, [ebx+TImage.wrapH]

        mov     eax, [GUI.tabMarginX]
        add     [.bounds.x], ecx
        add     [.bounds.x], eax
        sub     [.bounds.width], ecx

.icon_ok:

        stdcall DrawTextBox, edi, [esi+TTab.caption], [.bounds.x], [.bounds.y], [.bounds.width], [.bounds.height], 0, dtfAlignLeft or dtfAlignMiddle or dtfSingleLine, [GUI.DefaultFont], [GUI.clTabTxt]

.finish:
        popad
        return
endp



method TTabBar.Autosize
begin
        inherited
        mov     edx, [.self]
        mov     edx, [edx+TTabBar._canvas]
        test    edx, edx
        jz      .finish
        mov     edx, [edx+TImage.wrapH]
.finish:
        return
endp



method TTabBar.EventMouseLeave
begin
        set     [.self], TTabBar:Hovered, -1
        exec    [.self], TTabBar:ScrollToSelected
        return
endp


method TTabBar.EventMouseMove
begin
        push    eax edx
        or      edx, -1
        exec    [.self], TTabBar:HitTest, [.x], [.y]
        set     [.self], TTabBar:Hovered, edx
        pop     edx eax
        return
endp


method TTabBar.EventButtonPress
begin
        push    eax edx
        or      edx, -1
        exec    [.self], TTabBar:HitTest, [.x], [.y]
        set     [.self], TTabBar:Selected, edx
        pop     edx eax
        return
endp


method TTabBar.EventScroll
begin
        pushad
        mov     esi, [.self]
        mov     edx, [esi+TTabBar._items]

        mov     ecx, [esi+TTabBar.x_start]

        cmp     [.command], scWheelUp
        je      .scrollup

        cmp     [.command], scWheelDn
        je      .scrolldn

.finish:
        popad
        return

.scrolldn:
        mov     eax, [edx+TArray.count]
        dec     eax
        js      .finish

        imul    eax, sizeof.TTab
        lea     eax, [edx+eax+TArray.array]
        mov     ebx, [eax+TTab._x]
        add     ebx, [eax+TTab._width]
        sub     ebx, [esi+TTabBar._width]
        js      .finish

        add     ecx, 32
        cmp     ecx, ebx
        cmova   ecx, ebx

.set:
        mov     [esi+TTabBar.x_start], ecx
        exec    esi, TTabBar:RectChanged2, 0
        jmp     .finish


.scrollup:
        xor     eax, eax
        sub     ecx, 32
        cmovs   ecx, eax
        jmp     .set
endp



method TTabBar.SetHovered
begin
        pushad
        mov     esi, [.self]
        mov     edi, 64
        mov     eax, [esi+TTabBar._canvas]
        test    eax, eax
        jnz     .height_ok

        mov     edi, [eax+TImage.wrapH]

.height_ok:

        mov     edx, [.value]
        cmp     edx, [esi+TTabBar._hovered]
        je      .finish

        xchg    edx, [esi+TTabBar._hovered]
        test    edx, edx
        js      .old_ok

        cmp     edx, [esi+TTabBar._selected]
        je      .old_ok

        mov     eax, [esi+TTabBar._items]
        cmp     edx, [eax+TArray.count]
        jae     .old_ok

        imul    edx, sizeof.TTab
        lea     edx, [eax+edx+TArray.array]

        exec    esi, TTabBar:DrawTab, edx, tsNeutral

        mov     eax, [edx+TTab._x]
        sub     eax, [esi+TTabBar.x_start]
        exec    esi, TTabBar:RectChangedXY2, eax, 0, [edx+TTab._width], edi

.old_ok:
        mov     edx, [esi+TTabBar._hovered]
        test    edx, edx
        js      .finish

        cmp     edx, [esi+TTabBar._selected]
        je      .finish

        mov     eax, [esi+TTabBar._items]
        cmp     edx, [eax+TArray.count]
        jae     .finish

        imul    edx, sizeof.TTab
        lea     edx, [eax+edx+TArray.array]

        exec    esi, TTabBar:DrawTab, edx, tsHovered

        mov     eax, [edx+TTab._x]
        sub     eax, [esi+TTabBar.x_start]
        exec    esi, TTabBar:RectChangedXY2, eax, 0, [edx+TTab._width], edi

.finish:
        popad
        return
endp



method TTabBar.SetSelected
.tab_prev   dd ?
.tab_prev_i dd ?
.tab_new    dd ?
.tab_new_i  dd ?
begin
        pushad

        xor     eax, eax
        mov     [.tab_prev], eax
        mov     [.tab_new], eax
        dec     eax
        mov     [.tab_prev_i], eax
        mov     [.tab_new_i], eax

        mov     esi, [.self]
        mov     edi, 64
        mov     eax, [esi+TTabBar._canvas]
        test    eax, eax
        jnz     .height_ok

        mov     edi, [eax+TImage.wrapH]

.height_ok:

        mov     edx, [.value]
        cmp     edx, [esi+TTabBar._selected]
        je      .finish

        test    edx, edx
        js      .value_ok

        mov     eax, [esi+TTabBar._items]
        cmp     edx, [eax+TArray.count]
        jae     .finish

.value_ok:
        xchg    edx, [esi+TTabBar._selected]
        test    edx, edx
        js      .old_ok

        mov     eax, [esi+TTabBar._items]
        cmp     edx, [eax+TArray.count]
        jae     .old_ok

        mov     ebx, edx
        imul    ebx, sizeof.TTab
        lea     ebx, [eax+ebx+TArray.array]

        exec    esi, TTabBar:DrawTab, ebx, tsNeutral

        mov     eax, [ebx+TTab._x]
        sub     eax, [esi+TTabBar.x_start]
        exec    esi, TTabBar:RectChangedXY2, eax, 0, [ebx+TTab._width], edi

        mov     [.tab_prev], ebx
        mov     [.tab_prev_i], edx

.old_ok:
        mov     edx, [esi+TTabBar._selected]
        test    edx, edx
        js      .finish

        mov     eax, [esi+TTabBar._items]
        cmp     edx, [eax+TArray.count]
        jae     .finish

        mov     ebx, edx
        imul    ebx, sizeof.TTab
        lea     ebx, [eax+ebx+TArray.array]

        exec    esi, TTabBar:DrawTab, ebx, tsSelected
        exec    esi, TTabBar:ScrollToSelected

        mov     eax, [ebx+TTab._x]
        sub     eax, [esi+TTabBar.x_start]
        exec    esi, TTabBar:RectChangedXY2, eax, 0, [ebx+TTab._width], edi

        mov     [.tab_new], ebx
        mov     [.tab_new_i], edx

.finish:
        mov     eax, [.tab_prev]
        or      eax, [.tab_new]
        jz      .exit

        get     eax, esi, TTabBar:OnSelChange
        test    eax, eax
        jz      .exit

; interface TTabBar.OnSelChange, .self, .pTabUnselected, .iTabUnselected, .pTabSelected, .iTabSelected

        stdcall eax, esi, [.tab_prev], [.tab_prev_i], [.tab_new], [.tab_new_i]

.exit:
        popad
        return
endp


method TTabBar.Next
begin
        pushad

        mov     esi, [.self]
        mov     ecx, [esi+TTabBar._items]

        or      eax, -1
        mov     edx, [ecx+TArray.count] ; to
        test    edx, edx
        jz      .set

        xor     ebx, ebx        ; from

        mov     eax, [esi+TTabBar._selected]
        inc     eax

        cmp     eax, ebx
        cmovl   eax, ebx

        cmp     eax, edx
        cmovge  eax, ebx

.set:
        set     [.self], TTabBar:Selected, eax

        popad
        return
endp


method TTabBar.Prev
begin
        pushad

        mov     esi, [.self]
        mov     ecx, [esi+TTabBar._items]

        or      eax, -1
        mov     edx, [ecx+TArray.count] ; to
        test    edx, edx
        jz      .set

        dec     edx

        mov     eax, [esi+TTabBar._selected]
        dec     eax
        cmovs   eax, edx

.set:
        set     [.self], TTabBar:Selected, eax

        popad
        return
endp






method TTabBar.ScrollToSelected
begin
        pushad
        mov     esi, [.self]
        mov     edx, [esi+TTabBar._items]
        mov     ecx, [esi+TTabBar._selected]
        cmp     ecx, [edx+TArray.count]
        jae     .finish

        imul    ecx, sizeof.TTab
        lea     ecx, [edx+ecx+TArray.array]

        mov     eax, [ecx+TTab._x]
        sub     eax, [esi+TTabBar.x_start]
        js      .scroll_right
        cmp     eax, [esi+TTabBar._width]
        jge     .scroll_left

        add     eax, [ecx+TTab._width]
        cmp     eax, [esi+TTabBar._width]
        jge     .scroll_left

.finish:
        popad
        return

.scroll_right:

        mov     eax, [ecx+TTab._x]
        sub     eax, 32
        jns     .set_ofs
        xor     eax, eax

.set_ofs:
        mov     [esi+TTabBar.x_start], eax

        exec    esi, TTabBar:RectChanged2, 0
        jmp     .finish

.scroll_left:
        mov     eax, [ecx+TTab._x]
        add     eax, [ecx+TTab._width]
        sub     eax, [esi+TTabBar._width]

        mov     ebx, [edx+TArray.count]
        dec     ebx
        cmp     ebx, [esi+TTabBar._selected]
        jbe     .set_ofs
        add     eax, 32
        jmp     .set_ofs
endp



method TTabBar.__UpdateImages
begin
        inherited
        pushad

        mov     esi, [.self]

        stdcall GetFontMetrics, [GUI.DefaultFont]
        mov     edi, eax ; min tabbar height

        OutputValue "TextHeight: ", edi, 10, -1

        mov     edx, [esi+TTabBar._items]
        mov     ecx, [edx+TArray.count]
        lea     edx, [edx+TArray.array]
        xor     ebx, ebx        ; total width

.loop:
        dec     ecx
        js      .endloop

        add     ebx, [edx+TTab._width]
        mov     eax, [edx+TTab.icon]

        add     edx, sizeof.TTab

        test    eax, eax
        jz      .loop

        cmp     edi, [eax+TImage.wrapH]
        cmovl   edi, [eax+TImage.wrapH]
        jmp     .loop

.endloop:
        add     edi, 2 ; for the select mark.
        add     edi, [GUI.tabMarginY]
        add     edi, [GUI.tabMarginY]   ; height

        OutputValue "Tab height = ", edi, 10, -1

        mov     edx, [esi+TTabBar._canvas]
        test    edx, edx
        jz      .create_new

        cmp     ebx, [edx+TImage.wrapW]
        ja      .destroy

        cmp     edi, [edx+TImage.wrapH]
        ja      .destroy

        popad
        return

.destroy:

        stdcall DestroyImage, edx

.create_new:

        stdcall CreateImage, ebx, edi
        mov     [esi+TTabBar._canvas], eax
        and     [esi+TTabBar._canvas_valid], 0

        popad
        return

endp




endmodule