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.
588 lines
12 KiB
NASM
588 lines
12 KiB
NASM
%include "ponydos.inc"
|
||
cpu 8086
|
||
bits 16
|
||
|
||
%ifdef BLINKY
|
||
TITLEBAR_ATTRIBUTE equ 0x07
|
||
WINDOW_ATTRIBUTE equ 0x70
|
||
%else
|
||
TITLEBAR_ATTRIBUTE equ 0x0f
|
||
WINDOW_ATTRIBUTE equ 0xf0
|
||
%endif
|
||
|
||
WINDOW_STATUS_NORMAL equ 0
|
||
WINDOW_STATUS_MOVE equ 1
|
||
|
||
; 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 ds, bp
|
||
mov es, bp
|
||
|
||
call hook_self_onto_window_chain
|
||
|
||
; 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:
|
||
|
||
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
|
||
; Once we have deallocated our own memory, we may not call any
|
||
; external functions that might allocate. Safest place to do the
|
||
; deallocation is just before returning control to our caller
|
||
call deallocate_own_memory
|
||
.not_exiting:
|
||
|
||
pop es
|
||
pop ds
|
||
pop bp
|
||
pop di
|
||
pop si
|
||
pop dx
|
||
pop cx
|
||
pop bx
|
||
retf
|
||
|
||
; ------------------------------------------------------------------
|
||
; 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
|
||
|
||
call update_usage_display
|
||
|
||
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 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:
|
||
|
||
; 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 unhook_self_from_window_chain
|
||
mov byte [exiting], 1
|
||
; We don't need to call request_redraw here, since
|
||
; it will be called unconditionally above
|
||
jmp .title_bar_end
|
||
.not_close:
|
||
|
||
; 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:
|
||
pop ax
|
||
ret
|
||
|
||
; in:
|
||
; al = WM_KEYBOARD
|
||
; bx = window ID
|
||
; cl = typed character
|
||
; ch = pressed key
|
||
; out:
|
||
; clobbers everything
|
||
event_keyboard:
|
||
; Unlike most other events, keyboard events are not forwarded
|
||
; Since we do not care about the keyboard for this app, we just
|
||
; swallow the event
|
||
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:
|
||
; File open events are sent specifically to a process, so we don't
|
||
; need to forward it
|
||
; Unlike other event handlers, event_open_file is called with ds
|
||
; still set to calling process's segment, so that it can read the
|
||
; passed-in filename. For simplicity of code running after the,
|
||
; event handlers, we set ds to point to our own segment on return
|
||
push cs
|
||
pop ds
|
||
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
|
||
|
||
update_usage_display:
|
||
push ax
|
||
push cx
|
||
push si
|
||
push di
|
||
push ds
|
||
|
||
mov cx, PONYDOS_SEG
|
||
mov ds, cx
|
||
|
||
mov si, GLOBAL_MEMORY_ALLOCATION_MAP
|
||
mov di, window_data.indicators
|
||
mov cx, MEM_ALLOCATION_MAP_SIZE
|
||
.loop:
|
||
lodsb
|
||
test al, al
|
||
jz .unused
|
||
.used:
|
||
mov al, 0xfe
|
||
jmp .loop_bottom
|
||
.unused:
|
||
xor al, al
|
||
|
||
.loop_bottom:
|
||
stosb
|
||
inc di
|
||
loop .loop
|
||
|
||
.end:
|
||
pop ds
|
||
pop di
|
||
pop si
|
||
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
|
||
|
||
; ------------------------------------------------------------------
|
||
; Variables
|
||
; ------------------------------------------------------------------
|
||
|
||
exiting db 0
|
||
|
||
window_next dw 0xffff
|
||
window_x dw 65
|
||
window_y dw 3
|
||
window_width dw 10
|
||
window_height dw 3
|
||
|
||
window_mouse_released_inside db 0
|
||
window_status db WINDOW_STATUS_NORMAL
|
||
window_move_x_offset dw 0
|
||
|
||
window_data:
|
||
db 'U', TITLEBAR_ATTRIBUTE, 's', TITLEBAR_ATTRIBUTE, 'a', TITLEBAR_ATTRIBUTE, 'g', TITLEBAR_ATTRIBUTE, 'e', TITLEBAR_ATTRIBUTE
|
||
times 10-5-1 db 0x00, TITLEBAR_ATTRIBUTE
|
||
db 'x', TITLEBAR_ATTRIBUTE
|
||
db '0', WINDOW_ATTRIBUTE, '1', WINDOW_ATTRIBUTE, '2', WINDOW_ATTRIBUTE, '3', WINDOW_ATTRIBUTE, '4', WINDOW_ATTRIBUTE, '5'
|
||
db WINDOW_ATTRIBUTE, '6', WINDOW_ATTRIBUTE, '7', WINDOW_ATTRIBUTE, '8', WINDOW_ATTRIBUTE, '9', WINDOW_ATTRIBUTE
|
||
.indicators: times 10 db 0x00, WINDOW_ATTRIBUTE
|