ponydos/memory.asm

589 lines
12 KiB
NASM
Raw Permalink Normal View History

2023-03-27 10:00:48 +00:00
%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
2023-03-27 10:00:48 +00:00
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
2023-03-29 10:23:06 +00:00
.not_open_file:
2023-03-27 10:00:48 +00:00
.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:
2023-03-27 10:00:48 +00:00
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
2023-03-27 10:00:48 +00:00
; 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
2023-03-27 10:00:48 +00:00
; 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
2023-03-27 10:00:48 +00:00
; ------------------------------------------------------------------
; 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
2023-03-27 10:00:48 +00:00
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