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


; tree-view item state flags.
tvisExpanded = 1
tvisSelected = 2

; iterate visible items flags

iifViewport = 1         ; iterate only items that are in the viewport of the control.
                        ; if not set, the items that are "visible" because of the expanded parent items
                        ; will cause callback call as well.


; TTreeView:HitTest return flags

tvhtNone = 0
tvhtExpandIcon = 1
tvhtItemIcon = 2
tvhtItemText = 3


struct TTreeViewItem

  .level      dd ?        ; the level of the element in the tree.
  .xwidth     dd ?        ; width of the item in pixels.

  .caption    dd ?        ; hString of the name.
  .state      dd ?        ; one or more state flags.

  .imgNormal  dd ?        ; index to the icon for normal state
  .iSelected  dd ?        ; index to the icon for selected state

  .UserData   dd ?        ; the user data attached to the item.

  align 32
  .shift = 5

ends




object TTreeView, TScrollWindow

  ._items dd ?    ; TArray with TTreeViewItem elements.
  ._index dd ?    ; TArray with dword index to the displayable items of ._items array.

  ._focused    dd ?

  ._line_height dd ?
  ._text_offset dd ?
  ._margin_top  dd ?
  ._margin_left dd ?

  ._max_width   dd ?    ; 0 means unknown

  ._on_focused_change dd ?

  param  .OnFocusItem, ._on_focused_change, ._on_focused_change
  param  .FocusedItem, ._focused, .SetFocused

  method .SetFocused, .value

  method .Create, .parent
  method .Destroy

  method .ClearSelection, .mask
  method .IterateVisibleItems, .start_item, .flags, .callback, .user
  method .HitTest, .x, .y

  method .__RebuildIndex
  method .__RebuildWidths

  method .Paint
  method .UpdateScrollBar

  method .EventKeyPress, .utf8, .scancode, .kbdState
  method .EventButtonPress, .button, .kbdState, .x, .y

endobj



interface TTreeView.IterateCallback, .pTreeView, .pItem, .yItem, .user

interface TTreeView.OnFocusItem, .pTreeView, .pItem




method TTreeView.Create ; .parent
begin
        inherited [.parent]

        pushad
        mov     esi, [.self]

        stdcall CreateArray, sizeof.TTreeViewItem
        mov     [esi+TTreeView._items], eax

        stdcall CreateArray, 4
        mov     [esi+TTreeView._index], eax

        mov     [esi+TTreeView._margin_top], 4
        mov     [esi+TTreeView._margin_left], 4

        stdcall GetFontMetrics, [GUI.DefaultFont]
        add     eax, 4
        add     ebx, 2
        mov     [esi+TTreeView._line_height], eax
        mov     [esi+TTreeView._text_offset], ebx

        mov     [esi+TTreeView.__want_focus], 1

        popad
        return
endp



method TTreeView.Destroy
begin
        push    ebx

        mov     ebx, [.self]

        set     ebx, TTreeView:Visible, FALSE   ; in order to not try to update the view.

        stdcall FreeMem, [ebx+TTreeView._index]
        stdcall FreeMem, [ebx+TTreeView._items]

        pop     ebx

        inherited
        return
endp





method TTreeView.UpdateScrollBar
begin
        pushad

        mov     esi, [.self]
        mov     ecx, [esi+TWindow._width]
        mov     edx, [esi+TWindow._height]
        mov     edi, [GUI.boxBorderWidth]

        mov     eax, ecx
        mov     ebx, edx

        sub     eax, [GUI.scrollWidth]          ; width - scrollWidth
        sub     ebx, [GUI.scrollWidth]          ; height - scrollWidth

        sub     eax, edi                        ; width - boxBorderWidth
        sub     ebx, edi                        ; height - boxBorderWidth

        shl     edi, 1

        sub     edx, edi                        ; total client height - borders.
        js      .finish

        sub     ecx, edi                        ; total client width
        js      .finish

        set     [esi+TTreeView.YScroller], TScrollbar:x, eax
        set     [esi+TTreeView.YScroller], TScrollbar:y, [GUI.boxBorderWidth]
        set     [esi+TTreeView.YScroller], TScrollbar:height, edx

        set     [esi+TTreeView.XScroller], TScrollbar:x, [GUI.boxBorderWidth]
        set     [esi+TTreeView.XScroller], TScrollbar:y, ebx
        set     [esi+TTreeView.XScroller], TScrollbar:width, ecx
        set     [esi+TTreeView.XScroller], TScrollbar:Page, eax

        mov     ecx, [esi+TTreeView._max_width]
        sub     ecx, eax
        jns     @f
        xor     ecx, ecx
@@:
        cmp     ecx, [esi+TTreeView._start_x]
        jge     .set_max_x

        set     esi, TTreeView:StartX, ecx

.set_max_x:
        set     [esi+TTreeView.XScroller], TScrollbar:Max, ecx

        mov     ecx, [esi+TTreeView._line_height]
        mov     eax, edx
        cdq

        div     ecx
        test    edx, edx
        jz      @f
        inc     eax
@@:
        set     [esi+TTreeView.YScroller], TScrollbar:Page, eax

        mov     ebx, [esi+TTreeView._index]
        test    ebx, ebx
        jz      .finish

        mov     ebx, [ebx+TArray.count]
        sub     ebx, eax
        inc     ebx
        jns     @f
        xor     ebx, ebx
@@:
        cmp     ebx, [esi+TTreeView._start_y]
        jge     .set_start_y

        set     esi, TTreeView:StartY, ebx

.set_start_y:
        set     [esi+TTreeView.YScroller], TScrollbar:Max, ebx

.finish:
        popad
        return
endp





method TTreeView.__RebuildIndex
begin
        pushad

        mov     esi, [.self]

        mov     ecx, [esi+TTreeView._index]
        jecxz   @f

        stdcall FreeMem, ecx

@@:
        stdcall CreateArray, 4
        mov     [esi+TTreeView._index], eax

        xor     eax, eax
        mov     [esi+TTreeView._max_width], eax
        lea     edx, [esi+TTreeView._index]
        exec    esi, TTreeView:IterateVisibleItems, eax, eax, TTreeView.__AddIndex, edx

        exec    [.self], TTreeView:UpdateScrollBar
        popad
        return
endp



proc TTreeView.__AddIndex as TTreeView.IterateCallback
begin
        mov     esi, [.pTreeView]
        mov     ebx, [.pItem]

        mov     eax, [ebx+TTreeViewItem.xwidth]
        cmp     eax, [esi+TTreeView._max_width]
        jle     @f
        mov     [esi+TTreeView._max_width], eax
        OutputValue "Max width:", eax, 10, -1
@@:

        mov     esi, [esi+TTreeView._items]
        lea     esi, [esi+TArray.array]

        mov     edi, [.user]
        sub     ebx, esi
        shr     ebx, TTreeViewItem.shift

        stdcall AddArrayItems, [edi], 1
        mov     [edi], edx
        mov     [eax], ebx

        clc
        return
endp



method TTreeView.__RebuildWidths
begin
        pushad

        mov     esi, [.self]
        mov     edi, [esi+TTreeView._items]
        mov     ecx, [edi+TArray.count]
        lea     edi, [edi+TArray.array]
        jecxz   .finish

.loop:
        stdcall StrPtr, [edi+TTreeViewItem.caption]
        stdcall GetTextBounds, eax, [eax+string.len], [GUI.DefaultFont]

        mov     ebx, [edi+TTreeViewItem.level]
        mov     edx, [esi+TTreeView._line_height]

        imul    ebx, edx
        lea     ebx, [ebx+2*edx]                  ; two icons.
        add     ebx, [esi+TTreeView._margin_left] ; left margin.
        add     eax, ebx

        mov     [edi+TTreeViewItem.xwidth], eax
        add     edi, sizeof.TTreeViewItem
        loop    .loop

.finish:
        popad
        return
endp




method TTreeView.Paint
.bounds TBounds
.end_item dd ?
begin
        pushad

        mov     esi, [.self]
        get     ebx, esi, TWindow:Canvas
        test    ebx, ebx
        jz      .finish

        xor     eax, eax

        mov     [.bounds.x], eax
        mov     [.bounds.y], eax

        mov     ecx, [esi+TTreeView._width]
        mov     edx, [esi+TTreeView._height]

        mov     [.bounds.width], ecx
        mov     [.bounds.height], edx

        lea     eax, [.bounds]
        stdcall [DrawBox], [esi+TTreeView._canvas], eax, [GUI.clTreeViewBack], bxNone, 0

        mov     eax, [esi+TTreeView._items]
        mov     ebx, [eax+TArray.count]
        shl     ebx, TTreeViewItem.shift
        lea     ebx, [eax+TArray.array+ebx]
        mov     [.end_item], ebx

        mov     edi, [esi+TTreeView._index]
        mov     ecx, [esi+TTreeView._start_y]
        mov     edx, [esi+TTreeView._margin_top]

.loop:
        cmp     ecx, [edi+TArray.count]
        jae     .end_paint

        mov     eax, [edi+TArray.array + 4*ecx]
        shl     eax, TTreeViewItem.shift
        add     eax, [esi+TTreeView._items]
        add     eax, TArray.array

        xor     ebx, ebx
        cmp     ecx, [esi+TTreeView._focused]
        sete    bl
        push    ebx     ; .focused argument

        lea     ebx, [eax+sizeof.TTreeViewItem]
        cmp     ebx, [.end_item]
        jb      @f

        xor     ebx, ebx

@@:
        stdcall TTreeView.__PaintOneItem, esi, eax, ebx, [esi+TTreeView._line_height], [esi+TTreeView._line_height], edx     ; the last argument from the stack.

        inc     ecx
        add     edx, [esi+TTreeView._line_height]
        cmp     edx, [esi+TTreeView._height]
        jb      .loop

.end_paint:
        lea     eax, [.bounds]
        mov     ecx, [GUI.tvBorder]
        or      ecx, bxNoFill
        stdcall [DrawBox], [esi+TTreeView._canvas], eax, [GUI.clTreeViewBack], ecx, [GUI.boxBorderWidth]


.finish:
        inherited

        popad
        return
endp


proc TTreeView.__PaintOneItem, .pTreeView, .pItem, .pNextItem, .hsize, .vsize, .y, .focused
.back dd ?
.text dd ?
begin
        pushad

        mov     edx, [.y]
        mov     esi, [.pItem]
        mov     edi, [.pTreeView]

        mov     ecx, [esi+TTreeViewItem.level]
        imul    ecx, [.hsize]

        add     ecx, [edi+TTreeView._margin_left] ; left margin.
        sub     ecx, [edi+TTreeView._start_x]     ; left offset.

        mov     eax, [GUI.clTreeViewText]
        mov     ebx, [GUI.clTreeViewBack]

        test    [esi+TTreeViewItem.state], tvisSelected
        jz      @f
        mov     eax, [GUI.clTreeSelectedTxt]
        mov     ebx, [GUI.clTreeSelected]
@@:
        cmp     [.focused], 0
        je      @f
        mov     eax, [GUI.clTreeFocusedTxt]
        mov     ebx, [GUI.clTreeFocused]
@@:
        mov     [.text], eax
        mov     [.back], ebx

; draw state icon
        mov     eax, [.pNextItem]
        test    eax, eax
        jz      .state_icon_ok

        mov     eax, [eax+TTreeViewItem.level]
        cmp     eax, [esi+TTreeViewItem.level]
        jle     .state_icon_ok

        mov     ebx, [esi+TTreeViewItem.state]
        and     ebx, tvisExpanded
        mov     ebx, [GUI.tvIcons+8*ebx]                    ; TImage with the expand/collapse icon

        push    ecx edx

; center the icon
        mov     eax, [edi+TTreeView._line_height]
        sub     eax, [ebx+TImage.width]
        sar     eax, 1

        add     ecx, eax

        mov     eax, [edi+TTreeView._line_height]
        sub     eax, [ebx+TImage.height]
        sar     eax, 1

        add     edx, eax

;.draw_state:
        stdcall BlendImage, [edi+TTreeView._canvas], ecx, edx, ebx, 0, 0, [ebx+TImage.width], [ebx+TImage.height]
        pop     edx ecx

.state_icon_ok:
        add     ecx, [.hsize]

; draw item icon
        mov     ebx, [esi+TTreeViewItem.imgNormal]
        test    ebx, ebx
        jz      .icon_ok

        push    ecx edx

; center the icon
        mov     eax, [edi+TTreeView._line_height]
        sub     eax, [ebx+TImage.width]
        sar     eax, 1

        add     ecx, eax

        mov     eax, [edi+TTreeView._line_height]
        sub     eax, [ebx+TImage.height]
        sar     eax, 1

        add     edx, eax

        stdcall BlendImage, [edi+TTreeView._canvas], ecx, edx, ebx, 0, 0, [ebx+TImage.width], [ebx+TImage.height]

        pop     edx ecx

        add     ecx, [edi+TTreeView._line_height]

.icon_ok:
        stdcall StrPtr, [esi+TTreeViewItem.caption]
        jc      .finish

        push    edx
        stdcall GetTextBounds, eax, [eax+string.len], [GUI.DefaultFont]
        pop     edx
        add     eax, 4

        stdcall DrawSolidRect, [edi+TTreeView._canvas], ecx, edx, eax, [.vsize], [.back]

        add     edx, [edi+TTreeView._text_offset]
        add     ecx, 2

        stdcall StrPtr, [esi+TTreeViewItem.caption]
        stdcall DrawString, [edi+TTreeView._canvas], eax, [eax+string.len], ecx, edx, [GUI.DefaultFont], [.text]

.finish:
        popad
        return
endp




method TTreeView.EventKeyPress  ;, .utf8, .scancode, .kbdState
begin
        pushad

        mov     esi, [.self]

        mov     eax, [.utf8]
        test    eax, eax
        jz      .no_char

; search the treeview???

        popad
        return

.no_char:
        mov     eax, [.scancode]

        cmp     eax, keyUpNumpad
        je      .up
        cmp     eax, keyUp
        je      .up

        cmp     eax, keyDownNumpad
        je      .down
        cmp     eax, keyDown
        je      .down

        cmp     eax, keyPgUpNumpad
        je      .pgup
        cmp     eax, keyPgUp
        je      .pgup

        cmp     eax, keyPgDnNumpad
        je      .pgdn
        cmp     eax, keyPgDown
        je      .pgdn

        popad
        return

.up:
        mov     edx, 1
        jmp     .moveup

.pgup:
        get     edx, [esi+TTreeView.YScroller], TScrollbar:Page
        dec     edx


.moveup:
        mov     ebx, [esi+TTreeView._focused]

        test    [.kbdState], maskShift
        jnz     .sel_up_ok

        exec    esi, TTreeView:ClearSelection, not tvisSelected

.sel_up_ok:
        sub     ebx, edx
        jns     @f

        xor     ebx, ebx
@@:
        set     esi, TTreeView:FocusedItem, ebx

        cmp     ebx, [esi+TTreeView._start_y]
        jae     .refresh

        set     esi, TTreeView:StartY, ebx
        jmp     .finish

.refresh:
        exec    esi, TTreeView:Refresh

.finish:
        popad
        return


.down:
        mov     edx, 1
        jmp     .move_down

.pgdn:
        get     edx, [esi+TTreeView.YScroller], TScrollbar:Page
        dec     edx


.move_down:
        mov     edi, [esi+TTreeView._index]
        mov     ebx, [esi+TTreeView._focused]

        add     ebx, edx

        cmp     ebx, [edi+TArray.count]
        jb      .posok

        mov     ebx, [edi+TArray.count]
        dec     ebx

.posok:
        test    [.kbdState], maskShift
        jnz     .sel_dn_ok

        exec    esi, TTreeView:ClearSelection, not tvisSelected

.sel_dn_ok:
        set     esi, TTreeView:FocusedItem, ebx

        get     ecx, [esi+TTreeView.YScroller], TScrollbar:Page

        sub     ecx, 2
        sub     ebx, ecx

        cmp     ebx, [esi+TTreeView._start_y]
        jle     .refresh

        set     esi, TTreeView:StartY, ebx
        jmp     .finish

endp



method TTreeView.EventButtonPress        ;, .button, .kbdState, .x, .y
begin
        pushad

        mov     esi, [.self]

        exec    esi, TTreeView:HitTest, [.x], [.y]

        cmp     eax, tvhtNone
        je      .finish2

        cmp     eax, tvhtExpandIcon
        je      .expand_collapse

        test    [.kbdState], maskCtrl
        jnz     @f
        exec    esi, TTreeView:ClearSelection, not tvisSelected
@@:
        set     esi, TTreeView:FocusedItem, ecx
        jmp     .finish

.expand_collapse:

        xor     [edx+TTreeViewItem.state], tvisExpanded
        exec    esi, TTreeView:__RebuildIndex

.finish:
        exec    [.self], TWindow:Refresh

.finish2:
        popad
        return
endp







method TTreeView.IterateVisibleItems
.end dd ?

.min_item dd ?
.maxy dd ?
begin
        pushad

        mov     esi, [.self]

        mov     eax, [esi+TTreeView._height]
        mov     [.maxy], eax

        mov     eax, [esi+TTreeView._items]
        mov     eax, [eax+TArray.count]

        mov     ecx, [.start_item]
        mov     edx, [esi+TTreeView._start_y]

        shl     eax, TTreeViewItem.shift
        shl     ecx, TTreeViewItem.shift
        shl     edx, TTreeViewItem.shift

        add     eax, [esi+TTreeView._items]
        add     ecx, [esi+TTreeView._items]
        add     edx, [esi+TTreeView._items]

        lea     eax, [eax+TArray.array]
        lea     ecx, [ecx+TArray.array]
        lea     edx, [edx+TArray.array]

        mov     [.end], eax
        mov     [.min_item], edx

        xor     edx, edx
        dec     edx        ; current level = $ffffffff - unsigned cmp

        xor     edi, edi   ; current Y coordinate.
        add     edi, [esi+TTreeView._margin_top]   ; top margin.

.item_loop:
        cmp     ecx, [.end]
        jae     .end_items

        cmp     [ecx+TTreeViewItem.level], edx
        ja      .next_item
        je      .equal

; below
        mov     edx, [ecx+TTreeViewItem.level]

.equal:
        test    [ecx+TTreeViewItem.state], tvisExpanded
        jz      .visible

        inc     edx

.visible:
        test    [.flags], iifViewport
        jz      .yes

        cmp     ecx, [.min_item]
        jb      .next_item

        cmp     edi, [.maxy]
        ja      .end_items

.yes:
        pushad
        stdcall [.callback], esi, ecx, edi, [.user]
        popad
        jc      .found

        add     edi, [esi+TTreeView._line_height]

.next_item:
        add     ecx, sizeof.TTreeViewItem
        jmp     .item_loop

.end_items:
        clc
        popad
        return

.found:
        mov     [esp+regEAX*4], ecx
        popad
        return
endp




method TTreeView.HitTest
.index dd ?
begin
        pushad

        mov     esi, [.self]

        mov     eax, [.y]
        sub     eax, [esi+TTreeView._margin_top]
        xor     edx, edx
        mov     ecx, [esi+TTreeView._line_height]

        mov     [.index], edx

        div     ecx

        add     eax, [esi+TTreeView._start_y]
        mov     ebx, [esi+TTreeView._index]

        cmp     eax, [ebx+TArray.count]
        jae     .not_found

        mov     [.index], eax

        mov     edi, [ebx+4*eax+TArray.array]   ; index

        mov     ebx, [esi+TTreeView._items]

        cmp     edi, [ebx+TArray.count]
        jae     .not_found

        shl     edi, TTreeViewItem.shift
        lea     edi, [ebx+TArray.array + edi]   ; edi - pointer to the pointed item.

        mov     edx, [edi+TTreeViewItem.level]
        imul    edx, [esi+TTreeView._line_height]
        add     edx, [esi+TTreeView._margin_left]

        cmp     [.x], edx
        jb      .finish

        add     edx, [esi+TTreeView._line_height]

        mov     eax, tvhtExpandIcon

        cmp     [.x], edx
        jb      .finish

        add     edx, [esi+TTreeView._line_height]

        mov     eax, tvhtItemIcon

        cmp     [.x], edx
        jb      .finish

        mov     eax, tvhtItemText

.finish:
        mov     [esp+regEDX*4], edi
        mov     [esp+regEAX*4], eax
        mov     eax, [.index]
        mov     [esp+regECX*4], eax
        popad
        return

.not_found:
        xor     edi, edi
        mov     eax, tvhtNone
        jmp     .finish

endp



method TTreeView.ClearSelection
begin
        pushad

        mov     esi, [.self]
        mov     esi, [esi+TTreeView._items]
        lea     edi, [esi+TArray.array]
        mov     ecx, [esi+TArray.count]
        mov     ebx, [.mask]

.loop:
        dec     ecx
        js      .finish

        and     [edi+TTreeViewItem.state], ebx
        add     edi, sizeof.TTreeViewItem
        jmp     .loop


.finish:
        popad
        return
endp




method TTreeView.SetFocused     ;, .value
begin
        pushad
        mov     esi, [.self]
        mov     edi, [esi+TTreeView._items]
        mov     ecx, [.value]

        cmp     [esi+TTreeView._focused], ecx
        je      .finish

        mov     [esi+TTreeView._focused], ecx

        xor     ebx, ebx

        cmp     ecx, [edi+TArray.count]
        jae     .no_selection

        shl     ecx, TTreeViewItem.shift
        lea     ebx, [edi+TArray.array+ecx]

        or      [ebx+TTreeViewItem.state], tvisSelected

.no_selection:
        mov     eax, [esi+TTreeView._on_focused_change]
        test    eax, eax
        jz      .finish

        stdcall eax, esi, ebx           ; interface TTreeView.OnFocusItem, .pTreeView, .pItem

.finish:
        popad
        return
endp




endmodule