;        ===== DISPLAY.ASM - процедуры работы с экраном ======

; === Рисуем рамку заданного размера в центре экрана ===
Draw_frame proc
     mov bp,sp ;BP = SP
     add bp,2  ;Увеличим BP на 2, т.к. первые два байта - возврат из процедуры

     push es ;Сохраним ES

     push 0B800h ;ES должен указывать на сегмент 0-ой видеостраницы
     pop es      ;это сработает только на 286+ процессоре!!!

;________________________________________
;
; Производим вычисления для того, чтобы
; разместить ЛЮБУЮ рамку в цетре экрана.
;________________________________________

     mov ax,Height_X ;AX = высота нашей рамки
     shr al,1        ;делим высоту на 2 путем смещения битов вправо на 1
     mov dh,11       ;Середина
     sub dh,al       ;DH (строка) ГОТОВА!!!!!

     mov ax,Width_Y  ;AX = ширина нашей рамки
     shr al,1        ;делим ее на 2
     mov dl,39       ;Середина
     sub dl,al       ;DL (колонка) ГОТОВА!!!!!
;Теперь DH содержит центрированный ряд (строку),
;а DL - колонку относительно размеров рамки (окошка)...
;_________________________________________

  ;Сохраним полученный адрес, с которого начинается вывод рамки
  ;Нужно для того, чтобы выводить сообщения в рамке.
     mov Num_DX,dx

     mov ax,Other ;Получим дополнительную информацию
     test al,1    ;Нулевой бит равен 0?
     jz No_copyscr ;Если так, то копировать экран не нужно.

     mov ax,Height_X ;Иначе копируем в область 2 видеостраницы
     add ax,2        ;+2, т.к. учитываем '╔═╗' и '╚═╝'
     call Copy_scr

No_copyscr:
     call Get_linear ;получаем линейный адрес в видеобуфере из DX,
     push di         ;который и сохраним...

     mov ax,Attr ;в AH - атрибуты цвета рамки
     mov al,'╔'  ;верхний левый угол...
     stosw       ;заносим два байта (атрибут в AH / символ в AL)

     mov al,'═'  ;далее...
     mov cx,Width_Y ;в CX - ширина рамки
     rep stosw   ;поехали...

     mov al,'╗'  ;завершаем верхний ряд
     stosw

     pop di      ;восстановим DI + 160 (следующий ряд)
     add di,160
     inc dh      ;DH "идет в ногу" с текущим рядом
                 ;нужно для того, чтобы вывести внизу рамки сообщение

;теперь у нас примерно такая ситуация на экране:
;╔═══════════════════════════╗
;_
;где _, там у нас DI и DH


     mov cx,Height_X ;CX - кол-во повторов (высота)

Next_lined:
     push cx ;сохраним счетчик
     push di ;сохраним DI

     mov al,'║' ;вывели этот символ
     stosw

     mov al,32      ;32 - пробел (или 20h или ' ')
     mov cx,Width_Y ;CX = ширина
     rep stosw      ;понеслась...

     mov al,'║' ;завершаем ряд...
     stosw

     pop di
     add di,160 ;переводим DI на следующий ряд
     inc dh     ;передвигаем DH на 1
     pop cx     ;восстановим счетчик
     loop Next_lined ;следующий ряд...

;теперь у нас примерно такая ситуация на экране:
;╔═══════════════════════════╗
;║                           ║
;║                           ║
;║                           ║
;_ - тут DI и DH


     mov al,'╚' ;низ рамки...
     stosw

     mov al,'═'
     mov cx,Width_Y
     rep stosw

     mov al,'╝'
     stosw

;теперь у нас примерно такая ситуация на экране:
;╔═══════════════════════════╗
;║                           ║
;║                           ║
;║                           ║
;╚═══════════════════════════╝


;Выводим сообщение внизу рамки
     mov si,Mess_dn   ;SI = адрес строки для вывода
     call Draw_messfr ;Выводим сообщение ВНИЗУ рамки
     ;Вот зачем нам нужно было постоянно увеличивать DH на 1
     ;(чтобы DH "шло в ногу" с DI)!

;теперь у нас примерно такая ситуация на экране:
;╔═══════════════════════════╗
;║                           ║
;║                           ║
;║                           ║
;╚════ Сообщение внизу ══════╝


;Выводим сообщение вверху рамки
     mov dx,Num_DX
     push dx
;Вот нам и адрес верхнего ряда понадобился!
     mov si,Mess_up   ;SI = адрес строки для вывода
     call Draw_messfr ;Выводим сообщение вверху рамки

;теперь у нас примерно такая ситуация на экране:
;╔════ Сообщение вверху ═════╗
;║                           ║
;║                           ║
;║                           ║
;╚════ Сообщение внизу ══════╝


     pop dx
     add dx,0101h
     mov si,Mess_ins ;Адрес сообщения, которое будет внутри рамки
     or si,si        ;Если там 0, то не выводим...
     jz No_draw
     mov ah,[si]
     inc si
     call Print_string ;Выводим строку...

;теперь у нас примерно такая ситуация на экране:
;╔════ Сообщение вверху ═════╗
;║Сообщение внутри           ║
;║                           ║
;║                           ║
;╚════ Сообщение внизу ══════╝


No_draw:
     mov ax,Other ;Получим дополнительную информацию
     test ax,10b  ;Первый бит равен нулю?
     jz No_upline ;Если так, то на метку No_upline

     mov dx,Num_dx ;Получим начальное значение рамки (верхний левый угол - ╔)
     add dh,2      ;Прибавим 2 для того, чтобы вывести линию (см. ниже)
     call Get_linear

     mov ax,Attr
     mov al,'╟'
     mov cx,1
     stosw

     mov cx,Width_Y ;в CX - ширина рамки
     mov al,'─'
     rep stosw

     mov al,'╢'
     stosw

;теперь у нас примерно такая ситуация на экране:
;╔════ Сообщение вверху ═════╗
;║Сообщение внутри           ║
;╟───────── линия ───────────╢
;║                           ║
;║                           ║
;╚════ Сообщение внизу ══════╝


No_upline:
     pop es ;восстановим ES

     ret 14 ;Выходим, очистив стек от переменных в 14 байт (7 слов)
            ;Посмотрите, в MAIN.ASM как мы вызываем данную процедуру.
Draw_frame endp


; --- Вывод сообщениий вверху и внизу рамки ---
;Вспомогательна процедура.
Draw_messfr proc
     or si,si ;SI = 0?..
     jz No_drawup ;тогда ничего выводить не надо, выходим

     mov ah,[si]  ;Первый символ строки - атрибут (см. DATA.ASM)
     inc si       ;Следующий байт - начало строки
     call Count_strmid ;Вычисляем середину строки

     call Print_string ;Выводим строку на экран

No_drawup:
     ret
Draw_messfr endp

; === Вычисляем середину строки ===
;Вход: CS:SI - адрес строки
;Выход: DL - середина адреса для вывода строки
Count_strmid proc
     push es ;Сохраним регистры...
     push di
     push ax

     push cs ;ES=CS
     pop es
     mov di,si ;DI=SI
     xor al,al ;AL=0
     mov cx,0FFFFh ;сколько символов перебирать (возьмем максимум)...
     repne scasb ;Ищем 0 в строке
     ;0 найден! DI указывает на следующий символ за найденным 0

;SI=начало строки
;DI=конец строки+1
     sub di,si ;DI=DI-SI-1 = длина строки
     dec di

     shr di,1  ;Делим длину на 2
     mov ax,40 ;Делим кол-во символов в строке на 2 = 40
     sub ax,di ;AX=40-половина длины строки = нужная колонка
     mov dl,al ;DL=колонка, с которой следует выводить строку!

     pop ax    ;Восстановим регистры
     pop di
     pop es
     ret
Count_strmid endp

; === Вывод стоки на экран ===
;Вход: DS:SI - адрес строки для вывода
;      DX - координаты для вывода
;      AH - атрибуты строки
;Выход: ничего
Print_string proc
     call Get_linear ;Получаем линейный адрес строки

Next_symstr:
     lodsb          ;Получаем очередной символ строки
     or al,al       ;Это 0 (конец строки?)
     jz Stop_outstr ;Да - выходим...
     stosw          ;Иначе заносим в видеобуфер атрибут (AH) и символ (AL)
     jmp short Next_Symstr ;Следующий символ

Stop_outstr:
     ret
Print_string endp

; === Преобразование DH:DL в линейный массив ===
Get_linear proc
    push ax    ;сохраним все используемые регистры
    push bx
    push dx

    shl dl,1   ;математика: умножаем DL на 2 (DL=DL*2)...

    mov al,dh  ;в AL - ряд,
    mov bl,160 ;который нужно умножить на 160
    mul bl     ;умножаем: AL(ряд)*160; результат --- в AX

    mov di,ax ;результат умножения - в DI
    xor dh,dh ;аннулируем DH
    add di,dx ;теперь в DI линейный адрес в видеобуфере.

    pop dx    ;восстанавливаем регистры...
    pop bx
    pop ax
    ret

Get_linear endp

; === Проверяем видеорежим монитора и текущую видеостраницу ===
Check_video proc
     mov ah,0Fh
     int 10h
     cmp al,3  ;Текстовый режим?
     je Ok_video

     mov ax,3
     int 10h

Ok_video:
     or bh,bh  ;Нулевая страница?
     jz Ok_page

     mov ax,0500h
     int 10h

Ok_page:
     ret
Check_video endp

; === Сохраним экран ===
Save_mainscr proc
    pusha       ;Сохраним регистры...
    push es
    push ds

    push 0B800h ;с нулевой страницы
    pop ds
    xor si,si   ;нулевого символа

    push 0B900h ;в первую страницу...
    pop es
    xor di,di   ;нулевой символ...

    mov cx,2000 ;4000 байт
    rep movsw   ;делаем копию.

    pop ds      ;восстановим регистры.
    pop es
    popa
    ret
Save_mainscr endp

; === Восстановим экран ===
Restore_mainscr proc
    pusha
    push es
    push ds

    push 0B900h ;с первой видеостраницы
    pop ds
    xor si,si

    push 0B800h ;в нулевую...
    pop es
    xor di,di

    mov cx,2000
    rep movsw

    pop ds ;очень просто и быстро!!!
    pop es
    popa
    ret
Restore_mainscr endp

; === Копируем часть экрана ===
;Вход: DH - ряд, с которого необходимо начать копирование
;      AL - количество рядов для копирования
;Выход: ничего
Copy_scr proc
    pusha   ;Как обычно сохраним регистры
    push es
    push ds

    xor dl,dl       ;Обнулим DL на всякий случай. Теперь DH = ряд, DL = 0
    call Get_linear ;Получим линейный адрес

    mov bl,160  ;Получим количество байт, котрые нужно копировать
    mul bl
    mov cx,ax   ;Их - в CX (будем использовать CX как счетчик)

    mov si,di   ;DS:SI - откуда копируем
    xor di,di   ;ES:SI - куда копируем
    mov Num_copySI,si ;Сохраним полученные значения для восстановления
    mov Num_copyDI,di
    mov Num_copyCX,cx
    push 0B800h ;Настроим сегментные регистры
    pop ds
    push 0BA00h
    pop es
    rep movsb  ;Копируем...

    pop ds     ;Восстановим регистры и выйдем...
    pop es
    popa
    ret        ;Теперь есть копия в самом начале 2-ой видеостраницы.

Num_copySI dw ?
Num_copyDI dw ?
Num_copyCX dw ?
Copy_scr endp

; === Восстанавливаем часть экрана ===
;Вход: ничего (все уже сохранено в переменных )
;Выход: ничего
Restore_scr proc
    pusha      ;Сохраним регистры
    push es
    push ds

    mov di,Num_copySI ;Получим сохраненные процедурой Copy_scr значения
    mov si,Num_copyDI
    mov cx,Num_copyCX
    push 0BA00h       ;Настроим сегментные регистры
    pop ds
    push 0B800h
    pop es
    rep movsb  ;Копируем со 2-ой страницы в 0-ую...

    pop ds     ;Восстановим регистры
    pop es
    popa
    ret
Restore_scr endp

; === Прячем курсор, сохранив предварительно его текущую позицию ===
Hide_cursor proc
    mov ah,3          ;получаем текущую позицию курсора
    mov bh,Video_page
    int 10h
    mov Pos_cursor,dx ;теперь она в DX (отсчет с нуля!)

    mov ah,2  ;Установим курсор на первую страницу как на нулевой
    mov bh,1  ;Нужно для того, если пользователь захочет
    int 10h   ;посмотреть, что там DOS написАла (Ctrl+F5)

    mov bh,Video_page ;прячем курсор на 0-ой видеостранице
    mov dx,1900h      ;(установим его на 25 строку (19h))
    int 10h

    ret
Hide_cursor endp

; === Восстановим курсор ===
Restore_cursor proc
    mov ah,2
    mov bh,Video_page ;видеостраница
    mov dx,Pos_cursor ;сохраненная позиция
    int 10h           ;установим (позиционируем) курсор
    ret
Restore_cursor endp