d4c15687c8
Some BIOSs initialize the VGA card by default into a mode where the high bit of background nybble signals that the cell should blink. The simple way to avoid this is by restricting the background colours to the range 0…7. However, since our mouse cursor is implemented by swapping the foreground and the background colours, we also need to restrict the foreground colours to the range 0…7.
1289 lines
24 KiB
NASM
1289 lines
24 KiB
NASM
%include "ponydos.inc"
|
||
cpu 8086
|
||
bits 16
|
||
|
||
%ifdef BLINKY
|
||
TITLEBAR_ATTRIBUTE equ 0x07
|
||
WINDOW_ATTRIBUTE equ 0x70
|
||
CANCEL_ATTRIBUTE equ 0x07
|
||
%else
|
||
TITLEBAR_ATTRIBUTE equ 0x0f
|
||
WINDOW_ATTRIBUTE equ 0xf0
|
||
CANCEL_ATTRIBUTE equ 0x8f
|
||
%endif
|
||
OK_ATTRIBUTE equ 0x20
|
||
OPEN_DIALOG_ATTRIBUTE equ 0x70
|
||
FILENAME_ATTRIBUTE equ WINDOW_ATTRIBUTE
|
||
|
||
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 3
|
||
|
||
; 0x0000
|
||
jmp near process_event
|
||
|
||
; 0x0003 PROC_INITIALIZE_ENTRYPOINT
|
||
; initialize needs to preserve ds
|
||
initialize:
|
||
push ds
|
||
|
||
; On entry, ds and es will not be set correctly for us
|
||
mov bp, cs
|
||
mov es, bp
|
||
mov ds, bp
|
||
|
||
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
|
||
; WM_OPEN_FILE needs ds to be left-as is
|
||
cmp al, WM_OPEN_FILE
|
||
je .no_set_ds
|
||
push cs
|
||
pop ds
|
||
.no_set_ds:
|
||
push cs
|
||
pop es
|
||
|
||
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:
|
||
|
||
cmp al, WM_OPEN_FILE
|
||
jne .not_open_file
|
||
call event_open_file
|
||
jmp .end
|
||
.not_open_file:
|
||
|
||
.end:
|
||
cmp byte [exiting], 0
|
||
je .not_exiting
|
||
call deallocate_own_memory
|
||
.not_exiting:
|
||
|
||
pop es
|
||
pop ds
|
||
pop bp
|
||
pop di
|
||
pop si
|
||
pop dx
|
||
pop cx
|
||
pop bx
|
||
retf
|
||
|
||
; ------------------------------------------------------------------
|
||
; Filename handling
|
||
; ------------------------------------------------------------------
|
||
|
||
; in:
|
||
; cl = typed character
|
||
; ch = pressed key
|
||
; out:
|
||
; clobbers bx
|
||
filename_char_add:
|
||
cmp word [cur_filename_address], filename_window_data.filename + 2*(FS_DIRENT_NAME_SIZE-1)
|
||
je .done
|
||
|
||
mov bx, [cur_filename_address]
|
||
mov byte [bx], cl
|
||
add word [cur_filename_address], 2
|
||
|
||
call request_redraw
|
||
|
||
.done:
|
||
ret
|
||
|
||
; out:
|
||
; clobbers bx
|
||
filename_char_del:
|
||
cmp word [cur_filename_address], filename_window_data.filename
|
||
je .done
|
||
|
||
mov bx, [cur_filename_address]
|
||
mov byte [bx - 2], 0x00
|
||
sub word [cur_filename_address], 2
|
||
|
||
call request_redraw
|
||
|
||
.done:
|
||
ret
|
||
|
||
; out:
|
||
; clobbers everything
|
||
filename_ok:
|
||
; Just ignore if there's no filename.
|
||
cmp byte [filename_window_data.filename], 0
|
||
je .end
|
||
|
||
mov cx, FS_DIRENT_NAME_SIZE
|
||
mov di, window_title
|
||
mov si, filename_window_data.filename
|
||
.loop:
|
||
lodsb
|
||
stosb
|
||
inc si
|
||
loop .loop
|
||
|
||
mov si, window_title
|
||
xor dx, dx ; do create empty file
|
||
call PONYDOS_SEG:SYS_OPEN_FILE
|
||
test ax, ax
|
||
jnz .got_file
|
||
mov si, ood_window
|
||
call raise_error
|
||
ret
|
||
|
||
.got_file:
|
||
call allocate_segment
|
||
test dx, dx
|
||
jnz .got_memory
|
||
mov si, oom_window
|
||
call raise_error
|
||
ret
|
||
|
||
.got_memory:
|
||
mov [cur_file_address + 2], dx
|
||
mov word [cur_file_address], 0
|
||
mov word [beg_file_address], 0
|
||
|
||
push es
|
||
mov es, dx
|
||
xor bx, bx
|
||
xor di, di ; read
|
||
call PONYDOS_SEG:SYS_MODIFY_SECTORS
|
||
pop es
|
||
|
||
mov ch, cl
|
||
xor cl, cl
|
||
shl cx, 1 ; Multiply by 512
|
||
mov [end_file_address], cx
|
||
|
||
mov si, text_window
|
||
mov di, window
|
||
mov cx, window_struc.size
|
||
rep movsb
|
||
|
||
mov byte [file_opened], 1
|
||
|
||
call render_window
|
||
call request_redraw
|
||
|
||
.end:
|
||
ret
|
||
|
||
; ------------------------------------------------------------------
|
||
; Text file handling
|
||
; ------------------------------------------------------------------
|
||
|
||
; 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 di, [window.x]
|
||
mov bp, [window.y]
|
||
mov si, [window.data_address]
|
||
|
||
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
|
||
event_click:
|
||
push ax
|
||
; 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 close_window
|
||
jmp .end
|
||
.not_close:
|
||
|
||
; Did the user click on the resize button?
|
||
cmp byte [file_opened], 0
|
||
je .not_resize ; Can't resize while entering filename
|
||
cmp [window.x], cx
|
||
jne .not_resize
|
||
.resize:
|
||
mov byte [window_status], WINDOW_STATUS_RESIZE
|
||
jmp .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
|
||
jmp .end
|
||
|
||
.not_title_bar:
|
||
cmp byte [file_opened], 0
|
||
jne .not_filename_window
|
||
.filename_window:
|
||
sub bx, [window.y]
|
||
sub cx, [window.x]
|
||
|
||
mov ax, [window.width]
|
||
shl ax, 1
|
||
mul bx
|
||
add ax, cx
|
||
add ax, cx
|
||
|
||
add ax, filename_window_data
|
||
mov bx, ax
|
||
inc bx
|
||
|
||
cmp byte [bx], CANCEL_ATTRIBUTE
|
||
jne .not_cancel
|
||
.cancel:
|
||
call unhook_self_from_window_chain
|
||
mov byte [exiting], 1
|
||
jmp .end
|
||
.not_cancel:
|
||
cmp byte [bx], OK_ATTRIBUTE
|
||
jne .not_ok
|
||
.ok:
|
||
call filename_ok
|
||
.not_ok:
|
||
jmp .end
|
||
|
||
.not_filename_window:
|
||
mov ax, [window.x]
|
||
add ax, [window.width]
|
||
dec ax
|
||
cmp ax, cx
|
||
jne .not_scroll_bar
|
||
|
||
; Scroll up button?
|
||
mov ax, [window.y]
|
||
inc ax
|
||
cmp ax, bx
|
||
jne .not_scroll_up
|
||
.scroll_up:
|
||
call file_prev_line
|
||
test dx, dx
|
||
jz .end
|
||
|
||
call render_window
|
||
call request_redraw
|
||
jmp .end
|
||
.not_scroll_up:
|
||
|
||
; Scroll down button?
|
||
add ax, [window.height]
|
||
dec ax
|
||
dec ax
|
||
cmp ax, bx
|
||
jne .not_scroll_down
|
||
.scroll_down:
|
||
call file_next_line
|
||
test dx, dx
|
||
jz .end
|
||
|
||
call render_window
|
||
call request_redraw
|
||
.not_scroll_down:
|
||
|
||
.not_scroll_bar:
|
||
.end:
|
||
pop ax
|
||
ret
|
||
|
||
; in:
|
||
; al = WM_KEYBOARD
|
||
; bx = window ID
|
||
; cl = typed character
|
||
; ch = pressed key
|
||
; out:
|
||
; clobbers everything
|
||
event_keyboard:
|
||
cmp byte [file_opened], 0
|
||
jne .file_opened
|
||
cmp word [window.data_address], filename_window_data
|
||
jne .ret ; error windows
|
||
.file_not_opened:
|
||
cmp ch, 0x0e
|
||
jne .not_backspace
|
||
.backspace:
|
||
call filename_char_del
|
||
ret
|
||
.not_backspace:
|
||
cmp ch, 0x1c
|
||
jne .not_enter
|
||
.enter:
|
||
call filename_ok
|
||
ret
|
||
.not_enter:
|
||
call filename_char_add
|
||
ret
|
||
|
||
.file_opened:
|
||
cmp ch, 0x50 ; down key
|
||
jne .up_key_check
|
||
.down_key:
|
||
call file_next_line
|
||
test dx, dx
|
||
jz .ret
|
||
|
||
call render_window
|
||
call request_redraw
|
||
ret
|
||
|
||
.up_key_check:
|
||
cmp ch, 0x48 ; up key
|
||
jne .space_check
|
||
.up_key:
|
||
call file_prev_line
|
||
test dx, dx
|
||
jz .ret
|
||
|
||
call render_window
|
||
call request_redraw
|
||
ret
|
||
|
||
.space_check:
|
||
cmp cl, ' '
|
||
jne .ret
|
||
.space:
|
||
; Go down eight lines
|
||
xor ax, ax
|
||
mov cx, 8
|
||
.loop:
|
||
call file_next_line
|
||
or ax, dx
|
||
loop .loop
|
||
test ax, ax
|
||
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
|
||
|
||
; in:
|
||
; al = WM_OPEN_FILE
|
||
; ds:cx = filename
|
||
; ds ≠ cs
|
||
; out:
|
||
; ds = cs
|
||
; clobbers everything
|
||
event_open_file:
|
||
; Copy the file name over
|
||
mov si, cx
|
||
mov di, [es:cur_filename_address]
|
||
.copy_filename:
|
||
lodsb
|
||
test al, al
|
||
jz .copy_end
|
||
|
||
stosb
|
||
inc di
|
||
jmp .copy_filename
|
||
.copy_end:
|
||
mov [es:cur_filename_address], di
|
||
|
||
; Set ds to be as expected by most of the program
|
||
push cs
|
||
pop ds
|
||
|
||
; Mark that the filename came from WM_OPEN_FILE
|
||
mov byte [filename_from_wm_open_file], 1
|
||
|
||
call filename_ok
|
||
|
||
ret
|
||
|
||
; ------------------------------------------------------------------
|
||
; Event handler subroutines
|
||
; ------------------------------------------------------------------
|
||
|
||
; out:
|
||
; clobbers si, di, cx
|
||
close_window:
|
||
; If filename was from WM_OPEN_FILE event instead of user input,
|
||
; exit the app instead of going to name input dialog
|
||
cmp byte [filename_from_wm_open_file], 0
|
||
jne .exit_app
|
||
cmp word [window.data_address], oom_window_data
|
||
je .error_window
|
||
cmp word [window.data_address], ood_window_data
|
||
je .error_window
|
||
|
||
.exit_app:
|
||
call unhook_self_from_window_chain
|
||
mov byte [exiting], 1
|
||
ret
|
||
|
||
.error_window:
|
||
mov si, filename_window
|
||
mov di, window
|
||
mov cx, window_struc.size
|
||
rep movsb
|
||
|
||
call request_redraw
|
||
ret
|
||
|
||
; 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, text_window_data
|
||
mov ax, [window.width]
|
||
mov cx, [window.height]
|
||
mul cx
|
||
mov cx, ax
|
||
mov ax, WINDOW_ATTRIBUTE<<8 ; Attribute is in the high byte
|
||
rep stosw
|
||
|
||
; Set title bar to be white-on-black
|
||
mov di, text_window_data
|
||
mov ax, TITLEBAR_ATTRIBUTE<<8
|
||
mov cx, [window.width]
|
||
rep stosw
|
||
|
||
; Add title bar buttons
|
||
mov di, text_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, text_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, text_window_data
|
||
add di, [window.width]
|
||
add di, [window.width]
|
||
call print_file
|
||
|
||
add di, [window.width]
|
||
add di, [window.width]
|
||
sub di, 2
|
||
mov byte [di], 0x1E ; up
|
||
|
||
mov ax, [window.width]
|
||
mov cx, [window.height]
|
||
sub cx, 2
|
||
shl cx, 1
|
||
mul cx
|
||
add di, ax
|
||
mov byte [di], 0x1F ; down
|
||
|
||
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
|
||
|
||
; ------------------------------------------------------------------
|
||
; Error handling
|
||
; ------------------------------------------------------------------
|
||
|
||
; in:
|
||
; si = window of error type
|
||
; out:
|
||
; clobbers si, di, cx
|
||
raise_error:
|
||
mov di, window
|
||
mov cx, window_struc.size
|
||
rep movsb
|
||
|
||
call request_redraw
|
||
ret
|
||
|
||
; ------------------------------------------------------------------
|
||
; Memory management
|
||
; ------------------------------------------------------------------
|
||
|
||
; out:
|
||
; dx = segment, 0 for none found
|
||
allocate_segment:
|
||
push ax
|
||
push cx
|
||
push si
|
||
push es
|
||
|
||
mov ax, PONYDOS_SEG
|
||
mov es, ax
|
||
mov si, GLOBAL_MEMORY_ALLOCATION_MAP
|
||
mov cx, MEM_ALLOCATION_MAP_SIZE
|
||
.find_free_segment:
|
||
mov al, [es:si]
|
||
test al, al
|
||
jz .found_free_segment
|
||
inc si
|
||
loop .find_free_segment
|
||
xor dx, dx
|
||
jmp .end
|
||
.found_free_segment:
|
||
mov byte [es:si], 1 ; Mark as used
|
||
|
||
; Set up ax to point to the allocated segment
|
||
sub si, GLOBAL_MEMORY_ALLOCATION_MAP
|
||
mov cl, 12
|
||
shl si, cl
|
||
mov dx, si
|
||
|
||
.end:
|
||
pop es
|
||
pop si
|
||
pop cx
|
||
pop ax
|
||
ret
|
||
|
||
deallocate_own_memory:
|
||
push bx
|
||
push cx
|
||
push es
|
||
|
||
mov bx, PONYDOS_SEG
|
||
mov es, bx
|
||
|
||
cmp byte [file_opened], 0
|
||
je .file_not_opened
|
||
.file_opened:
|
||
mov bx, [cur_file_address + 2]
|
||
mov cl, 12
|
||
shr bx, cl
|
||
|
||
mov byte [es:GLOBAL_MEMORY_ALLOCATION_MAP + bx], 0
|
||
.file_not_opened:
|
||
|
||
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
|
||
|
||
; ------------------------------------------------------------------
|
||
; Variables
|
||
; ------------------------------------------------------------------
|
||
|
||
filename_from_wm_open_file db 0
|
||
exiting db 0
|
||
|
||
window_title times FS_DIRENT_NAME_SIZE db 0
|
||
|
||
cur_file_address: dw 0
|
||
dw 0 ; Segment
|
||
beg_file_address dw 0
|
||
end_file_address dw 0
|
||
|
||
window:
|
||
.x dw 24
|
||
.y dw 9
|
||
.width dw FS_DIRENT_NAME_SIZE + 2
|
||
.height dw 6
|
||
.data_address dw filename_window_data
|
||
|
||
struc window_struc
|
||
.x resw 1
|
||
.y resw 1
|
||
.width resw 1
|
||
.height resw 1
|
||
.data_address resw 1
|
||
.size:
|
||
endstruc
|
||
|
||
filename_window:
|
||
.x dw 24
|
||
.y dw 9
|
||
.width dw FS_DIRENT_NAME_SIZE + 2
|
||
.height dw 6
|
||
.data_address dw filename_window_data
|
||
|
||
text_window:
|
||
.x dw 17
|
||
.y dw 7
|
||
.width dw 52
|
||
.height dw 16
|
||
.data_address dw text_window_data
|
||
|
||
oom_window:
|
||
.x dw 30
|
||
.y dw 10
|
||
.width dw 13
|
||
.height dw 2
|
||
.data_address dw oom_window_data
|
||
|
||
ood_window:
|
||
.x dw 36
|
||
.y dw 13
|
||
.width dw 17
|
||
.height dw 2
|
||
.data_address dw ood_window_data
|
||
|
||
window_next dw 0xffff
|
||
window_mouse_released_inside db 0
|
||
window_status db WINDOW_STATUS_NORMAL
|
||
window_move_x_offset dw 0
|
||
|
||
cur_filename_address dw filename_window_data.filename
|
||
file_opened db 0
|
||
|
||
; pre-built windows
|
||
filename_window_data:
|
||
; header
|
||
db 'V', TITLEBAR_ATTRIBUTE, 'i', TITLEBAR_ATTRIBUTE, 'e', TITLEBAR_ATTRIBUTE, 'w', TITLEBAR_ATTRIBUTE, 'e', TITLEBAR_ATTRIBUTE, 'r', TITLEBAR_ATTRIBUTE
|
||
times FS_DIRENT_NAME_SIZE + 2 - 7 db 0, TITLEBAR_ATTRIBUTE
|
||
db 'x', TITLEBAR_ATTRIBUTE
|
||
; blank line
|
||
times FS_DIRENT_NAME_SIZE + 2 db 0, OPEN_DIALOG_ATTRIBUTE
|
||
; filename
|
||
db 0, OPEN_DIALOG_ATTRIBUTE
|
||
.filename:
|
||
times FS_DIRENT_NAME_SIZE db 0, FILENAME_ATTRIBUTE
|
||
db 0, OPEN_DIALOG_ATTRIBUTE
|
||
; blank line
|
||
times FS_DIRENT_NAME_SIZE + 2 db 0, OPEN_DIALOG_ATTRIBUTE
|
||
; buttons line
|
||
times 7 db 0, OPEN_DIALOG_ATTRIBUTE
|
||
db 0, CANCEL_ATTRIBUTE, 'C', CANCEL_ATTRIBUTE, 'a', CANCEL_ATTRIBUTE, 'n', CANCEL_ATTRIBUTE, 'c', CANCEL_ATTRIBUTE, 'e', CANCEL_ATTRIBUTE, 'l', CANCEL_ATTRIBUTE, 0, CANCEL_ATTRIBUTE
|
||
times 5 db 0, OPEN_DIALOG_ATTRIBUTE
|
||
db 0, OK_ATTRIBUTE, 'O', OK_ATTRIBUTE, 'K', OK_ATTRIBUTE, 0x, OK_ATTRIBUTE
|
||
times 8 db 0, OPEN_DIALOG_ATTRIBUTE
|
||
; blank line
|
||
times FS_DIRENT_NAME_SIZE + 2 db 0, OPEN_DIALOG_ATTRIBUTE
|
||
|
||
oom_window_data:
|
||
db 'E', TITLEBAR_ATTRIBUTE, 'r', TITLEBAR_ATTRIBUTE, 'r', TITLEBAR_ATTRIBUTE, 'o', TITLEBAR_ATTRIBUTE, 'r', TITLEBAR_ATTRIBUTE
|
||
times 13-5-1 db 0x00, TITLEBAR_ATTRIBUTE
|
||
db 'x', TITLEBAR_ATTRIBUTE
|
||
db 'O', WINDOW_ATTRIBUTE, 'u', WINDOW_ATTRIBUTE, 't', WINDOW_ATTRIBUTE, ' ', WINDOW_ATTRIBUTE, 'o', WINDOW_ATTRIBUTE, 'f', WINDOW_ATTRIBUTE
|
||
db ' ', WINDOW_ATTRIBUTE, 'm', WINDOW_ATTRIBUTE, 'e', WINDOW_ATTRIBUTE, 'm', WINDOW_ATTRIBUTE, 'o', WINDOW_ATTRIBUTE, 'r', WINDOW_ATTRIBUTE
|
||
db 'y', WINDOW_ATTRIBUTE
|
||
|
||
ood_window_data:
|
||
db 'E', TITLEBAR_ATTRIBUTE, 'r', TITLEBAR_ATTRIBUTE, 'r', TITLEBAR_ATTRIBUTE, 'o', TITLEBAR_ATTRIBUTE, 'r', TITLEBAR_ATTRIBUTE
|
||
times 17-5-1 db 0x00, TITLEBAR_ATTRIBUTE
|
||
db 'x', TITLEBAR_ATTRIBUTE
|
||
db 'O', WINDOW_ATTRIBUTE, 'u', WINDOW_ATTRIBUTE, 't', WINDOW_ATTRIBUTE, ' ', WINDOW_ATTRIBUTE, 'o', WINDOW_ATTRIBUTE, 'f', WINDOW_ATTRIBUTE
|
||
db ' ', WINDOW_ATTRIBUTE, 'd', WINDOW_ATTRIBUTE, 'i', WINDOW_ATTRIBUTE, 's', WINDOW_ATTRIBUTE, 'k', WINDOW_ATTRIBUTE, ' ', WINDOW_ATTRIBUTE
|
||
db 's', WINDOW_ATTRIBUTE, 'p', WINDOW_ATTRIBUTE, 'a', WINDOW_ATTRIBUTE, 'c', WINDOW_ATTRIBUTE, 'e', WINDOW_ATTRIBUTE
|
||
|
||
section .bss
|
||
text_window_data resw ROWS*COLUMNS
|