%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