HeavyThing - hnwatch/ui.inc

Jeff Marrison

	; ------------------------------------------------------------------------
	; 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/>.
	; ------------------------------------------------------------------------
	;
	; ui.inc: tui goods to display the globals from hnmodel.inc
	;

globals
{
	main_screen	dq	0		; topmost UI object that holds everything else
	main_datagrid	dq	0		; we keep a global reference to it for updates
	status_format	dq	0		; formatter object for statusbar updates

	item_screen	dq	0		; if an item was selected, this is its container
	item_format1	dq	0		; formatter objects for itemselected display
	item_format2	dq	0		; ""
	item_format3	dq	0		; ""
	item_format4	dq	0		; ""
	item_kids	dq	0		; a stringmap of item-specific child items
	; when an item is selected for display this gets set to its ID (string)
	displayitem	dq	0

	; global string object that contains our current main nav (topstories, newstories, etc)
	; set initially by _start (aka main)
	navstring	dq	0
}

include 'textify.inc'

falign
ui$init:
	prolog	ui$init
	push	rbx r12 r13
	mov	edi, 1
	call	unsignedmap$new
	mov	[item_kids], rax

	; we need a base tui_object to hold "everything"
	mov	edi, tui_object_size
	call	heap$alloc
	mov	rbx, rax
	mov	rdi, rax
	mov	[main_screen], rax
	; use our custom vtable so we can grab the main nav keys
	mov	qword [rax], .custom_vtable
	movq	xmm0, [_math_onehundred]
	movq	xmm1, [_math_onehundred]
	call	tui_object$init_dd
	; so now we have a 100% x 100% dynamic-sized outer tui object
	
	; the default layout for tui_objects is vertical, and our main
	; HN index needs to be a datagrid object, so we'll add that next
	; 100% x 100% so it will autofill all available space:
	movq	xmm0, [_math_onehundred]
	movq	xmm1, [_math_onehundred]
	ansi_colors edi, 'black', 'lightgray'
	ansi_colors esi, 'lightgray', 'black'
	ansi_colors edx, 'lightgray', 'blue'
	call	tui_datagrid$new_dd
	mov	r12, rax
	; override its virtual method table with our own so we can hook itemselected
	mov	qword [rax], .datagrid_vtable
	; add our datagrid header information
	; first column we want width 4 for our rank
	mov	rdi, rax
	mov	rsi, .label_rank
	mov	edx, 4
	; update 20160808: Klaus Alexander Seistrup reckons it'll look nicer right aligned:
	; mov	ecx, tui_textalign_left
	mov	ecx, tui_textalign_right
	mov	r8, .prop_rank
	call	tui_datagrid$nvaddproperty_i
	; next column is dynamic width title
	mov	rdi, r12
	mov	rsi, .label_title
	movq	xmm0, [_math_onehundred]
	mov	edx, tui_textalign_left
	mov	rcx, .prop_title
	call	tui_datagrid$nvaddproperty_d
	; next column is width 4 score/points
	mov	rdi, r12
	mov	rsi, .label_score
	mov	edx, 4
	mov	ecx, tui_textalign_right
	mov	r8, .prop_score
	call	tui_datagrid$nvaddproperty_i
	; next column is width 7 age
	mov	rdi, r12
	mov	rsi, .label_age
	mov	edx, 7
	mov	ecx, tui_textalign_right
	mov	r8, .prop_age
	call	tui_datagrid$nvaddproperty_i
	; next column is width 16 username
	mov	rdi, r12
	mov	rsi, .label_by
	mov	edx, 16
	mov	ecx, tui_textalign_left
	mov	r8, .prop_by
	call	tui_datagrid$nvaddproperty_i
	; last column is width 4 comment count
	mov	rdi, r12
	mov	rsi, .label_descendants
	mov	edx, 4
	mov	ecx, tui_textalign_right
	mov	r8, .prop_descendants
	call	tui_datagrid$nvaddproperty_i
	; so now that we are done with column definitions, next up
	; is to add it to our outer tui_object
	mov	rdi, rbx
	mov	rsi, r12
	mov	rdx, [rdi]
	call	qword [rdx+tui_vappendchild]
	; now we can set our global var so that updates can set the data
	mov	[main_datagrid], r12

	; last but not least, add a statusbar to the bottom of our main tui_object
	movq	xmm0, [_math_onehundred]
	ansi_colors edi, 'black', 'gray'
	mov	esi, 1
	call	tui_statusbar$new_d
	mov	r13, rax
	
	; set its initial text
	mov	rdi, rax
	mov	rsi, .copyright
	call	tui_statusbar$nvsettext

	; we are not using tui_statusbar$nvaddlabel because we want highlightchars in
	; the tui_label object (otherwise, this is a straight copy from nvaddlabel)
macro add_statusbar_label s*, c* {
	mov	rdi, .s1
	mov	rsi, s
	call	string$concat
	push	rax
	mov	esi, 1
	mov	rdx, rax
	mov	ecx, [r13+tui_statusbar_colors_ofs]
	mov	r8d, tui_textalign_left
	mov	edi, [rdx]
	call	tui_label$new_ii
	; set the highlightchar and highlightcolors
	ansi_colors r8d, 'venetianred', 'gray'
	mov	dword [rax+tui_label_highlightchar_ofs], c
	mov	dword [rax+tui_label_highlightcolor_ofs], r8d
	mov	rdi, r13
	mov	rdx, rax
	mov	[rax+tui_parent_ofs], rdi
	mov	rdi, [rdi+tui_children_ofs]
	mov	rsi, [rdi+_list_first_ofs]
	call	list$insert_after
	; done with our string
	pop	rdi
	call	heap$free
	mov	rsi, [r13]
	mov	rdi, r13
	call	qword [rsi+tui_vlayoutchanged]
}

	; add our labels
	; add_statusbar_label .stat_reset, 'R'
	add_statusbar_label .stat_job, 'J'
	add_statusbar_label .stat_show, 'S'
	add_statusbar_label .stat_ask, 'A'
	add_statusbar_label .stat_new, 'N'
	add_statusbar_label .stat_top, 'T'

	; add it to our main tui_object
	mov	rdi, rbx
	mov	rsi, r13
	mov	rdx, [rdi]
	call	qword [rdx+tui_vappendchild]


	; create our status formatter object
	xor	edi, edi
	call	formatter$new
	mov	[status_format], rax
	mov	rdi, rax
	mov	rsi, .copyright
	call	formatter$add_static
	mov	rdi, [status_format]
	mov	rsi, .s1
	call	formatter$add_static

	mov	rdi, [status_format]
	mov	rsi, .s2
	call	formatter$add_static
	mov	rdi, [status_format]
	mov	esi, 1
	xor	edx, edx
	call	formatter$add_unsigned

	mov	rdi, [status_format]
	mov	rsi, .s3
	call	formatter$add_static
	mov	rdi, [status_format]
	mov	esi, 1
	xor	edx, edx
	call	formatter$add_unsigned

	mov	rdi, [status_format]
	mov	rsi, .s4
	call	formatter$add_static
	mov	rdi, [status_format]
	mov	esi, 1
	xor	edx, edx
	call	formatter$add_unsigned

	mov	rdi, [status_format]
	mov	rsi, .s5
	call	formatter$add_static
	mov	rdi, [status_format]
	mov	esi, 1
	xor	edx, edx
	call	formatter$add_unsigned

	; three formatters for itemselected display
	; text for Rank. Title
	xor	edi, edi
	call	formatter$new
	mov	[item_format1], rax
	mov	rdi, rax
	mov	rsi, .item_format_space
	call	formatter$add_static
	mov	rdi, [item_format1]
	mov	esi, 1
	xor	edx, edx
	call	formatter$add_unsigned
	mov	rdi, [item_format1]
	mov	rsi, .item_format1_dotspace
	call	formatter$add_static
	mov	rdi, [item_format1]
	xor	esi, esi
	call	formatter$add_string
	; text for X points by username T ago
	xor	edi, edi
	call	formatter$new
	mov	[item_format2], rax
	mov	rdi, rax
	mov	rsi, .item_format_quadspace
	call	formatter$add_static
	mov	rdi, [item_format2]
	mov	esi, 1
	xor	edx, edx
	call	formatter$add_unsigned
	mov	rdi, [item_format2]
	mov	rsi, .item_format2_pointsby
	call	formatter$add_static
	mov	rdi, [item_format2]
	xor	esi, esi
	call	formatter$add_string
	mov	rdi, [item_format2]
	mov	rsi, .item_format_space
	call	formatter$add_static
	mov	rdi, [item_format2]
	xor	esi, esi
	call	formatter$add_string
	mov	rdi, [item_format2]
	mov	rsi, .item_format2_ago
	call	formatter$add_static
	; text for X points by username | Y comments
	xor	edi, edi
	call	formatter$new
	mov	[item_format3], rax
	mov	rdi, rax
	mov	rsi, .item_format_quadspace
	call	formatter$add_static
	mov	rdi, [item_format3]
	mov	esi, 1
	xor	edx, edx
	call	formatter$add_unsigned
	mov	rdi, [item_format3]
	mov	rsi, .item_format2_pointsby
	call	formatter$add_static
	mov	rdi, [item_format3]
	xor	esi, esi
	call	formatter$add_string
	mov	rdi, [item_format3]
	mov	rsi, .item_format_space
	call	formatter$add_static
	mov	rdi, [item_format3]
	xor	esi, esi
	call	formatter$add_string
	mov	rdi, [item_format3]
	mov	rsi, .item_format3_pipe
	call	formatter$add_static
	mov	rdi, [item_format3]
	mov	esi, 1
	xor	edx, edx
	call	formatter$add_unsigned
	mov	rdi, [item_format3]
	mov	rsi, .item_format3_comments
	call	formatter$add_static
	; text for BY TIME ago
	mov	edi, 1
	call	formatter$new
	mov	[item_format4], rax
	mov	rdi, rax
	xor	esi, esi
	call	formatter$add_string
	mov	rdi, [item_format4]
	xor	esi, esi
	call	formatter$add_string
	mov	rdi, [item_format4]
	mov	rsi, .item_format4_ago
	call	formatter$add_static

	; set the hnmodel's statuscb and its arg
	mov	qword [statuscb], ui$statusbar_update
	mov	[statuscbarg], r13

	; so now we are done constructing our main view, fire up our signature
	; splash page intro
	mov	rdi, rbx
	call	tui_splash$new

	; set the hnmodels' updatedcb to our composer
	mov	qword [updatedcb], ui$compose
	
	; and last but not least, hook all that into a tui_terminal object
	mov	rdi, rax
	call	tui_terminal$new

	pop	r13 r12 rbx
	epilog
cleartext .label_rank, 'Pos'
cleartext .prop_rank, 'rank'
cleartext .label_title, 'Title'
cleartext .prop_title, 'title'
cleartext .label_score, 'Pts'
cleartext .prop_score, 'score'
cleartext .label_age, 'Age'
cleartext .prop_age, 'age'
cleartext .label_by, 'By'
cleartext .prop_by, 'by'
cleartext .label_descendants, 'Cmt'
cleartext .prop_descendants, 'descendants'

cleartext .stat_top, 'Top'
cleartext .stat_new, 'New'
cleartext .stat_ask, 'Ask'
cleartext .stat_show, 'Show'
cleartext .stat_job, 'Job'
cleartext .stat_reset, 'Reset'

cleartext .copyright, 'hnwatch v1.24 ',0xc2,0xa9,' 2015-2018 2 Ton Digital'

cleartext .item_format_space, ' '
cleartext .item_format_quadspace, '    '
cleartext .item_format1_dotspace, '. '
cleartext .item_format2_pointsby, ' points by '
cleartext .item_format2_ago, ' ago'
cleartext .item_format3_pipe, ' ago | '
cleartext .item_format3_comments, ' comments'
cleartext .item_format4_ago, 'ago'
dalign
.s1:
        dq      3
if string_bits = 32
        dd      ' ', 0x2502, ' '
else
        dw      ' ', 0x2502, ' '
end if
cleartext .s2, 'I: '
cleartext .s3, ' R: '
cleartext .s4, ' B: '
cleartext .s5, ' E: '
	; this is a copy of tui_object$simple_vtable, with an override for our key handler
dalign
.custom_vtable:
	dq	tui_object$cleanup, tui_object$simple_clone, tui_object$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	ui$main_keyevent, tui_object$domodal, tui_object$endmodal, tui_object$exit, tui_object$calcbounds, tui_object$calcchildbounds
	dq	tui_object$appendchild, tui_object$appendbastard, tui_object$prependchild, tui_object$contains, tui_object$getchildindex
	dq	tui_object$removechild, tui_object$removebastard, tui_object$removeallchildren, tui_object$removeallbastards
	dq	tui_object$getobjectsunderpoint, tui_object$flatten, tui_object$firekeyevent, tui_object$ontab, tui_object$onshifttab
	dq	tui_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked
dalign
.datagrid_vtable:
; direct copy of datagrid's vtable, but with our own itemselected
	dq	tui_object$cleanup, tui_datagrid$clone, tui_object$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_object$appendchild, tui_object$appendbastard, tui_object$prependchild, tui_object$contains, tui_object$getchildindex
	dq	tui_object$removechild, tui_object$removebastard, tui_object$removeallchildren, tui_object$removeallbastards
	dq	tui_object$getobjectsunderpoint, tui_object$flatten, tui_object$firekeyevent, tui_object$ontab, tui_object$onshifttab
	dq	tui_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked
	; our itemselected:
	dq	ui$itemselected



	; three arguments: rdi == our main tui_object, esi == key, edx == esc_key
falign
ui$main_keyevent:
	prolog	ui$main_keyevent
	mov	rdi, .topstories
	cmp	esi, 't'
	je	.gotit
	cmp	esi, 'T'
	je	.gotit
	mov	rdi, .newstories
	cmp	esi, 'n'
	je	.gotit
	cmp	esi, 'N'
	je	.gotit
	mov	rdi, .askstories
	cmp	esi, 'a'
	je	.gotit
	cmp	esi, 'A'
	je	.gotit
	mov	rdi, .showstories
	cmp	esi, 's'
	je	.gotit
	cmp	esi, 'S'
	je	.gotit
	mov	rdi, .jobstories
	cmp	esi, 'j'
	je	.gotit
	cmp	esi, 'J'
	je	.gotit
if defined use_reset_goods
	; special handling required for reset
	cmp	esi, 'r'
	je	.reset
	cmp	esi, 'R'
	je	.reset
end if
	; otherwise, not handled
	xor	eax, eax
	epilog
cleartext .topstories, 'topstories'
cleartext .newstories, 'newstories'
cleartext .askstories, 'askstories'
cleartext .showstories, 'showstories'
cleartext .jobstories, 'jobstories'
.gotit:
	; rdi == the string that we want, compare it to our navstring
	push	rdi
	mov	rsi, [navstring]
	call	string$equals
	pop	rdi
	test	eax, eax
	jnz	.nothingtodo
	; otherwise, we have a new main nav to goto
	mov	[navstring], rdi
	call	hnmodel$newmain
	; call our compose in the interim so that the user
	; gets an indication their key was accepted (there is lag
	; while the new eventstream gets underway)
	xor	edi, edi			; no key triggered the updatecb
	call	ui$compose
.nothingtodo:
	mov	eax, 1				; key handled
	epilog
.reset:
	; let hnmodel do a full reset
	call	hnmodel$reset
	; update our UI so the user knows something happened
	xor	edi, edi
	call	ui$compose
	mov	eax, 1
	epilog


	; single argument in rdi: an item's json object
	; returns a new string of its age (e.g. 3h25m)
	; or null if no time is present in the json
falign
ui$jsonage:
	prolog	ui$jsonage
	mov	rsi, .prop_time
	call	json$getvaluebyname
	test	rax, rax
	jz	.nullret
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.nullret
	; convert that to an unsigned
	mov	rdi, rax
	call	string$to_unsigned
	; convert that to our julian date
	mov	rdi, rax
	xor	esi, esi
	call	ctime$to_jd
	; hangon to that value
	movaps	xmm15, xmm0
	; get the current julian date
	call	timestamp
	; less the jsons
	subsd	xmm0, xmm15
	; format that as a string
	mov	edi, 2		; minute == minimum resolution
	xor	esi, esi	; no fractions
	call	format$duration
	epilog
.nullret:
	mov	rdi, .null
	call	string$copy
	epilog
cleartext .prop_time, 'time'
cleartext .null, '(null)'



	; helper function to update a heightlocked row
	; rdi == unsigned item number
	; rsi == item json
	; edx == nesting level
	; rcx == the row we are modifying
falign
ui$itemupdaterow:
	prolog	ui$itemupdaterow
	push	rbx r12 r13 r14 r15
	mov	rbx, rcx
	mov	r12, rdi
	mov	r13, rsi
	mov	r14d, edx
	; set our row's ID
	mov	qword [rcx+tui_object_size], rdi
	; set our first child's width to our nesting level
	mov	rdi, [rcx+tui_children_ofs]
	mov	rsi, [rdi+_list_first_ofs]
	mov	rdi, [rsi+_list_valueofs]
	shl	edx, 1
	mov	dword [rdi+tui_width_ofs], edx
	mov	rsi, [rdi]
	call	qword [rsi+tui_vsizechanged]
	; our last child is the inner vertical, we need to reset both tui_text's contents
	mov	rdi, [rbx+tui_children_ofs]
	mov	rsi, [rdi+_list_last_ofs]
	mov	r15, [rsi+_list_valueofs]
	; r15 == our heightlocked vertical layout row
	; its first child is the BY TIME ago
	; its last child is the text itself
	mov	rdi, r13
	call	ui$jsonage
	mov	r14, rax
	mov	rdi, r13
	mov	rsi, .prop_by
	call	json$getvaluebyname
	test	rax, rax
	jz	.no_username
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.no_username
.username_ready:
	mov	rdi, [item_format4]
	mov	rsi, rax
	mov	rdx, r14
	call	formatter$doit
	mov	rdi, r14
	mov	r14, rax
	call	heap$free
	mov	rdi, [r15+tui_children_ofs]
	mov	rsi, [rdi+_list_first_ofs]
	mov	rdi, [rsi+_list_valueofs]
	mov	rsi, r14
	call	tui_text$nvsettext
	mov	rdi, r14
	call	heap$free
	; do the same for the text
	; get our text from the json in r13
	mov	rdi, r13
	mov	rsi, .prop_text
	call	json$getvaluebyname
	test	rax, rax
	jz	.no_text
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.no_text
.text_ready:
	mov	rdi, rax
	call	textify
	mov	rdi, [r15+tui_children_ofs]
	mov	rsi, [rdi+_list_last_ofs]
	mov	rdi, [rsi+_list_valueofs]
	mov	rsi, rax
	push	rax
	call	tui_text$nvsettext
	pop	rdi
	call	heap$free

	mov	rdi, rbx
	mov	rsi, [rbx]
	call	qword [rsi+tui_vlayoutchanged]

	pop	r15 r14 r13 r12 rbx
	epilog
.no_username:
	mov	rax, .null
	jmp	.username_ready
.no_text:
	mov	rax, .null
	jmp	.text_ready
cleartext .prop_text, 'text'
cleartext .prop_by, 'by'
cleartext .null, '(null)'



	; helper function to create a heightlocked row
	; rdi == unsigned item number (so we can track updates)
	; rsi == item json
	; edx == nesting level
falign
ui$itemnewrow:
	prolog	ui$itemnewrow
	push	rbx r12 r13 r14
	; heightlocked row ==
	; indent | light coloured by TIME ago
	;        | text
	mov	r12, rdi
	mov	r13, rsi
	mov	r14d, edx
	mov	edi, tui_object_size + 8	; +8 so we can store an id at tui_object_size of this row
	call	heap$alloc
	mov	rdi, rax
	push	rax
	mov	qword [rax], .custom_vtable_outer
	movq	xmm0, [_math_onehundred]
	mov	esi, 2
	call	tui_object$init_di
	pop	rbx
	; set our item number for future easy reference
	mov	qword [rbx+tui_object_size], r12
	; set its layout to horizontal
	mov	dword [rbx+tui_layout_ofs], tui_layout_horizontal
	; add to that our spacer

	mov	edi, tui_background_size
	call	heap$alloc
	mov	qword [rax], tui_background$vtable
	mov	rdi, rax
	mov	esi, r14d
	mov	edx, 2
	mov	ecx, ' '
	shl	esi, 1
	ansi_colors r8d, 'lightgray', 'black'
	push	rax
	call	tui_background$init_ii
	pop	rsi
	mov	rdi, rbx
	mov	rdx, [rdi]
	call	qword [rdx+tui_vappendchild]
	; now we need a 100% wide by variable height vertical layout
	mov	edi, tui_object_size
	call	heap$alloc
	mov	r12, rax
	mov	rdi, rax
	mov	qword [rax], .custom_vtable_inner
	movq	xmm0, [_math_onehundred]
	mov	esi, 2
	call	tui_object$init_di
	; add that to our horizontal outer box
	mov	rdi, rbx
	mov	rsi, r12
	mov	rdx, [rdi]
	call	qword [rdx+tui_vappendchild]
	; to our inner heightlocked one, we need a 100% wide x 1 for by + timeago
	; first, get our text for it
	mov	rdi, r13
	call	ui$jsonage
	mov	r14, rax
	mov	rdi, r13
	mov	rsi, .prop_by
	call	json$getvaluebyname
	test	rax, rax
	jz	.no_username
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.no_username
.username_ready:
	mov	rdi, [item_format4]
	mov	rsi, rax
	mov	rdx, r14
	call	formatter$doit
	mov	rdi, r14
	mov	r14, rax
	call	heap$free
	; now we can create our tui_text 100% wide by 1
	movq	xmm0, [_math_onehundred]		; width
	mov	edi, 1					; height
	mov	rsi, r14				; initial text
	ansi_colors edx, 'darkslategray', 'black'
	mov	ecx, edx				; colors and focuscolors
	xor	r8d, r8d				; no spinner
	call	tui_text$new_di
	mov	dword [rax+tui_text_focussed_ofs], 0
	mov	dword [rax+tui_text_docursor_ofs], 0
	mov	dword [rax+tui_text_editable_ofs], 0
	mov	dword [rax+tui_text_align_ofs], tui_textalign_left
	; add that to our RHS in r12
	mov	rdi, r12
	mov	rsi, rax
	mov	rdx, [rdi]
	call	qword [rdx+tui_vappendchild]
	; free our initialtext string
	mov	rdi, r14
	call	heap$free
	; get our text from the json
	mov	rdi, r13
	mov	rsi, .prop_text
	call	json$getvaluebyname
	test	rax, rax
	jz	.no_text
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.no_text
.text_ready:
	mov	rdi, rax
	call	textify
	; and last but not least, a heightlocked multiline
	movq	xmm0, [_math_onehundred]		; width
	mov	edi, 1					; height
	mov	rsi, rax				; initial text
	ansi_colors edx, 'lightgray', 'black'
	mov	ecx, edx				; colors and focuscolors
	xor	r8d, r8d				; no spinner
	push	rax
	call	tui_text$new_di
	; enable multiline, left alignment, wordwrap, and heightlock
	mov	dword [rax+tui_text_multiline_ofs], 1
	mov	dword [rax+tui_text_focussed_ofs], 0
	mov	dword [rax+tui_text_docursor_ofs], 0
	mov	dword [rax+tui_text_editable_ofs], 0
	mov	dword [rax+tui_text_heightlock_ofs], 1
	mov	dword [rax+tui_text_align_ofs], tui_textalign_left
	mov	dword [rax+tui_text_wrap_ofs], 2
	; add that to our RHS in r12
	mov	rdi, r12
	mov	rsi, rax
	mov	rdx, [rdi]
	call	qword [rdx+tui_vappendchild]
	pop	rdi
	call	heap$free
	; done, dusted
	mov	rax, rbx
	pop	r14 r13 r12 rbx
	epilog
.no_username:
	mov	rax, .null
	jmp	.username_ready
.no_text:
	mov	rax, .null
	jmp	.text_ready
cleartext .prop_text, 'text'
cleartext .prop_by, 'by'
cleartext .null, '(null)'
	
falign
.outer_layoutchanged:
	; called with rdi == our row object, we need to set our row's height to our second child's height
	; then call our own sizechanged, followed by tui_object's layoutchanged
	push	rdi
	mov	rsi, [rdi+tui_children_ofs]
	mov	rdx, [rsi+_list_last_ofs]
	mov	rdx, [rdx+_list_valueofs]
	mov	ecx, [rdx+tui_height_ofs]
	mov	r8, [rdi]
	mov	[rdi+tui_height_ofs], ecx
	push	rcx
	call	qword [r8+tui_vsizechanged]
	pop	rcx
	; set our first child's height = second child's height also
	mov	rdi, [rsp]
	mov	rsi, [rdi+tui_children_ofs]
	mov	rdx, [rsi+_list_first_ofs]
	mov	rdi, [rdx+_list_valueofs]
	mov	r8, [rdi]
	mov	[rdi+tui_height_ofs], ecx
	call	qword [r8+tui_vsizechanged]
	pop	rdi
	call	tui_object$layoutchanged
	ret
falign
.inner_layoutchanged:
	; same as above, but we set it to our last child's height+1
	push	rdi
	mov	rsi, [rdi+tui_children_ofs]
	mov	rdx, [rsi+_list_last_ofs]
	mov	rdx, [rdx+_list_valueofs]
	mov	ecx, [rdx+tui_height_ofs]
	mov	r8, [rdi]
	add	ecx, 1
	mov	[rdi+tui_height_ofs], ecx
	call	qword [r8+tui_vsizechanged]
	pop	rdi
	call	tui_object$layoutchanged
	ret


	; this is a copy of tui_object$simple_vtable, with an override for our layoutchanged
dalign
.custom_vtable_outer:
	dq	tui_object$cleanup, tui_object$simple_clone, tui_object$draw, tui_object$redraw, tui_object$updatedisplaylist, tui_object$sizechanged
	dq	tui_object$timer, .outer_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_object$appendchild, tui_object$appendbastard, tui_object$prependchild, tui_object$contains, tui_object$getchildindex
	dq	tui_object$removechild, tui_object$removebastard, tui_object$removeallchildren, tui_object$removeallbastards
	dq	tui_object$getobjectsunderpoint, tui_object$flatten, tui_object$firekeyevent, tui_object$ontab, tui_object$onshifttab
	dq	tui_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked
dalign
.custom_vtable_inner:
	dq	tui_object$cleanup, tui_object$simple_clone, tui_object$draw, tui_object$redraw, tui_object$updatedisplaylist, tui_object$sizechanged
	dq	tui_object$timer, .inner_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_object$appendchild, tui_object$appendbastard, tui_object$prependchild, tui_object$contains, tui_object$getchildindex
	dq	tui_object$removechild, tui_object$removebastard, tui_object$removeallchildren, tui_object$removeallbastards
	dq	tui_object$getobjectsunderpoint, tui_object$flatten, tui_object$firekeyevent, tui_object$ontab, tui_object$onshifttab
	dq	tui_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked




	; three arguments: rdi == our main tui_object, esi == key, edx == esc_key
falign
ui$item_keyevent:
	prolog	ui$item_keyevent
	cmp	esi, 27			; ESC key
	je	.bailout
	cmp	esi, 't'
	je	.bailout
	cmp	esi, 'T'
	je	.bailout
	cmp	esi, 'n'
	je	.bailout
	cmp	esi, 'N'
	je	.bailout
	cmp	esi, 'a'
	je	.bailout
	cmp	esi, 'A'
	je	.bailout
	cmp	esi, 's'
	je	.bailout
	cmp	esi, 'S'
	je	.bailout
	cmp	esi, 'j'
	je	.bailout
	cmp	esi, 'J'
	je	.bailout
if defined use_reset_goods
	cmp	esi, 'r'
	je	.bailout
	cmp	esi, 'R'
	je	.bailout
end if
	cmp	edx, 0x41		; up arrow
	je	.uparrow
	cmp	edx, 0x42		; down arrow
	je	.downarrow
	cmp	edx, 0x44		; left arrow
	je	.bailout
	xor	eax, eax
	epilog
.bailout:
	push	rsi rdx
	mov	rax, [main_datagrid]
	; swap the item_screen with the data_grid
	; (we are cheating a bit and directly manipulating its child list)
	mov	rsi, [main_screen]
	mov	rdi, [rsi+tui_children_ofs]
	mov	rdx, [_list_first]
	mov	[rdx+_list_valueofs], rax
	; call the main screen's layoutchanged
	mov	rdi, [main_screen]
	mov	rsi, [rdi]
	call	qword [rsi+tui_vlayoutchanged]
	; clear our displayitem
	mov	qword [displayitem], 0
	; clear our item_kids map
	mov	rdi, [item_kids]
	xor	esi, esi
	call	unsignedmap$clear
	; destroy our item_screen itself
	mov	rdi, [item_screen]
	mov	rsi, [rdi]
	call	qword [rsi+tui_vcleanup]
	mov	rdi, [item_screen]
	call	heap$free
	mov	qword [item_screen], 0
	pop	rsi rdx
	cmp	esi, 27
	jne	.pass_keyevent
	mov	eax, 1
	epilog
.pass_keyevent:
	mov	rdi, [main_datagrid]
	call	ui$main_keyevent
	epilog
.uparrow:
	; if our scroll pos at +4 is already 0, do nothing
	cmp	dword [rdi+tui_scroll_ofs+4], 0
	je	.uparrow_nothingtodo
	; otherwise, decrement it and be done
	sub	dword [rdi+tui_scroll_ofs+4], 1
	; call draw
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	; done, dusted
.uparrow_nothingtodo:
	mov	eax, 1
	epilog
.downarrow:
	mov	rsi, [rdi+tui_parent_ofs]
	mov	ecx, [rsi+tui_height_ofs]	; height of our actual parent object
	xor	r8d, r8d
	push	rdi rcx
	mov	rdi, [rdi+tui_children_ofs]
	mov	rsi, .heightcalc
	push	r8
	mov	rdx, rsp
	call	list$foreach_arg
	pop	r8 rcx rdi
	sub	r8d, 99				; the height of our "filler" less the statusbar height
	cmp	r8, rcx
	jbe	.downarrow_nothingtodo
	sub	r8, rcx
	cmp	dword [rdi+tui_scroll_ofs+4], r8d
	jae	.downarrow_nothingtodo
	add	dword [rdi+tui_scroll_ofs+4], 1
	; call draw
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
.downarrow_nothingtodo:
	mov	eax, 1
	epilog
falign
.heightcalc:
	; rdi == tui object of our item screen, rsi == pointer to stackspace for counter
	mov	eax, [rdi+tui_height_ofs]
	add	dword [rsi], eax
	ret
	


	; no arguments, called when an item is selected, and when new items come in (compose)
falign
ui$itemupdate:
	prolog	ui$itemupdate
	; sanity check to make sure we have a displayitem
	cmp	qword [displayitem], 0
	je	.nothingtodo
	; otherwise, find the item in the item map
	mov	rdi, [displayitem]
	mov	esi, 10
	call	string$from_unsigned
	push	rax
	mov	rdi, [items]
	mov	rsi, rax
	call	stringmap$find_value
	pop	rdi
	push	rax rdx
	call	heap$free
	pop	rdx rax
	test	eax, eax
	jz	.nothingtodo			; sanity only, it better be here
	test	rdx, rdx
	jz	.nothingtodo
	push	rbx r12 r13 r14 r15
	mov	rbx, rdx			; the item's json object
	; otherwise, set/update first line of text, which is rank and title
	mov	rdi, rdx
	mov	rsi, .prop_rank
	call	json$getvaluebyname
	test	rax, rax
	jz	.kakked
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.kakked
	; turn that into an unsigned
	mov	rdi, rax
	call	string$to_unsigned
	mov	r12, rax
	; get the title
	mov	rdi, rbx
	mov	rsi, .prop_title
	call	json$getvaluebyname
	test	rax, rax
	jz	.kakked
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.kakked
	; format those two for our first display line
	mov	rdi, [item_format1]
	mov	rsi, r12
	mov	rdx, rax
	call	formatter$doit
	mov	r12, rax
	; get the first child from the item screen, which we know is tui_text
	mov	rdi, [item_screen]
	mov	rsi, [rdi+tui_children_ofs]
	mov	rdx, [rsi+_list_first_ofs]
	mov	rdi, [rdx+_list_valueofs]
	mov	rsi, rax
	call	tui_text$nvsettext
	; free the format string
	mov	rdi, r12
	call	heap$free
	; next up, second line
	mov	rdi, rbx
	mov	rsi, .prop_score
	call	json$getvaluebyname
	test	rax, rax
	jz	.check_ago

	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.check_ago
	; turn that into an unsigned
	mov	rdi, rax
	call	string$to_unsigned
	mov	r12, rax
	; get the username
	mov	rdi, rbx
	mov	rsi, .prop_by
	call	json$getvaluebyname
	test	rax, rax
	jz	.check_ago
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.check_ago
	mov	r13, rax
	; age next
	mov	rdi, rbx
	mov	rsi, .prop_age
	call	json$getvaluebyname
	test	rax, rax
	jz	.kakked
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.kakked
	mov	r14, rax
	; comment count last but not least
	mov	rdi, rbx
	mov	rsi, .prop_descendants
	call	json$getvaluebyname
	test	rax, rax
	jz	.check_ago
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.check_ago
	; turn that into an unsigned
	mov	rdi, rax
	call	string$to_unsigned
	mov	r15, rax
	; second line formatter
	mov	rdi, [item_format2]
	mov	rsi, r12
	mov	rdx, r13
	mov	rcx, r14
	mov	r8, r15
	mov	r9, [item_format3]
	test	r15, r15
	cmovnz	rdi, r9
	call	formatter$doit
	mov	r12, rax
	; get the second child from the item screen, which we know is tui_text
	mov	rdi, [item_screen]
	mov	rsi, [rdi+tui_children_ofs]
	mov	rdx, [rsi+_list_first_ofs]
	mov	rdx, [rdx+_list_nextofs]
	mov	rdi, [rdx+_list_valueofs]
	mov	rsi, rax
	call	tui_text$nvsettext
	; free the format string
	mov	rdi, r12
	call	heap$free
	jmp	.url_or_text
cleartext .ago_cat, ' ago'
.check_ago:
	; for job listings, there is no score, only a time
	mov	rdi, rbx
	call	ui$jsonage
	mov	rdi, rax
	mov	rsi, .ago_cat
	push	rax
	call	string$concat
	mov	rdi, [rsp]
	mov	[rsp], rax
	call	heap$free
	mov	rax, [rsp]
	mov	rdi, [item_screen]
	mov	rsi, [rdi+tui_children_ofs]
	mov	rdx, [rsi+_list_first_ofs]
	mov	rdx, [rdx+_list_nextofs]
	mov	rdi, [rdx+_list_valueofs]
	mov	rsi, rax
	call	tui_text$nvsettext
	pop	rdi
	call	heap$free

.url_or_text:
	; url or text is next
	xor	r14d, r14d
	xor	r15d, r15d
	mov	rdi, rbx
	mov	rsi, .prop_url
	call	json$getvaluebyname
	xor	r12d, r12d
	test	rax, rax
	jz	.skip_url
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.skip_url
	mov	r12, rax
	mov	r14, [rax]		; its length
.skip_url:
	mov	rdi, rbx
	mov	rsi, .prop_text
	call	json$getvaluebyname
	xor	r13d, r13d
	test	rax, rax
	jz	.skip_text
	mov	rdi, rax
	call	json$stringvalue
	mov	r13, rax
	mov	r15, [rax]		; its length
.skip_text:
	mov	rdi, r12
	or	rdi, rsi
	jz	.skip_line3
	; depending on which one's length is set determines which one we'll use
	test	r14d, r14d
	cmovz	r12, r13
	; textify it (good for asks, etc as they can also be HTML)

	; update 20160809: for Ask HN (and probably others) where no text is specified
	; r12 can end up being 0 here...
	test	r12, r12
	jz	.skip_line3
	; end update 20160809

	mov	rdi, r12
	call	textify
	; get the third child from the item screen, which we know is tui_text
	mov	rdi, [item_screen]
	mov	rsi, [rdi+tui_children_ofs]
	mov	rdx, [rsi+_list_first_ofs]
	mov	rdx, [rdx+_list_nextofs]
	mov	rdx, [rdx+_list_nextofs]
	mov	rdi, [rdx+_list_valueofs]
	mov	rsi, rax
	push	rax
	call	tui_text$nvsettext
	pop	rdi
	call	heap$free

.skip_line3:

	; next up: iterate/descend its kids, and their kids, etc.
	; first, recursively retrieve our items
	mov	rdi, [item_kids]
	mov	rsi, [displayitem]
	xor	edx, edx
	call	unsignedmap$insert_unique
	; get its kids list
	mov	rdi, rbx
	mov	rsi, .prop_kids
	call	json$getvaluebyname
	; if there is no kids, we are done
	test	rax, rax
	jz	.kakked
	; otherwise, foreach item there, do the deed
	mov	rdi, rax
	mov	rsi, .retriever
	call	json$foreach
	; so now, as items come in, this will repeatedly get called
	; and new kids of kids, etc will get updated correctly

	; so now we have to walk forward and update all of our item_screen's
	; rows starting at line 4, providing there _is_ a next
	mov	rdi, [item_screen]
	mov	rsi, [rdi+tui_children_ofs]
	mov	rdx, [rsi+_list_first_ofs]
	mov	rdx, [rdx+_list_nextofs]
	mov	rdx, [rdx+_list_nextofs]
	mov	ecx, 1
	mov	rdx, [rdx+_list_nextofs]
	; make a spot on our stack to hold that value and our nesting level
	push	rcx rdx
	; recurse again through the kids, updating as we go
	mov	rdi, rbx
	mov	rsi, .prop_kids
	call	json$getvaluebyname
	mov	rdi, rax
	mov	rsi, .rowupdate
	mov	rdx, rsp
	call	json$foreach_arg
	add	rsp, 16

	; call layout changed on our item_screen
	mov	rdi, [item_screen]
	mov	rsi, [rdi]
	call	qword [rsi+tui_vlayoutchanged]
	
.kakked:
	pop	r15 r14 r13 r12 rbx
.nothingtodo:
	epilog
falign
.rowupdate:
	; rdi == json value object of the kids id, rsi == pointer to our item_screen's child list ITEM and nesting level
	cmp	dword [rdi+json_type_ofs], json_value
	jne	.rowupdate_nothingtodo
	push	rbx r12 r13 r14
	mov	r12, rsi
	mov	rdi, [rdi+json_value_ofs]
	push	rdi
	call	string$to_unsigned
	mov	r13, rax			; unsigned numeric form of our id
	pop	rsi
	mov	rdi, [items]
	call	stringmap$find_value
	test	eax, eax
	jz	.rowupdate_notfound
	test	rdx, rdx
	jz	.rowupdate_notfound
	mov	rbx, rdx
	; so now, rbx == json object of the item we are updating
	; make sure it does not contain "deleted":true
	mov	rdi, rdx
	mov	rsi, .prop_deleted
	call	json$getvaluebyname
	test	rax, rax
	jz	.rowupdate_keepgoing
	mov	rdi, rax
	call	json$stringvalue
	test	rax, rax
	jz	.rowupdate_keepgoing
	mov	rdi, rax
	mov	rsi, .true
	call	string$equals
	test	eax, eax
	jnz	.rowupdate_notfound

.rowupdate_keepgoing:
	; and r12 is a pointer to our item_screen's child list ITEM
	; r13 == numeric unsigned of our id
	; three case scenarios:
	; 1) this item has not yet been added, thus list item has no next
	; 2) this item got updated, this row's id matches our own
	; 3) this item got reordered, this row's id != match our own
	; for both case 2 and 3, we can safely just update the row
	mov	rsi, [r12]			; list item pointer
	cmp	qword [rsi+_list_nextofs], 0
	je	.rowupdate_caseone

	; update this row with our goods
	mov	rcx, [rsi+_list_valueofs]
	mov	rdi, r13
	mov	rsi, rbx
	mov	rdx, [r12+8]
	call	ui$itemupdaterow
	; move our list pointer to the next spot before we keep going

	mov	rsi, [r12]
	mov	rdx, [rsi+_list_nextofs]
	mov	[r12], rdx

.rowupdate_godeeper:
	mov	rdi, rbx
	mov	rsi, .prop_kids
	call	json$getvaluebyname
	; if there are no kids, we are done
	test	rax, rax
	jz	.rowupdate_notfound
	mov	rdi, rax
	mov	rsi, .rowupdate
	; make new stackspace for our goods
	mov	rcx, [r12+8]
	mov	r8, [r12]
	add	rcx, 1
	push	rcx r8
	mov	rdx, rsp
	call	json$foreach_arg
	mov	rcx, [rsp]
	mov	[r12], rcx
	add	rsp, 16
.rowupdate_notfound:
	pop	r14 r13 r12 rbx
.rowupdate_nothingtodo:
	ret
cleartext .prop_deleted, 'deleted'
cleartext .true, 'true'

.rowupdate_caseone:
	; create a new row to hold our text with the appropriate nesting level
	mov	rdi, r13
	mov	rsi, rbx			; item json
	mov	rdx, [r12+8]			; nesting level
	call	ui$itemnewrow
	; insert that row into our children before the list item at [r12]
	mov	rdi, [item_screen]
	mov	rcx, rdi
	mov	rdi, [rdi+tui_children_ofs]
	mov	rsi, [r12]			; our current list location
	mov	rdx, rax
	mov	[rax+tui_parent_ofs], rcx	; set our new row's parent
	call	list$insert_before

	; done with this row, go deeper
	jmp	.rowupdate_godeeper

falign
.retriever:
	; this is called with rdi == json value object of the kids id
	cmp	dword [rdi+json_type_ofs], json_value
	jne	.retriever_nothingtodo
	mov	rdi, [rdi+json_value_ofs]
	mov	esi, 10
	push	rdi
	call	string$to_unsigned
	mov	rdi, [item_kids]
	mov	rsi, rax
	xor	edx, edx
	call	unsignedmap$insert_unique

	; next up, make sure the hnmodel either has or has queued this item already:
	mov	rdi, [rsp]
	xor	esi, esi		; not an update
	call	hnmodel$retrieve
	; and last but not least, if this item has already been retrieved, recursively walk its kids
	mov	rdi, [items]
	pop	rsi
	call	stringmap$find_value
	test	eax, eax
	jz	.retriever_nothingtodo
	test	rdx, rdx
	jz	.retriever_nothingtodo
	mov	rdi, rdx
	mov	rsi, .prop_kids
	call	json$getvaluebyname
	; if there is no kids, we are doen
	test	rax, rax
	jz	.retriever_nothingtodo
	; otherwise, foreach item there, do the deed
	mov	rdi, rax
	mov	rsi, .retriever
	call	json$foreach
.retriever_nothingtodo:
	ret
cleartext .prop_rank, 'rank'
cleartext .prop_kids, 'kids'
cleartext .prop_title, 'title'
cleartext .prop_score, 'score'
cleartext .prop_by, 'by'
cleartext .prop_age, 'age'
cleartext .prop_descendants, 'descendants'
cleartext .prop_url, 'url'
cleartext .prop_text, 'text'
	



	; this gets called from our datagrid with rdi == datagrid, rsi == json of the item selected
falign
ui$itemselected:
	prolog	ui$itemselected
	push	rbx
	mov	rbx, rsi
	; create our item_screen, remove the datagrid from the mainscreen, replace it with item_screen
	mov	edi, tui_object_size
	call	heap$alloc
	mov	rdi, rax
	mov	[item_screen], rax
	; use our custom vtable so we can grab the main nav keys
	mov	qword [rax], .custom_vtable
	movq	xmm0, [_math_onehundred]
	movq	xmm1, [_math_onehundred]
	call	tui_object$init_dd
	; so now we have a 100% x 100% dynamic-sized outer tui object
	; add 3 empty tui_text lines, heightlocked for our item-specific goods
	movq	xmm0, [_math_onehundred]		; width
	mov	edi, 1					; height
	xor	esi, esi				; initial text
	ansi_rgbi edx, 40, 40, 40
	ansi_rgbi ecx, 247, 247, 247
	shl	edx, 8
	or	edx, ecx
	mov	ecx, edx				; colors and focuscolors
	xor	r8d, r8d				; no spinner
	call	tui_text$new_di
	; enable multiline, left alignment, wordwrap, and heightlock
	mov	dword [rax+tui_text_multiline_ofs], 1
	mov	dword [rax+tui_text_focussed_ofs], 0
	mov	dword [rax+tui_text_docursor_ofs], 0
	mov	dword [rax+tui_text_editable_ofs], 0
	mov	dword [rax+tui_text_heightlock_ofs], 1
	mov	dword [rax+tui_text_align_ofs], tui_textalign_left
	mov	dword [rax+tui_text_wrap_ofs], 2

	; append that to our item_screen
	mov	rdi, [item_screen]
	mov	rsi, rax
	mov	rdx, [rdi]
	call	qword [rdx+tui_vappendchild]

	; again for the next line
	movq	xmm0, [_math_onehundred]		; width
	mov	edi, 1					; height
	xor	esi, esi				; initial text
	ansi_rgbi edx, 130, 130, 130
	ansi_rgbi ecx, 247, 247, 247
	shl	edx, 8
	or	edx, ecx
	mov	ecx, edx				; colors and focuscolors
	xor	r8d, r8d				; no spinner
	call	tui_text$new_di
	; enable multiline, left alignment, wordwrap, and heightlock
	mov	dword [rax+tui_text_multiline_ofs], 1
	mov	dword [rax+tui_text_focussed_ofs], 0
	mov	dword [rax+tui_text_docursor_ofs], 0
	mov	dword [rax+tui_text_editable_ofs], 0
	mov	dword [rax+tui_text_heightlock_ofs], 1
	mov	dword [rax+tui_text_align_ofs], tui_textalign_left
	mov	dword [rax+tui_text_wrap_ofs], 2

	; append that to our item_screen
	mov	rdi, [item_screen]
	mov	rsi, rax
	mov	rdx, [rdi]
	call	qword [rdx+tui_vappendchild]

	; and one more for the url or text itself
	movq	xmm0, [_math_onehundred]		; width
	mov	edi, 1					; height
	xor	esi, esi				; initial text
	ansi_colors edx, 'lightgray', 'black'
	mov	ecx, edx				; colors and focuscolors
	xor	r8d, r8d				; no spinner
	call	tui_text$new_di
	; enable multiline, left alignment, wordwrap, and heightlock
	mov	dword [rax+tui_text_multiline_ofs], 1
	mov	dword [rax+tui_text_focussed_ofs], 0
	mov	dword [rax+tui_text_docursor_ofs], 0
	mov	dword [rax+tui_text_editable_ofs], 0
	mov	dword [rax+tui_text_heightlock_ofs], 1
	mov	dword [rax+tui_text_align_ofs], tui_textalign_left
	mov	dword [rax+tui_text_wrap_ofs], 2

	; append that to our item_screen
	mov	rdi, [item_screen]
	mov	rsi, rax
	mov	rdx, [rdi]
	call	qword [rdx+tui_vappendchild]

	; to "fill" the gap in our scrollable vertical layout window
	; we need a 100% wide by 100 row high background object (so that it clears empty space)
	mov	edi, tui_background_size
	call	heap$alloc
	mov	qword [rax], tui_background$vtable
	movq	xmm0, [_math_onehundred]
	mov	esi, 100
	mov	rdi, rax
	mov	edx, ' '
	ansi_colors ecx, 'lightgray', 'black'
	push	rax
	call	tui_background$init_di
	pop	rsi
	; append that to our item_screen
	mov	rdi, [item_screen]
	mov	rdx, [rdi]
	call	qword [rdx+tui_vappendchild]

	mov	rax, [item_screen]
	; replace the datagrid with our itemscreen
	; (we are cheating a bit and directly manipulating its child list)
	mov	rsi, [main_screen]
	mov	rdi, [rsi+tui_children_ofs]
	mov	rdx, [_list_first]
	mov	[rdx+_list_valueofs], rax
	; set our item screen's parent = main_screen
	mov	[rax+tui_parent_ofs], rsi
	; call the main screen's layoutchanged
	mov	rdi, [main_screen]
	mov	rsi, [rdi]
	call	qword [rsi+tui_vlayoutchanged]

	; get the ID string value from the itemselected json
	mov	rdi, rbx
	mov	rsi, .id
	call	json$getvaluebyname
	mov	rdi, rax
	call	json$stringvalue
	; sanity only
	test	rax, rax
	jz	.alldone
	mov	rdi, rax
	mov	esi, 10
	call	string$to_unsigned
	; set our global displayitem to it
	mov	[displayitem], rax
	call	ui$itemupdate
.alldone:
	
	pop	rbx
	epilog
cleartext .id, 'id'

	; this is a copy of tui_object$simple_vtable, with an override for our key handler
dalign
.custom_vtable:
	dq	tui_object$cleanup, tui_object$simple_clone, tui_object$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	ui$item_keyevent, tui_object$domodal, tui_object$endmodal, tui_object$exit, tui_object$calcbounds, tui_object$calcchildbounds
	dq	tui_object$appendchild, tui_object$appendbastard, tui_object$prependchild, tui_object$contains, tui_object$getchildindex
	dq	tui_object$removechild, tui_object$removebastard, tui_object$removeallchildren, tui_object$removeallbastards
	dq	tui_object$getobjectsunderpoint, tui_object$flatten, tui_object$firekeyevent, tui_object$ontab, tui_object$onshifttab
	dq	tui_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked


	; this gets called with rdi == our statusbar object, rsi == string for our status
falign
ui$statusbar_update:
	prolog	ui$statusbar_update
	mov	r8, [items]
	push	rbx r12
	mov	rbx, rdi
	mov	r12, rsi
	mov	rdi, [status_format]
	mov	rsi, [r8+_avlofs_right]		; items.count
	mov	rdx, [requestcount]
	mov	rcx, [bytecount]
	mov	r8, [errorcount]
	call	formatter$doit
	push	rax
	mov	rdi, rbx
	mov	rsi, rax
	call	tui_statusbar$nvsettext
	pop	rdi
	call	heap$free
	pop	r12 rbx
	epilog



	; this gets called with a single arg from hnmodel after the items map has been updated
	; rdi == string item that caused it or null if we did it from in here
falign
ui$compose:
	prolog	ui$compose
	; Burning Purpose: create a new json array for our main datagrid from mainorder + item map
	; this is not very efficient thanks to this thing wholly recomposing EVERYTHING for each
	; and every item that gets updated, but it is fast enough not to warrant more complexity
	; for keeping them together instead... if we were dealing with a huge amount of inflight
	; items, this choice would obviously not be acceptable. hahah, YMMV.
	push	rbx r12
	mov	r12, rdi
	mov	rdi, .noname
	call	json$newarray
	mov	rbx, rax

	mov	rdi, [mainorder]
	mov	rsi, .eachmainorder
	mov	rdx, rax
	call	list$foreach_arg

	; now we can update the main_datagrid
	mov	rdi, [main_datagrid]
	mov	rsi, rbx
	call	tui_datagrid$nvsetdata

	test	r12, r12
	jz	.skip_itemcheck
	mov	rdi, r12
	mov	esi, 10
	call	string$to_unsigned
	mov	rdi, [item_kids]
	mov	rsi, rax
	call	unsignedmap$find_value
	test	eax, eax
	jz	.skip_itemcheck

	; otherwise, it exists in the items list, so call itemupdate
	call	ui$itemupdate
.skip_itemcheck:
	pop	r12 rbx
	epilog
falign
.eachmainorder:
	; rdi == string key from list, rsi == json array destination
	push	rbx r12 r13 r14 r15
	mov	rbx, rdi
	mov	r12, rsi
	; find this item in our items map
	mov	rdi, [items]
	mov	rsi, rbx
	call	stringmap$find_value
	test	eax, eax
	jz	.nodeal
	test	rdx, rdx
	jz	.nodeal
	; otherwise, rdx == the item json, deep copy it
	mov	r14, rdx
	mov	rdi, rdx
	call	json$copy
	mov	r13, rax
	; set its rank = r12's arraylength+1
	mov	rdi, .rank
	call	string$copy
	push	rax
	mov	rdi, r12
	call	json$arraylength
	lea	rdi, [rax+1]
	mov	esi, 10
	call	string$from_unsigned
	pop	rdi
	mov	rsi, rax
	mov	r15, rax
	call	json$newvalue_nocopy
	; append that to our json copy in r13
	mov	rdi, r13
	mov	rsi, rax
	call	json$appendchild
	; add or replace the item map's json as well:
	mov	rdi, r14
	mov	rsi, .rank
	call	json$getvaluebyname
	test	rax, rax
	jnz	.set_map_rank
	; otherwise, add a new one to it
	mov	rdi, .rank
	mov	rsi, r15
	call	json$newvalue
	mov	rdi, r14
	mov	rsi, rax
	call	json$appendchild
	jmp	.map_rank_done
.set_map_rank:
	; rax is the rank in the map's json, replace its value
	; with a copy of the string in r15
	push	rax
	mov	rdi, r15
	call	string$copy
	pop	rsi
	mov	rdi, [rsi+json_value_ofs]
	mov	[rsi+json_value_ofs], rax
	call	heap$free
.map_rank_done:
	; calculate the age of this item
	mov	rdi, .age
	call	string$copy
	push	rax
	mov	rdi, r13
	call	ui$jsonage
	pop	rdi
	mov	rsi, rax
	mov	r15, rax
	call	json$newvalue_nocopy
	; append that to our json copy in r13
	mov	rdi, r13
	mov	rsi, rax
	call	json$appendchild
	; set the original map's age as well
	mov	rdi, r14
	mov	rsi, .age
	call	json$getvaluebyname
	test	rax, rax
	jnz	.set_map_age
	; otherwise, add a new one
	mov	rdi, .age
	mov	rsi, r15
	call	json$newvalue
	mov	rdi, r14
	mov	rsi, rax
	call	json$appendchild
	jmp	.skipage
.set_map_age:
	; rax is the rank in the map's json, replace its value
	; with a copy of the string in r15
	push	rax
	mov	rdi, r15
	call	string$copy
	pop	rsi
	mov	rdi, [rsi+json_value_ofs]
	mov	[rsi+json_value_ofs], rax
	call	heap$free
	jmp	.skipage
.skipage_free:
	pop	rdi
	call	heap$free
.skipage:
	; last but not least, append our new json object in r13 to our r12 array
	mov	rdi, r12
	mov	rsi, r13
	call	json$appendchild
.nodeal:
	pop	r15 r14 r13 r12 rbx
	ret
cleartext .noname, 'noname'
cleartext .rank, 'rank'
cleartext .time, 'time'
cleartext .age, 'age'