ponydos/memory.asm
Juhani Krekelä d4c15687c8 Add option for 8-colour mode
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.
2023-05-11 22:02:18 +03:00

588 lines
12 KiB
NASM
Raw Permalink 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
%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