%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 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: .end: 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 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: 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 ; TODO: error management call allocate_segment ; TODO: error management 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 ; TODO: error management pop es mov ch, cl xor cl, cl shl cx, 1 ; Multiply by 512 mov [end_file_address], cx ; TODO: does this need to be restructured to be more safe to mouse ; interrupts? (I think not.) mov ax, [text_window_width] mov [window_width], ax mov ax, [text_window_height] mov [window_height], ax mov ax, [text_window_x] mov [window_x], ax mov ax, [text_window_y] mov [window_y], ax mov byte [file_opened], 1 call render_window call request_redraw 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 si, filename_window_data cmp byte [file_opened], 0 je .file_not_opened mov si, text_window_data .file_not_opened: 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] 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 unhook_self_from_window_chain ; Nothing can call into us again after we unhook ; the window, so deallocate the memory we have ; reserved call deallocate_own_memory ; We don't need to call request_redraw here, since ; it will be called unconditionally above 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 call deallocate_own_memory 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 .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 ; ------------------------------------------------------------------ ; 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 ; 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 ; ------------------------------------------------------------------ ; 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 ; ------------------------------------------------------------------ ; String functions ; ------------------------------------------------------------------ ; in: ; ds:si = string ; out: ; cx = stlen strlen: push ax push di push es mov cx, ds mov es, cx mov di, si mov cx, -1 xor ax, ax repne scasb not cx dec cx pop es pop di pop ax ret ; ------------------------------------------------------------------ ; Variables ; ------------------------------------------------------------------ tmp_window_title db "ponydos.asm", 0, 0 window_title times FS_DIRENT_NAME_SIZE + 1 db 0 cur_file_address: dw 0 ; Segment dw 0 beg_file_address dw 0 end_file_address dw 0 window_next dw 0xffff window_x dw 24 window_y dw 9 window_width dw FS_DIRENT_NAME_SIZE + 2 window_height dw 6 text_window_x dw 17 text_window_y dw 7 text_window_width dw 52 text_window_height dw 16 window_mouse_released_inside db 0 window_status db WINDOW_STATUS_NORMAL window_move_x_offset dw 0 CANCEL_COLOR equ 0x80 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, 0x80, 'C', 0x80, 'a', 0x80, 'n', 0x80, 'c', 0x80, 'e', 0x80, 'l', 0x80, 0, 0x80 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 cur_filename_address dw filename_window_data.filename file_opened db 0 section .bss text_window_data resw ROWS*COLUMNS