ponydos/viewer.asm

907 lines
16 KiB
NASM
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

%include "ponydos.inc"
cpu 8086
bits 16
WINDOW_STATUS_NORMAL equ 0
WINDOW_STATUS_MOVE equ 1
WINDOW_STATUS_RESIZE equ 2
; Resize button, title, space, close button
WINDOW_MIN_WIDTH equ 1 + 8 + 1 + 1
WINDOW_MIN_HEIGHT equ 2
; 0x0000
jmp near process_event
; 0x0003 PROC_INITIALIZE_ENTRYPOINT
; initialize needs to preserve ds
; in:
; ds:si = text filename
; ax:bx = file address
; cx = text file size in sectors
initialize:
push ds
; Setup the input parameters for now
mov bp, cs
mov ds, bp
mov si, tmp_window_title
mov dx, 1
call PONYDOS_SEG:SYS_OPEN_FILE ; TODO: error
mov dx, 0xF000
mov es, dx
xor bx, bx
xor di, di ; read
call PONYDOS_SEG:SYS_MODIFY_SECTORS
mov ax, es
; End of temporary set-up
; On entry, ds and es will not be set correctly for us
mov bp, cs
mov es, bp
push cx
call strlen
mov di, window_title
rep movsb
pop cx
mov ds, bp
mov [cur_file_address + 2], ax
mov [cur_file_address], bx
mov [beg_file_address], bx
shl cx, 1 ; 2
shl cx, 1 ; 4
shl cx, 1 ; 8
shl cx, 1 ; 16
shl cx, 1 ; 32
shl cx, 1 ; 64
shl cx, 1 ; 128
shl cx, 1 ; 256
shl cx, 1 ; 512
add bx, cx
mov [end_file_address], bx
call hook_self_onto_window_chain
call render_window
; We must explicitly request redraw from the compositor
call request_redraw
pop ds
retf
; process_event needs to preserve all registers other than ax
; in:
; al = event
; bx = window ID
; cx, dx = event-specific
; out:
; ax = event-specific
process_event:
push bx
push cx
push dx
push si
push di
push bp
push ds
push es
; On entry, ds and es will not be set correctly for us
mov bp, cs
mov ds, bp
mov es, bp
cmp al, WM_PAINT
jne .not_paint
call event_paint
jmp .end
.not_paint:
cmp al, WM_MOUSE
jne .not_mouse
call event_mouse
jmp .end
.not_mouse:
cmp al, WM_KEYBOARD
jne .not_keyboard
call event_keyboard
jmp .end
.not_keyboard:
cmp al, WM_UNHOOK
jne .not_unhook
call event_unhook
jmp .end
.not_unhook:
.end:
pop es
pop ds
pop bp
pop di
pop si
pop dx
pop cx
pop bx
retf
; ------------------------------------------------------------------
; File handlers
; ------------------------------------------------------------------
; in:
; es:di = where to start printing on screen
print_file:
push ax
push bx
push cx
push dx
push si
push di
push ds
lds si, [cur_file_address]
mov cx, [cs:window_height]
dec cx
mov dx, [cs:window_width]
mov bl, 1 ; Haven't read anything yet
.window_loop:
push di
.line_loop:
cmp si, [cs:end_file_address]
jne .not_end_file
test bl, bl
jz .end_window_loop ; Need to have read something to hit end-of-file
.not_end_file:
lodsb
xor bl, bl ; Have read something
; Special byte handling
cmp al, 0x0A ; \n
je .next_line
cmp al, 0x09 ; \t
jne .null_check
add di, 8
sub dx, 4
jc .next_line
jz .next_line
jmp .line_loop
.null_check:
test al, al
jz .end_window_loop
stosb
inc di
dec dx
jnz .line_loop
.next_line:
mov dx, [cs:window_width]
pop di
add di, [cs:window_width]
add di, [cs:window_width]
loop .window_loop
.ret:
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
.end_window_loop:
pop di
jmp .ret
; out:
; dx = non-zero if cur_file_address is updated
file_next_line:
push ax
push si
push ds
xor dx, dx
lds si, [cur_file_address]
.loop:
lodsb
cmp al, 0x0A ; \n
je .found_next_line
test al, al
jz .ret
cmp si, [cs:end_file_address]
je .ret
jmp .loop
.found_next_line:
cmp si, [cs:end_file_address]
je .ret
cmp byte [ds:si], 0
je .ret
not dx
mov [cs:cur_file_address], si
.ret:
pop ds
pop si
pop ax
ret
; out:
; dx = non-zero if cur_file_address is updated
file_prev_line:
push ax
push si
push ds
std
xor dx, dx
lds si, [cur_file_address]
cmp si, [cs:beg_file_address] ; Already at the beginning?
je .ret
dec si
cmp si, [cs:beg_file_address] ; Last line was empty?
je .ret
dec si
.loop:
cmp si, [cs:beg_file_address]
je .found_prev_line
lodsb
cmp al, 0x0A ; \n
jne .loop
inc si
inc si
.found_prev_line:
not dx
mov [cs:cur_file_address], si
.ret:
cld
pop ds
pop si
pop ax
ret
; ------------------------------------------------------------------
; Event handlers
; ------------------------------------------------------------------
; in:
; al = WM_PAINT
; bx = window ID
; out:
; clobbers everything
event_paint:
; Forward the paint event to the next window in the chain
; We must do this before we paint ourselves, because painting must
; happen from the back to the front
; Because we only have one window, we don't need to save our own ID
mov bx, [window_next]
call send_event
mov bx, [window_width] ; Buffer width, usually same as window width
mov cx, [window_width]
mov dx, [window_height]
mov si, window_data
mov di, [window_x]
mov bp, [window_y]
cmp di, 0
jge .not_clip_left
.clip_left:
; Adjust the start of buffer to point to the first cell
; that is on screen
sub si, di
sub si, di
; Adjust window width to account for non-rendered area
; that is off screen
add cx, di
; Set X to 0
xor di, di
.not_clip_left:
mov ax, di
add ax, cx
cmp ax, COLUMNS
jle .not_clip_right
.clip_right:
; Adjust the width to only go as far as the right edge
sub ax, COLUMNS
sub cx, ax
.not_clip_right:
mov ax, bp
add ax, dx
cmp ax, ROWS
jle .not_clip_bottom
.clip_bottom:
; Adjust the height to only go as far as the bottom edge
sub ax, ROWS
sub dx, ax
.not_clip_bottom:
call PONYDOS_SEG:SYS_DRAW_RECT
ret
; in:
; al = WM_MOUSE
; bx = window ID
; cl = X
; ch = Y
; dl = mouse buttons held down
; out:
; clobbers everything
event_mouse:
test dl, MOUSE_PRIMARY | MOUSE_SECONDARY
jnz .not_end_window_change
; If we were moving or resizing the window, releasing the
; button signals the end of the action
mov byte [window_status], WINDOW_STATUS_NORMAL
.not_end_window_change:
; Expand X and Y to 16 bits for easier calculations
; Because we only have one window, we don't need to save our own ID
xor bx, bx
mov bl, ch
xor ch, ch
; Are we moving the window at the moment?
cmp byte [window_status], WINDOW_STATUS_MOVE
jne .not_moving
call move_window
.not_moving:
; Are we resizing the window at the moment?
cmp byte [window_status], WINDOW_STATUS_RESIZE
jne .not_resizing
call resize_window
.not_resizing:
; Check if the mouse is outside our window
cmp cx, [window_x]
jl .outside ; x < window_x
cmp bx, [window_y]
jl .outside ; y < window_y
mov ax, [window_x]
add ax, [window_width]
cmp ax, cx
jle .outside ; window_x + window_width <= x
mov ax, [window_y]
add ax, [window_height]
cmp ax, bx
jle .outside ; window_y + window_height <= y
.inside:
cmp byte [window_mouse_released_inside], 0
je .not_click
test dl, MOUSE_PRIMARY | MOUSE_SECONDARY
jz .not_click
.click:
call event_click
.not_click:
; We need to keep track of if the mouse has been inside our
; window without the buttons held, in order to avoid
; generating click events in cases where the cursor is
; dragged into our window while buttons are held
test dl, MOUSE_PRIMARY | MOUSE_SECONDARY
jz .buttons_not_held
.buttons_held:
mov byte [window_mouse_released_inside], 0
jmp .buttons_end
.buttons_not_held:
mov byte [window_mouse_released_inside], 1
.buttons_end:
; We must forward the event even if it was inside our
; window, to make sure other windows know when the mouse
; leaves them
; Set x and y to 255 so that windows below ours don't think
; the cursor is inside them
; Also clear the mouse buttons not absolutely necessary
; but it's cleaner if other windows don't get any
; information about the mouse
mov al, WM_MOUSE
mov bx, [window_next]
mov cx, 0xffff
xor dl, dl
call send_event
ret
.outside:
mov byte [window_mouse_released_inside], 0
; Not our window, forward the event
mov al, WM_MOUSE
mov ch, bl ; Pack the X and Y back into cx
mov bx, [window_next]
call send_event
ret
ret
; in:
; bx = Y
; cx = X
; dl = mouse buttons
; out:
; clobbers ax
event_click:
; This is not a true event passed into our event handler, but
; rather one we've synthetized from the mouse event
; The reason we synthetize this event is because most interface
; elements react to clicks specifically, so having this event
; making implementing them easier
; Raising a window is done by first unhooking, then rehooking it to
; the window chain
call unhook_self_from_window_chain
call hook_self_onto_window_chain
call request_redraw
; Did the user click the title bar?
cmp [window_y], bx
jne .not_title_bar
.title_bar:
; Did the user click the window close button?
mov ax, [window_x]
add ax, [window_width]
dec ax
cmp ax, cx
jne .not_close
.close:
call unhook_self_from_window_chain
; Nothing can call into us again after we unhook
; the window, so deallocate the memory we have
; reserved
call deallocate_own_memory
; We don't need to call request_redraw here, since
; it will be called unconditionally above
jmp .title_bar_end
.not_close:
; Did the user click on the resize button?
cmp [window_x], cx
jne .not_resize
.resize:
mov byte [window_status], WINDOW_STATUS_RESIZE
jmp .title_bar_end
.not_resize:
; Clicking on the title bar signals beginning of a window
; move
mov byte [window_status], WINDOW_STATUS_MOVE
mov ax, [window_x]
sub ax, cx
mov [window_move_x_offset], ax
.title_bar_end:
.not_title_bar:
.end:
ret
; in:
; al = WM_KEYBOARD
; bx = window ID
; cl = typed character
; ch = pressed key
; out:
; clobbers everything
event_keyboard:
cmp ch, 0x50 ; down key
jne .up_key_check
call file_next_line
test dx, dx
jz .ret
call render_window
call request_redraw
.up_key_check:
cmp ch, 0x48 ; up key
jne .ret
call file_prev_line
test dx, dx
jz .ret
call render_window
call request_redraw
.ret:
ret
; in:
; al = WM_UNHOOK
; bx = window ID
; cx = window ID of the window to unhook from the window chain
; out:
; ax = own window ID if we did not unhook
; next window ID if we did
; clobbers everything else
event_unhook:
cmp bx, cx
je .unhook_self
; Save our own ID
push bx
; Propagate the event
mov bx, [window_next]
call send_event
; Update window_next in case the next one unhooked
mov [window_next], ax
; Return our own ID
pop ax
ret
.unhook_self:
; Return window_next to the caller, unhooking us from the
; chain
mov ax, [window_next]
ret
; ------------------------------------------------------------------
; Event handler subroutines
; ------------------------------------------------------------------
; in:
; bx = Y
; cx = X
move_window:
push ax
; Offset the X coördinate so that the apparent drag position
; remains the same
mov ax, cx
add ax, [window_move_x_offset]
; Only do an update if something has changed. Reduces flicker
cmp [window_x], ax
jne .update_location
cmp [window_y], bx
jne .update_location
jmp .end
.update_location:
mov [window_x], ax
mov [window_y], bx
call request_redraw
.end:
pop ax
ret
; in:
; bx = Y
; cx = X
resize_window:
push ax
push bx
push bp
; Calculate new width
mov ax, [window_width]
add ax, [window_x]
sub ax, cx
cmp ax, WINDOW_MIN_WIDTH
jge .width_large_enough
mov ax, WINDOW_MIN_WIDTH
.width_large_enough:
cmp ax, COLUMNS
jle .width_small_enough
mov ax, COLUMNS
.width_small_enough:
; Calculate new height
mov bp, [window_height]
add bp, [window_y]
sub bp, bx
cmp bp, WINDOW_MIN_HEIGHT
jge .height_large_enough
mov bp, WINDOW_MIN_HEIGHT
.height_large_enough:
cmp bp, ROWS
jle .height_small_engough
mov bp, ROWS
.height_small_engough:
; Only do an update if something has changed. Reduces flicker
cmp [window_width], ax
jne .update_size
cmp [window_height], bp
jne .update_size
jmp .end
.update_size:
mov bx, [window_x]
add bx, [window_width]
sub bx, ax
mov [window_x], bx
mov [window_width], ax
mov bx, [window_y]
add bx, [window_height]
sub bx, bp
mov [window_y], bx
mov [window_height], bp
call render_window
call request_redraw
.end:
pop bp
pop bx
pop ax
render_window:
push ax
push cx
push dx
push si
push di
; Clear window to be black-on-white
mov di, window_data
mov ax, [window_width]
mov cx, [window_height]
mul cx
mov cx, ax
mov ax, 0xf000 ; Attribute is in the high byte
rep stosw
; Set title bar to be white-on-black
mov di, window_data
mov ax, 0x0f00
mov cx, [window_width]
rep stosw
; Add title bar buttons
mov di, window_data
mov byte [di], 0x17 ; Resize arrow
add di, [window_width]
add di, [window_width]
sub di, 2
mov byte [di], 'x' ; Close button
; Add window title
mov di, window_data
add di, 2
mov si, window_title
mov cx, [window_width]
dec cx
dec cx
cmp cx, FS_DIRENT_NAME_SIZE
jle .copy_title
mov cx, FS_DIRENT_NAME_SIZE
.copy_title:
lodsb
stosb
inc di
loop .copy_title
; Print text
mov di, window_data
add di, [window_width]
add di, [window_width]
call print_file
pop di
pop si
pop dx
pop cx
pop ax
ret
; ------------------------------------------------------------------
; Window chain
; ------------------------------------------------------------------
; in:
; al = event
; bx = window to send the event to
; cx, dx = event-specific
; out:
; ax = event-specific, 0 if bx=0
send_event:
test bx, bx
jnz .non_zero_id
; Returning 0 if the window ID is 0 makes window unhooking simpler
xor ax, ax
ret
.non_zero_id:
push bp
; Push the return address
push cs
mov bp, .end
push bp
; Push the address we're doing a far-call to
mov bp, bx
and bp, 0xf000 ; Highest nybble of window ID marks the segment
push bp
xor bp, bp ; Event handler is always at address 0
push bp
retf
.end:
pop bp
ret
hook_self_onto_window_chain:
push ax
push es
mov ax, PONYDOS_SEG
mov es, ax
; Window ID is made of the segment (top nybble) and an arbitrary
; process-specific part (lower three nybbles). Since we only have
; one window, we can leave the process-specific part as zero
mov ax, cs
xchg [es:GLOBAL_WINDOW_CHAIN_HEAD], ax
; Save the old head of the chain, so that we can propagate events
; down to it
mov [window_next], ax
pop es
pop ax
ret
unhook_self_from_window_chain:
push bx
push cx
push es
mov ax, PONYDOS_SEG
mov es, ax
mov al, WM_UNHOOK
mov bx, [es:GLOBAL_WINDOW_CHAIN_HEAD]
; Our window ID is just our segment, see the comment in
; hook_self_onto_window_chain
mov cx, cs
call send_event
; Update the head of the chain, in case we were at the head
mov [es:GLOBAL_WINDOW_CHAIN_HEAD], ax
pop es
pop cx
pop bx
ret
; ------------------------------------------------------------------
; Memory management
; ------------------------------------------------------------------
deallocate_own_memory:
push bx
push cx
push es
mov bx, PONYDOS_SEG
mov es, bx
; Segment 0xn000 corresponds to slot n in the allocation table
mov bx, cs
mov cl, 12
shr bx, cl
mov byte [es:GLOBAL_MEMORY_ALLOCATION_MAP + bx], 0
pop es
pop cx
pop bx
ret
; ------------------------------------------------------------------
; Painting
; ------------------------------------------------------------------
request_redraw:
push ax
push es
mov ax, PONYDOS_SEG
mov es, ax
mov byte [es:GLOBAL_REDRAW], 1
pop es
pop ax
ret
; ------------------------------------------------------------------
; String functions
; ------------------------------------------------------------------
; in:
; ds:si = string
; out:
; cx = stlen
strlen:
push ax
push di
push es
mov cx, ds
mov es, cx
mov di, si
mov cx, -1
xor ax, ax
repne scasb
not cx
dec cx
pop es
pop di
pop ax
ret
; ------------------------------------------------------------------
; Variables
; ------------------------------------------------------------------
tmp_window_title db "ponydos.asm", 0, 0
window_title times FS_DIRENT_NAME_SIZE + 1 db 0
cur_file_address: dw 0 ; Segment
dw 0
beg_file_address: dw 0
end_file_address: dw 0
window_next dw 0xffff
window_x dw 4
window_y dw 12
window_width dw 32
window_height dw 8
window_mouse_released_inside db 0
window_status db WINDOW_STATUS_NORMAL
window_move_x_offset dw 0
section .bss
window_data resw ROWS*COLUMNS