%include "ponydos.inc" cpu 8086 bits 16 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_COLOR jne .not_cancel .cancel: call unhook_self_from_window_chain mov byte [exiting], 1 jmp .end .not_cancel: cmp byte [bx], OK_COLOR 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, 0xf000 ; Attribute is in the high byte rep stosw ; Set title bar to be white-on-black mov di, text_window_data mov ax, 0x0f00 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 CANCEL_COLOR equ 0x8f OK_COLOR equ 0x20 filename_window_data: ; header db 'V', 0x0f, 'i', 0x0f, 'e', 0x0f, 'w', 0x0f, 'e', 0x0f, 'r', 0x0f times FS_DIRENT_NAME_SIZE + 2 - 7 db 0, 0x0f db 'x', 0x0f ; blank line times FS_DIRENT_NAME_SIZE + 2 db 0, 0x70 ; filename db 0, 0x70 .filename: times FS_DIRENT_NAME_SIZE db 0, 0xf0 db 0, 0x70 ; blank line times FS_DIRENT_NAME_SIZE + 2 db 0, 0x70 ; buttons line times 7 db 0, 0x70 db 0, 0x8f, 'C', 0x8f, 'a', 0x8f, 'n', 0x8f, 'c', 0x8f, 'e', 0x8f, 'l', 0x8f, 0, 0x8f times 5 db 0, 0x70 db 0, 0x20, 'O', 0x20, 'K', 0x20, 0x, 0x20 times 8 db 0, 0x70 ; blank line times FS_DIRENT_NAME_SIZE + 2 db 0, 0x70 oom_window_data: db 'E', 0x0f, 'r', 0x0f, 'r', 0x0f, 'o', 0x0f, 'r', 0x0f times 13-5-1 db 0x00, 0x0f db 'x', 0x0f db 'O', 0xf0, 'u', 0xf0, 't', 0xf0, ' ', 0xf0, 'o', 0xf0, 'f', 0xf0 db ' ', 0xf0, 'm', 0xf0, 'e', 0xf0, 'm', 0xf0, 'o', 0xf0, 'r', 0xf0 db 'y', 0xf0 ood_window_data: db 'E', 0x0f, 'r', 0x0f, 'r', 0x0f, 'o', 0x0f, 'r', 0x0f times 17-5-1 db 0x00, 0x0f db 'x', 0x0f db 'O', 0xf0, 'u', 0xf0, 't', 0xf0, ' ', 0xf0, 'o', 0xf0, 'f', 0xf0 db ' ', 0xf0, 'd', 0xf0, 'i', 0xf0, 's', 0xf0, 'k', 0xf0, ' ', 0xf0 db 's', 0xf0, 'p', 0xf0, 'a', 0xf0, 'c', 0xf0, 'e', 0xf0 section .bss text_window_data resw ROWS*COLUMNS