; ------------------------------------------------------------------------
; HeavyThing x86_64 assembly language library and showcase programs
; Copyright © 2015-2018 2 Ton Digital
; Homepage: https://2ton.com.au/
; Author: Jeff Marrison <jeff@2ton.com.au>
;
; This file is part of the HeavyThing library.
;
; HeavyThing is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License, or
; (at your option) any later version.
;
; HeavyThing is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License along
; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
; ------------------------------------------------------------------------
;
; tui_alert.inc: basic TUI alert box functionality
; nothing terribly fancy, haha, but useful in situations where we have to do
; "normal" alert-type goods
;
; to determine which button was pressed, the button itself will be passed inside
; a click event, in which case you can examine it's button text
;
; This simplifies tui_alert, insofar as all we really do is provide a convenience
; constructor that builds out the panel/message/buttons
;
if used tui_alert$vtable | defined include_everything
; we need to override the vtable only so we can deal with tab focus changes between buttons
dalign
tui_alert$vtable:
dq tui_panel$cleanup, tui_panel$clone, tui_panel$draw, tui_object$redraw, tui_object$updatedisplaylist, tui_object$sizechanged
dq tui_object$timer, tui_object$layoutchanged, tui_object$move, tui_object$setfocus, tui_object$gotfocus, tui_object$lostfocus
dq tui_object$keyevent, tui_object$domodal, tui_object$endmodal, tui_object$exit, tui_object$calcbounds, tui_object$calcchildbounds
dq tui_panel$appendchild, tui_object$appendbastard, tui_panel$prependchild, tui_panel$contains, tui_panel$getchildindex
dq tui_panel$removechild, tui_object$removebastard, tui_object$removeallchildren, tui_object$removeallbastards
dq tui_object$getobjectsunderpoint, tui_object$flatten, tui_object$firekeyevent, tui_alert$ontab, tui_alert$onshifttab
dq tui_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked
end if
if used tui_alert$new | defined include_everything
; button flags is an or combination of one or more of these:
tui_alert_ok = 1
tui_alert_cancel = 2
tui_alert_yes = 4
tui_alert_no = 8
tui_alert_continue = 16
tui_alert_quit = 32
; six arguments: rdi == string title, rsi == string message (multiline okay), edx == button flags, ecx == panel/title colors, r8d == button colors, r9d == focus button colors
falign
tui_alert$new:
prolog tui_alert$new
sub rsp, 80
mov [rsp], rdi
mov [rsp+8], rsi
mov [rsp+16], rdx
mov [rsp+24], rcx
mov [rsp+32], r8
mov [rsp+40], r9
; calculate the dimensions we need for our panel
; even though it is inefficient, we split the message first so we can determine
; the longest line (inefficient insofar as tui_label will split it again)
mov rdi, rsi
mov esi, 10
call string$split
mov [rsp+48], rax
mov qword [rsp+56], 0
mov rdi, rax
mov rsi, .linelength
lea rdx, [rsp+56]
call list$foreach_arg
mov rdi, [rsp]
; start with min 32
mov r10d, 32
; titlelen + 6 bigger?
mov r11, [rdi]
add r11d, 6
cmp r11d, r10d
cmova r10d, r11d
; linelen(max) + 6 bigger?
mov r11d, [rsp+56]
add r11d, 6
cmp r11d, r10d
cmova r10d, r11d
; combined width of buttons + 6 bigger?
; textlen+7 is what tui_button allocates space-wise
; ok == 9
; cancel = 13
; yes = 10
; no = 9
; continue = 15
; quit = 11
xor eax, eax
mov r11d, 9
test dword [rsp+16], 1
cmovnz eax, r11d
mov ecx, eax
add ecx, 13
test dword [rsp+16], 2
cmovnz eax, ecx
mov ecx, eax
add ecx, 10
test dword [rsp+16], 4
cmovnz eax, ecx
mov ecx, eax
add ecx, 9
test dword [rsp+16], 8
cmovnz eax, ecx
mov ecx, eax
add ecx, 15
test dword [rsp+16], 16
cmovnz eax, ecx
mov ecx, eax
add ecx, 11
test dword [rsp+16], 32
cmovnz eax, ecx
add eax, 6
cmp eax, r10d
cmova r10d, eax
; width is complete
mov [rsp+64], r10d
; height is next
mov rax, [rsp+48]
mov edx, [rax+_list_size_ofs]
mov ecx, edx
add ecx, 1
cmp edx, 1
cmova edx, ecx
add edx, 8
mov [rsp+68], edx
; get rid of our list
mov rdi, rax
mov rsi, heap$free
call list$clear
mov rdi, [rsp+48]
call heap$free
; we can construct our panel now
mov edi, [rsp+64] ; width
mov esi, [rsp+68] ; height
mov rdx, [rsp] ; title
mov ecx, [rsp+24] ; boxcolors
mov r8d, [rsp+24] ; titlecolors
call tui_panel$new_ii
; make sure we use our vtable for the panel
mov qword [rax], tui_alert$vtable
; also, we'd like a dropshadow under our panel:
mov dword [rax+tui_dropshadow_ofs], 1
mov [rsp+56], rax ; our end return object
; prepend our message with a linefeed so it ends up padded properly:
mov rdi, .lfstr
mov rsi, [rsp+8]
call string$concat
mov [rsp+64], rax
movq xmm0, [_math_onehundred]
movq xmm1, [_math_onehundred]
mov rdi, rax ; filltext
mov esi, [rsp+24] ; colors
mov edx, tui_textalign_center
call tui_label$new_dd
; add that as a child to our panel
mov rdi, [rsp+56]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; free our filltext
mov rdi, [rsp+64]
call heap$free
; next up, we need a 100% wide horizontal layout 4 height box to hold our buttons
mov edi, tui_object_size
call heap$alloc
mov qword [rax], tui_object$simple_vtable
mov [rsp+64], rax
mov rdi, rax
movq xmm0, [_math_onehundred]
mov esi, 4
call tui_object$init_di
mov rdi, [rsp+64]
mov dword [rdi+tui_layout_ofs], tui_layout_horizontal
; add that to our panel:
mov rsi, rdi
mov rdi, [rsp+56]
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; add an hspacer so that all our buttons end up equidistant
movq xmm0, [_math_onehundred]
call tui_hspacer$new_d
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; next up: create our buttons
test dword [rsp+16], 1
jz .skipok
mov rdi, .okstr
mov esi, [rsp+24] ; background colors
mov edx, [rsp+32] ; colors
mov ecx, [rsp+40] ; focuscolors
call tui_button$new
; add that to our buttonbox
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; add another hspacer
movq xmm0, [_math_onehundred]
call tui_hspacer$new_d
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
calign
.skipok:
test dword [rsp+16], 2
jz .skipcancel
mov rdi, .cancelstr
mov esi, [rsp+24] ; background colors
mov edx, [rsp+32] ; colors
mov ecx, [rsp+40] ; focuscolors
call tui_button$new
; add that to our buttonbox
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; add another hspacer
movq xmm0, [_math_onehundred]
call tui_hspacer$new_d
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
calign
.skipcancel:
test dword [rsp+16], 4
jz .skipyes
mov rdi, .yesstr
mov esi, [rsp+24] ; background colors
mov edx, [rsp+32] ; colors
mov ecx, [rsp+40] ; focuscolors
call tui_button$new
; add that to our buttonbox
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; add another hspacer
movq xmm0, [_math_onehundred]
call tui_hspacer$new_d
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
calign
.skipyes:
test dword [rsp+16], 8
jz .skipno
mov rdi, .nostr
mov esi, [rsp+24] ; background colors
mov edx, [rsp+32] ; colors
mov ecx, [rsp+40] ; focuscolors
call tui_button$new
; add that to our buttonbox
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; add another hspacer
movq xmm0, [_math_onehundred]
call tui_hspacer$new_d
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
calign
.skipno:
test dword [rsp+16], 16
jz .skipcontinue
mov rdi, .continuestr
mov esi, [rsp+24] ; background colors
mov edx, [rsp+32] ; colors
mov ecx, [rsp+40] ; focuscolors
call tui_button$new
; add that to our buttonbox
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; add another hspacer
movq xmm0, [_math_onehundred]
call tui_hspacer$new_d
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
calign
.skipcontinue:
test dword [rsp+16], 32
jz .skipquit
mov rdi, .quitstr
mov esi, [rsp+24] ; background colors
mov edx, [rsp+32] ; colors
mov ecx, [rsp+40] ; focuscolors
call tui_button$new
; add that to our buttonbox
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; add another hspacer
movq xmm0, [_math_onehundred]
call tui_hspacer$new_d
mov rdi, [rsp+64]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
calign
.skipquit:
; so now, we need to focus the SECOND child of our buttonbox
mov rdi, [rsp+64]
mov rdi, [rdi+tui_children_ofs]
mov rdi, [rdi+_list_first_ofs]
cmp qword [rdi+_list_nextofs], 0
je .skipfocus
mov rdi, [rdi+_list_nextofs]
mov rdi, [rdi]
mov rsi, [rdi]
call qword [rsi+tui_vgotfocus]
calign
.skipfocus:
; do our final return
mov rax, [rsp+56]
add rsp, 80
epilog
cleartext .lfstr, 10
cleartext .okstr, 'Ok'
cleartext .cancelstr, 'Cancel'
cleartext .yesstr, 'Yes'
cleartext .nostr, 'No'
cleartext .continuestr, 'Continue'
cleartext .quitstr, 'Quit'
falign
.linelength:
; called for each string line in the message, rdi == string, rsi == pointer to update with our length
mov edx, [rdi]
mov ecx, [rsi]
cmp edx, ecx
cmova ecx, edx
mov [rsi], ecx
ret
end if
if used tui_alert$addbutton | defined include_everything
; two arguments: rdi == tui_alert (which is a tui_panel), rsi == tui_button to add
; NOTE: we do not pay attention to sizing/resizing of the tui_alert panel itself, up to the caller to do that
falign
tui_alert$addbutton:
prolog tui_alert$addbutton
; burning purpose: adhere to the tui_alert$new structure, and add this button
mov rdi, [rdi+tui_panel_guts_ofs]
mov rdi, [rdi+tui_children_ofs]
mov rdi, [rdi+_list_last_ofs]
mov rdi, [rdi] ; the buttonbox
push rdi
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; add an hspacer to it
movq xmm0, [_math_onehundred]
call tui_hspacer$new_d
pop rdi
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
epilog
end if
;
; some notes here on the tab/shifttab goods:
; instead of creating an actual custom tui_panel object, and keeping our button list separately
; we _ASSUME_ that no other objects were added to us, and our contents are precisely what the above
; constructor tui_alert$new produced.
; This lets us grab our guts' last child, which is the button box, and navigate its children directly
;
if used tui_alert$ontab | defined include_everything
; single argument in rdi: our tui_alert (which is a tui_panel)
falign
tui_alert$ontab:
prolog tui_alert$ontab
push rbx r12
mov rbx, [rdi+tui_panel_guts_ofs]
mov rbx, [rbx+tui_children_ofs]
mov rbx, [rbx+_list_last_ofs] ; last child of the panel == our buttonbox
mov rbx, [rbx]
mov rbx, [rbx+tui_children_ofs]
cmp qword [rbx+_list_size_ofs], 1
je .nothingtodo
; first up, locate the button that _is_ focussed
mov r12, [rbx+_list_first_ofs]
calign
.locatefocus:
; our buttonbox is: hspacer, button, hspacer [, button, hspacer]...
mov r12, [r12+_list_nextofs]
mov rdi, [r12] ; a button object
cmp dword [rdi+tui_button_focussed_ofs], 0
jne .focusfound
; NOTE: this will crash of course if it isn't built exactly the way we expect
mov r12, [r12+_list_nextofs]
jmp .locatefocus
calign
.focusfound:
; unfocus the one that has it
mov rsi, [rdi]
call qword [rsi+tui_vlostfocus]
mov r12, [r12+_list_nextofs]
mov r12, [r12+_list_nextofs]
test r12, r12
jz .resetfocus
; otherwise, this one gets focus
mov rdi, [r12]
mov rsi, [rdi]
call qword [rsi+tui_vgotfocus]
; done, dusted.
pop r12 rbx
epilog
calign
.resetfocus:
; we ran off the end of our list
mov r12, [rbx+_list_first_ofs]
mov r12, [r12+_list_nextofs]
mov rdi, [r12]
mov rsi, [rdi]
call qword [rsi+tui_vgotfocus]
pop r12 rbx
epilog
calign
.nothingtodo:
pop r12 rbx
epilog
end if
if used tui_alert$onshifttab | defined include_everything
; single argument in rdi: our tui_alert (which is a tui_panel)
falign
tui_alert$onshifttab:
prolog tui_alert$onshifttab
push rbx r12
mov rbx, [rdi+tui_panel_guts_ofs]
mov rbx, [rbx+tui_children_ofs]
mov rbx, [rbx+_list_last_ofs] ; last child of the panel == our buttonbox
mov rbx, [rbx]
mov rbx, [rbx+tui_children_ofs]
cmp qword [rbx+_list_size_ofs], 1
je .nothingtodo
; first up, locate the button that _is_ focussed
mov r12, [rbx+_list_first_ofs]
calign
.locatefocus:
; our buttonbox is: hspacer, button, hspacer [, button, hspacer]...
mov r12, [r12+_list_nextofs]
mov rdi, [r12] ; a button object
cmp dword [rdi+tui_button_focussed_ofs], 0
jne .focusfound
; NOTE: this will crash of course if it isn't built exactly the way we expect
mov r12, [r12+_list_nextofs]
jmp .locatefocus
calign
.focusfound:
; unfocus the one that has it
mov rsi, [rdi]
call qword [rsi+tui_vlostfocus]
mov r12, [r12+_list_prevofs]
mov r12, [r12+_list_prevofs]
test r12, r12
jz .resetfocus
; otherwise, this one gets focus
mov rdi, [r12]
mov rsi, [rdi]
call qword [rsi+tui_vgotfocus]
; done, dusted.
pop r12 rbx
epilog
calign
.resetfocus:
; we ran off the end of our list
mov r12, [rbx+_list_last_ofs]
mov r12, [r12+_list_prevofs]
mov rdi, [r12]
mov rsi, [rdi]
call qword [rsi+tui_vgotfocus]
pop r12 rbx
epilog
calign
.nothingtodo:
pop r12 rbx
epilog
end if